-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Implement EIP234 block_hash for eth_getLogs #9256
Changes from all commits
69f2d05
5a13b56
74f0e03
6702767
5db8fc3
73684a8
ca22582
c7e3f2f
9c9b613
6f52ff9
e16292e
be2e2e2
8bf6a95
4b45f9a
2ad31e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1813,76 +1813,100 @@ impl BlockChainClient for Client { | |
| self.engine.additional_params().into_iter().collect() | ||
| } | ||
|
|
||
| fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry> { | ||
| // Wrap the logic inside a closure so that we can take advantage of question mark syntax. | ||
| let fetch_logs = || { | ||
| let chain = self.chain.read(); | ||
| fn logs(&self, filter: Filter) -> Result<Vec<LocalizedLogEntry>, BlockId> { | ||
| let chain = self.chain.read(); | ||
|
|
||
| // First, check whether `filter.from_block` and `filter.to_block` is on the canon chain. If so, we can use the | ||
| // optimized version. | ||
| let is_canon = |id| { | ||
| match id { | ||
| // If it is referred by number, then it is always on the canon chain. | ||
| &BlockId::Earliest | &BlockId::Latest | &BlockId::Number(_) => true, | ||
| // If it is referred by hash, we see whether a hash -> number -> hash conversion gives us the same | ||
| // result. | ||
| &BlockId::Hash(ref hash) => chain.is_canon(hash), | ||
| } | ||
| }; | ||
| // First, check whether `filter.from_block` and `filter.to_block` is on the canon chain. If so, we can use the | ||
| // optimized version. | ||
| let is_canon = |id| { | ||
| match id { | ||
| // If it is referred by number, then it is always on the canon chain. | ||
| &BlockId::Earliest | &BlockId::Latest | &BlockId::Number(_) => true, | ||
| // If it is referred by hash, we see whether a hash -> number -> hash conversion gives us the same | ||
| // result. | ||
| &BlockId::Hash(ref hash) => chain.is_canon(hash), | ||
| } | ||
| }; | ||
|
|
||
| let blocks = if is_canon(&filter.from_block) && is_canon(&filter.to_block) { | ||
| // If we are on the canon chain, use bloom filter to fetch required hashes. | ||
| let from = self.block_number_ref(&filter.from_block)?; | ||
| let to = self.block_number_ref(&filter.to_block)?; | ||
| let blocks = if is_canon(&filter.from_block) && is_canon(&filter.to_block) { | ||
| // If we are on the canon chain, use bloom filter to fetch required hashes. | ||
| // | ||
| // If we are sure the block does not exist (where val > best_block_number), then return error. Note that we | ||
| // don't need to care about pending blocks here because RPC query sets pending back to latest (or handled | ||
| // pending logs themselves). | ||
| let from = match self.block_number_ref(&filter.from_block) { | ||
| Some(val) if val <= chain.best_block_number() => val, | ||
| _ => return Err(filter.from_block.clone()), | ||
| }; | ||
| let to = match self.block_number_ref(&filter.to_block) { | ||
| Some(val) if val <= chain.best_block_number() => val, | ||
| _ => return Err(filter.to_block.clone()), | ||
| }; | ||
|
|
||
| chain.blocks_with_bloom(&filter.bloom_possibilities(), from, to) | ||
| .into_iter() | ||
| .filter_map(|n| chain.block_hash(n)) | ||
| .collect::<Vec<H256>>() | ||
| } else { | ||
| // Otherwise, we use a slower version that finds a link between from_block and to_block. | ||
| let from_hash = Self::block_hash(&chain, filter.from_block)?; | ||
| let from_number = chain.block_number(&from_hash)?; | ||
| let to_hash = Self::block_hash(&chain, filter.to_block)?; | ||
|
|
||
| let blooms = filter.bloom_possibilities(); | ||
| let bloom_match = |header: &encoded::Header| { | ||
| blooms.iter().any(|bloom| header.log_bloom().contains_bloom(bloom)) | ||
| }; | ||
| // If from is greater than to, then the current bloom filter behavior is to just return empty | ||
| // result. There's no point to continue here. | ||
| if from > to { | ||
| return Err(filter.to_block.clone()); | ||
| } | ||
|
|
||
| let (blocks, last_hash) = { | ||
| let mut blocks = Vec::new(); | ||
| let mut current_hash = to_hash; | ||
| chain.blocks_with_bloom(&filter.bloom_possibilities(), from, to) | ||
| .into_iter() | ||
| .filter_map(|n| chain.block_hash(n)) | ||
| .collect::<Vec<H256>>() | ||
| } else { | ||
| // Otherwise, we use a slower version that finds a link between from_block and to_block. | ||
| let from_hash = match Self::block_hash(&chain, filter.from_block) { | ||
| Some(val) => val, | ||
| None => return Err(filter.from_block.clone()), | ||
| }; | ||
| let from_number = match chain.block_number(&from_hash) { | ||
| Some(val) => val, | ||
| None => return Err(BlockId::Hash(from_hash)), | ||
| }; | ||
| let to_hash = match Self::block_hash(&chain, filter.to_block) { | ||
| Some(val) => val, | ||
| None => return Err(filter.to_block.clone()), | ||
| }; | ||
|
|
||
| loop { | ||
| let header = chain.block_header_data(¤t_hash)?; | ||
| if bloom_match(&header) { | ||
| blocks.push(current_hash); | ||
| } | ||
| let blooms = filter.bloom_possibilities(); | ||
| let bloom_match = |header: &encoded::Header| { | ||
| blooms.iter().any(|bloom| header.log_bloom().contains_bloom(bloom)) | ||
| }; | ||
|
|
||
| // Stop if `from` block is reached. | ||
| if header.number() <= from_number { | ||
| break; | ||
| } | ||
| current_hash = header.parent_hash(); | ||
| let (blocks, last_hash) = { | ||
| let mut blocks = Vec::new(); | ||
| let mut current_hash = to_hash; | ||
|
|
||
| loop { | ||
| let header = match chain.block_header_data(¤t_hash) { | ||
| Some(val) => val, | ||
| None => return Err(BlockId::Hash(current_hash)), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From RPC-side you'll input block numbers for
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part of the code only deals with the case where if either from_block or to_block is a block hash. If both of them are numbers (which is the case for RPC's |
||
| }; | ||
| if bloom_match(&header) { | ||
| blocks.push(current_hash); | ||
| } | ||
|
|
||
| blocks.reverse(); | ||
| (blocks, current_hash) | ||
| }; | ||
|
|
||
| // Check if we've actually reached the expected `from` block. | ||
| if last_hash != from_hash || blocks.is_empty() { | ||
| return None; | ||
| // Stop if `from` block is reached. | ||
| if header.number() <= from_number { | ||
| break; | ||
| } | ||
| current_hash = header.parent_hash(); | ||
| } | ||
|
|
||
| blocks | ||
| blocks.reverse(); | ||
| (blocks, current_hash) | ||
| }; | ||
|
|
||
| Some(self.chain.read().logs(blocks, |entry| filter.matches(entry), filter.limit)) | ||
| // Check if we've actually reached the expected `from` block. | ||
| if last_hash != from_hash || blocks.is_empty() { | ||
| // In this case, from_hash is the cause (for not matching last_hash). | ||
| return Err(BlockId::Hash(from_hash)); | ||
| } | ||
|
|
||
| blocks | ||
| }; | ||
|
|
||
| fetch_logs().unwrap_or_default() | ||
| Ok(self.chain.read().logs(blocks, |entry| filter.matches(entry), filter.limit)) | ||
| } | ||
|
|
||
| fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>> { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,8 @@ pub struct TestBlockChainClient { | |
| pub receipts: RwLock<HashMap<TransactionId, LocalizedReceipt>>, | ||
| /// Logs | ||
| pub logs: RwLock<Vec<LocalizedLogEntry>>, | ||
| /// Should return errors on logs. | ||
| pub error_on_logs: RwLock<Option<BlockId>>, | ||
| /// Block queue size. | ||
| pub queue_size: AtomicUsize, | ||
| /// Miner | ||
|
|
@@ -178,6 +180,7 @@ impl TestBlockChainClient { | |
| traces: RwLock::new(None), | ||
| history: RwLock::new(None), | ||
| disabled: AtomicBool::new(false), | ||
| error_on_logs: RwLock::new(None), | ||
| }; | ||
|
|
||
| // insert genesis hash. | ||
|
|
@@ -233,6 +236,11 @@ impl TestBlockChainClient { | |
| *self.logs.write() = logs; | ||
| } | ||
|
|
||
| /// Set return errors on logs. | ||
| pub fn set_error_on_logs(&self, val: Option<BlockId>) { | ||
| *self.error_on_logs.write() = val; | ||
| } | ||
|
|
||
| /// Add blocks to test client. | ||
| pub fn add_blocks(&self, count: usize, with: EachBlockWith) { | ||
| let len = self.numbers.read().len(); | ||
|
|
@@ -665,13 +673,18 @@ impl BlockChainClient for TestBlockChainClient { | |
| self.receipts.read().get(&id).cloned() | ||
| } | ||
|
|
||
| fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry> { | ||
| fn logs(&self, filter: Filter) -> Result<Vec<LocalizedLogEntry>, BlockId> { | ||
| match self.error_on_logs.read().as_ref() { | ||
| Some(id) => return Err(id.clone()), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to return both the block ID and the block hash?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
| None => (), | ||
| } | ||
|
|
||
| let mut logs = self.logs.read().clone(); | ||
| let len = logs.len(); | ||
| match filter.limit { | ||
| Ok(match filter.limit { | ||
| Some(limit) if limit <= len => logs.split_off(len - limit), | ||
| _ => logs, | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| fn last_hashes(&self) -> LastHashes { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our current use cases are mostly fine. We have cases where we set
fromBlockto a nonexistent future block (infilter_changes), but we actually just expect empty result in that case. I didn't find any case where we setfromBlockto a current block, buttoBlockto a nonexistent block.But please do note that this is a breaking change.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to validate that
from <= toas well? I don't think we validate that on the RPC layer.