fix(bitcoind_rpc): fix filter iter may not handle reorgs properly#1909
fix(bitcoind_rpc): fix filter iter may not handle reorgs properly#1909Musab1258 wants to merge 3 commits intobitcoindevkit:masterfrom
Conversation
f6fc514 to
71bf53d
Compare
4ef94df to
6f20e8a
Compare
| } | ||
| // Fetch next filter | ||
| let mut height = self.height; | ||
| let mut hash = self.client.get_block_hash(height as _)?; |
There was a problem hiding this comment.
Don't we need to compare this with the previous header returned? Otherwise how can we detect reorgs between separate calls to next?
There was a problem hiding this comment.
Hi @evanlinjin, Thanks for the review. I think the code addresses your concern about detecting reorgs between calls to next.
When next() finishes, it updates self.height on line 177 to the next height it expects to process. It also stores the hash and match status for the block it just processed in self.blocks on line 175 self.insert_block(height, hash);). And it persists both of self.height and self.blocks until the next call to next().
What was done on line 155 and line 156 is that the height and hash variables were initialized with the values of self.height (which is the height we expect to process next) and self.client.get_block_hash(height as _) (which is its hash)
Then, the code from lines 157 to 166 gets the header of the current block let header = self.client.get_block_header(&hash)?;, calculates the height of the assumed parent's block let prev_height = height.saturating_sub(1);, retrieves the hash of the assumed parent through its height, and compares it to the prev_blockhash field from the current block's header. If they match, then the chain is consistent, and if they don't, a reorg has occured.
There was a problem hiding this comment.
Thanks for the detailed explanation. I see where you're coming from, but this approach misses a common case: when a reorg replaces the block at the current height N while keeping the parent at N-1 unchanged. In that case, .prev_blockhash still matches what we expect, so your check passes — but the block at N has changed, and we silently continue on an inconsistent chain.
To fully detect reorgs between next() calls, we also need to compare the current block hash at N against what was previously seen at N. If it differs, that’s a reorg, even if the parent hasn’t changed.
Let me know if you’d like help implementing that check.
There was a problem hiding this comment.
Are you suggesting to also make use of the nextblockhash field of getblockheader RPC when validating headers?
What do you think @Musab1258?
| // blocks map | ||
| blocks: BTreeMap<Height, BlockHash>, | ||
| // map of height -> (hash, is_match) | ||
| blocks: BTreeMap<Height, (BlockHash, bool)>, |
There was a problem hiding this comment.
Tracking both BlockHash and is_match in the same map introduces unnecessary coupling. I’d suggest keeping blocks as-is, and recording matched blocks in a separate BTreeSet<Height>.
| let mut hash = self.client.get_block_hash(height as _)?; | ||
| loop { | ||
| let header = self.client.get_block_header(&hash)?; | ||
| let prev_height = height.saturating_sub(1); |
There was a problem hiding this comment.
I would consider adding a maximum reorg depth here to avoid unbounded backtracking in the case of a malicious server.
|
Thanks @Musab1258 for laying the groundwork here. I’ve incorporated your changes and built on them in #1985 to help get it across the finish line. Closing this PR in favor of the updated one. |
|
Hi @LagginTimes, I'm just seeing this, and I apologize for not responding to your review on time. I was busy with work and did not check my emails. After @evanlinjin's review and my response, I was expecting more reviews and was checking regularly. But as I got busy, I stopped checking frequently. However, I will check your PR to see the changes you made and also learn from it. |
Description
This PR fixes FilterIter not handling reorgs properly as stated in this issue #1848
Notes to the reviewers z
The code in this fix detects reorgs by checking if the previous_block_hash matches the hash of the previous block in the chain. It then finds the last common block between the old and new chains with the find_fork_point method. And resets the iterator's state to the fork point(i.e. point of reorg) and resume scanning from the new chain.
I also added tests to simulate a reorg and verifythat the iterator correctly switches to the new chain
Changelog notice
Checklists
All Submissions:
cargo fmtandcargo clippybefore committingNew Features:
Bugfixes: