-
Notifications
You must be signed in to change notification settings - Fork 2.7k
fix/chain_head: Ensure correct events for finalized branch #13632
Conversation
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
| // Make sure we have not encountered a gap in notifications. | ||
| // Note: Let `generate_import_events()` populate the cache. | ||
| if let Some(best_block_hash) = self.best_block_cache { | ||
| // If the best reported block is a children of the last finalized, | ||
| // then we had a gap in notification. | ||
| let ancestor = sp_blockchain::lowest_common_ancestor( | ||
| &*self.client, | ||
| *last_finalized, | ||
| best_block_hash, | ||
| )?; | ||
|
|
||
| // A descendent of the finalized block was already reported | ||
| // before the `NewBlock` event containing the finalized block | ||
| // is reported. | ||
| if ancestor.hash == *last_finalized { | ||
| return Err(SubscriptionManagementError::Custom( | ||
| "A descendent of the finalized block was already reported".into(), | ||
| )) | ||
| } | ||
| } |
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.
I don't get this piece of code? How do you know that there wasn't any new best block event for the last finalized block?
And why do you have this as part of of the for loop while it doesn't depend on any from the loop?
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.
Hey
How do you know that there wasn't any new best block event for the last finalized block?
We depend on the pin_block which returns true if this is the first time we "pin" the block and false if the block was previously pinned. We call pin_block twice for each block: from the import and finalized branches.
substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
Lines 364 to 369 in df6a438
| for (hash, parent) in finalized_block_hashes.iter().zip(parents) { | |
| // This block is already reported by the import notification. | |
| if !self.sub_handle.pin_block(*hash)? { | |
| continue | |
| } | |
And why do you have this as part of of the for loop while it doesn't depend on any from the loop?
For any blocks that we have not seen yet we want to generate the NewBlock, except for the last block for which we want to generate the NewBlock + BestBlock events. For the last block, I've added the extra check to make sure we catch the following case:
[Block 0] - [Block 1] - [Block 2] - ...
^^^ T0: Block import branch reports block 2 for the first time
^^^^^^^^^^^^^^^ T1: Finalized branch reports 0 and 1 for the first timeWe could enter this path only if the BlockImport notification is not generated for 0, 1; but somehow we receive the BlockImport for block 2. And at the same time we get Finalized for 0 and 1 (blocks that we have not been reported by BlockImport).
This check may be an overcautious one, let me know what you think of it, thanks!
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.
For me the flow was a little bit hard to follow. I pushed a commit that improves the flow and adds some more docs. I hope that is okay.
| }; | ||
| let Some(parent) = self.client.hash(first_number.saturating_sub(One::one()))? else { | ||
| let Some(first_header) = self.client.header(*first_hash)? else { | ||
| return Err(SubscriptionManagementError::BlockHashAbsent) |
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.
super nit: This sounds like the block hash is absent, but its more HeaderNotFound or something.
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.
That makes sense! I've changed it to BlockHeaderAbsent to make it explicit that we are missing the header from the db, thanks!
Co-authored-by: Sebastian Kunert <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
…h#13632) * chain_head/follow: Ensure correct events for finalized branch Signed-off-by: Alexandru Vasile <[email protected]> * Reenable tests Signed-off-by: Alexandru Vasile <[email protected]> * Do some clean ups and add some more docs * Fix gramatic * Update client/rpc-spec-v2/src/chain_head/chain_head_follow.rs Co-authored-by: Sebastian Kunert <[email protected]> * rpc/chain_head: Introduce error for absent headers Signed-off-by: Alexandru Vasile <[email protected]> --------- Signed-off-by: Alexandru Vasile <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Sebastian Kunert <[email protected]>
…h#13632) * chain_head/follow: Ensure correct events for finalized branch Signed-off-by: Alexandru Vasile <[email protected]> * Reenable tests Signed-off-by: Alexandru Vasile <[email protected]> * Do some clean ups and add some more docs * Fix gramatic * Update client/rpc-spec-v2/src/chain_head/chain_head_follow.rs Co-authored-by: Sebastian Kunert <[email protected]> * rpc/chain_head: Introduce error for absent headers Signed-off-by: Alexandru Vasile <[email protected]> --------- Signed-off-by: Alexandru Vasile <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Sebastian Kunert <[email protected]>
Issue paritytech/polkadot-sdk#48 has detected a few flaky tests coming form the chainHead.
Error
The error encountered (extracted from here):
Investigation
The issue happens on the following code path:
the finalized event is triggered before the new_block event
finalized branch is responsible for generating NewBlock + BestBlock event
substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
Lines 335 to 341 in 12c3235
the
best_block_cacheis set, however the intent was just to verify if we have encountered a gap insubstrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
Lines 394 to 404 in 0506da0
the
generate_import_events()checks if the best block was already encountered to not regenerate the event.substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
Lines 295 to 297 in 0506da0
Fix
Remove populating the
best_block_cacheon the finalized branch to allowgenerate_import_events()to properly generate theBestBlockevent for the latest finalized hash.Testing Done
cargo testin a loop withstress-ng --cpu 6 --io 4for 239 times on my machine. Although it gives us some confidence, I've only encountered theFinalizedevent arriving beforeNewBlockjust a few times in a more intensive test while introducing the code.Notes
// CC @paritytech/tools-team