Missing blocks in filter_changes RPC (#9947)
* feat + fix: Missing blocks in filter_changes RPC * Implement validity check of reported hashes (in case of re-org) and re-set last_block to the last block reported that is still valid. * Add const to set history size for validity check (32). * test: in the case of a re-org we get same block number if hash is different
This commit is contained in:
parent
8b607efc40
commit
03600dce97
@ -17,7 +17,7 @@
|
||||
//! Helper type with all filter state data.
|
||||
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
collections::{BTreeSet, HashSet, VecDeque},
|
||||
sync::Arc,
|
||||
};
|
||||
use ethereum_types::H256;
|
||||
@ -49,7 +49,11 @@ impl SyncPollFilter {
|
||||
#[derive(Clone)]
|
||||
pub enum PollFilter {
|
||||
/// Number of last block which client was notified about.
|
||||
Block(BlockNumber),
|
||||
Block {
|
||||
last_block_number: BlockNumber,
|
||||
#[doc(hidden)]
|
||||
recent_reported_hashes: VecDeque<(BlockNumber, H256)>,
|
||||
},
|
||||
/// Hashes of all pending transactions the client knows about.
|
||||
PendingTransaction(BTreeSet<H256>),
|
||||
/// Number of From block number, last seen block hash, pending logs and log filter itself.
|
||||
@ -62,6 +66,10 @@ pub enum PollFilter {
|
||||
}
|
||||
}
|
||||
|
||||
impl PollFilter {
|
||||
pub (in v1) const MAX_BLOCK_HISTORY_SIZE: usize = 32;
|
||||
}
|
||||
|
||||
/// Returns only last `n` logs
|
||||
pub fn limit_logs(mut logs: Vec<Log>, limit: Option<usize>) -> Vec<Log> {
|
||||
let len = logs.len();
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! Eth Filter RPC implementation
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
|
||||
use ethcore::miner::{self, MinerService};
|
||||
use ethcore::filter::Filter as EthcoreFilter;
|
||||
@ -153,7 +153,10 @@ impl<T: Filterable + Send + Sync + 'static> EthFilter for T {
|
||||
fn new_block_filter(&self) -> Result<RpcU256> {
|
||||
let mut polls = self.polls().lock();
|
||||
// +1, since we don't want to include the current block
|
||||
let id = polls.create_poll(SyncPollFilter::new(PollFilter::Block(self.best_block_number() + 1)));
|
||||
let id = polls.create_poll(SyncPollFilter::new(PollFilter::Block {
|
||||
last_block_number: self.best_block_number(),
|
||||
recent_reported_hashes: VecDeque::with_capacity(PollFilter::MAX_BLOCK_HISTORY_SIZE),
|
||||
}));
|
||||
Ok(id.into())
|
||||
}
|
||||
|
||||
@ -171,15 +174,33 @@ impl<T: Filterable + Send + Sync + 'static> EthFilter for T {
|
||||
};
|
||||
|
||||
Box::new(filter.modify(|filter| match *filter {
|
||||
PollFilter::Block(ref mut block_number) => {
|
||||
// +1, cause we want to return hashes including current block hash.
|
||||
let current_number = self.best_block_number() + 1;
|
||||
let hashes = (*block_number..current_number).into_iter()
|
||||
.map(BlockId::Number)
|
||||
.filter_map(|id| self.block_hash(id).map(Into::into))
|
||||
.collect::<Vec<RpcH256>>();
|
||||
|
||||
*block_number = current_number;
|
||||
PollFilter::Block {
|
||||
ref mut last_block_number,
|
||||
ref mut recent_reported_hashes,
|
||||
} => {
|
||||
// Check validity of recently reported blocks -- in case of re-org, rewind block to last valid
|
||||
while let Some((num, hash)) = recent_reported_hashes.front().cloned() {
|
||||
if self.block_hash(BlockId::Number(num)) == Some(hash) { break; }
|
||||
*last_block_number = num - 1;
|
||||
recent_reported_hashes.pop_front();
|
||||
}
|
||||
let current_number = self.best_block_number();
|
||||
let mut hashes = Vec::new();
|
||||
for n in (*last_block_number + 1)..=current_number {
|
||||
let block_number = BlockId::Number(n);
|
||||
match self.block_hash(block_number) {
|
||||
Some(hash) => {
|
||||
*last_block_number = n;
|
||||
hashes.push(RpcH256::from(hash));
|
||||
// Only keep the most recent history
|
||||
if recent_reported_hashes.len() >= PollFilter::MAX_BLOCK_HISTORY_SIZE {
|
||||
recent_reported_hashes.pop_back();
|
||||
}
|
||||
recent_reported_hashes.push_front((n, hash));
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
Either::A(future::ok(FilterChanges::Hashes(hashes)))
|
||||
},
|
||||
|
@ -317,10 +317,26 @@ fn rpc_blocks_filter() {
|
||||
|
||||
tester.client.add_blocks(2, EachBlockWith::Nothing);
|
||||
|
||||
let hash1 = tester.client.block_hash(BlockId::Number(1)).unwrap();
|
||||
let hash2 = tester.client.block_hash(BlockId::Number(2)).unwrap();
|
||||
let response = format!(
|
||||
r#"{{"jsonrpc":"2.0","result":["0x{:x}","0x{:x}"],"id":1}}"#,
|
||||
tester.client.block_hash(BlockId::Number(1)).unwrap(),
|
||||
tester.client.block_hash(BlockId::Number(2)).unwrap());
|
||||
hash1,
|
||||
hash2);
|
||||
|
||||
assert_eq!(tester.io.handle_request_sync(request_changes), Some(response.to_owned()));
|
||||
|
||||
// in the case of a re-org we get same block number if hash is different - BlockId::Number(2)
|
||||
tester.client.blocks.write().remove(&hash2).unwrap();
|
||||
tester.client.numbers.write().remove(&2).unwrap();
|
||||
*tester.client.last_hash.write() = hash1;
|
||||
tester.client.add_blocks(2, EachBlockWith::Uncle);
|
||||
|
||||
let request_changes = r#"{"jsonrpc": "2.0", "method": "eth_getFilterChanges", "params": ["0x0"], "id": 2}"#;
|
||||
let response = format!(
|
||||
r#"{{"jsonrpc":"2.0","result":["0x{:x}","0x{:x}"],"id":2}}"#,
|
||||
tester.client.block_hash(BlockId::Number(2)).unwrap(),
|
||||
tester.client.block_hash(BlockId::Number(3)).unwrap());
|
||||
|
||||
assert_eq!(tester.io.handle_request_sync(request_changes), Some(response.to_owned()));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user