Finality notification: Optimize calculation of stale heads#11200
Finality notification: Optimize calculation of stale heads#11200
Conversation
While looking into some problem on Versi where a collator seemed to be stuck. I found out that it was not stuck but there was a huge gap between last finalized and best block. This lead to a lot leaves and it was basically trapped inside some loop of reading block headers from the db to find the stale heads. While looking into this I found out that `leaves` already supports the feature to give us the stale heads relative easily. However, the semantics change a little bit. Instead of returning all stale heads of blocks that are not reachable anymore after finalizing a block, we currently only return heads with a number lower than the finalized block. This should be no problem, because these other leaves that are stale will be returned later when a block gets finalized which number is bigger than the block number of these leaves. While doing that, I also changed `tree_route` of the `FinalityNotification` to include the `old_finalized`. Based on the comment I assumed that this was already part of it. However, if wanted, I can revert this change.
client/api/src/leaves.rs
Outdated
| /// This means that no changes are done. | ||
| /// | ||
| /// Returns the leaves that would be displaced. | ||
| pub fn simulate_finalize_height(&self, number: N) -> FinalizationDisplaced<H, N> { |
There was a problem hiding this comment.
I'm happy to accept any kind of better name :D
There was a problem hiding this comment.
displaced_by_finalize_height?
client/api/src/client.rs
Outdated
| /// Path from the old finalized to new finalized parent (implicitly finalized blocks). | ||
| /// | ||
| /// This maps to the range `[old_finalized, new_finalized]`. |
There was a problem hiding this comment.
FWIW I was also under the impression that this would already include the last finalized block.
client/api/src/leaves.rs
Outdated
| /// This means that no changes are done. | ||
| /// | ||
| /// Returns the leaves that would be displaced. | ||
| pub fn simulate_finalize_height(&self, number: N) -> FinalizationDisplaced<H, N> { |
There was a problem hiding this comment.
displaced_by_finalize_height?
| // It is not guaranteed that `backend.blockchain().leaves()` doesn't return | ||
| // heads that were in a stale state before this finalization and thus already | ||
| // included in previous notifications. We want to skip such heads. | ||
| // Given the "route" from the currently finalized block to the head under | ||
| // analysis, the condition for it to be added to the new stale heads list is: | ||
| // `!retracted.is_empty() && last_finalized_number <= pivot.number` | ||
| // 1. "route" has some "retractions". | ||
| // 2. previously finalized block number is not greater than the "route" pivot: | ||
| // - if `last_finalized_number <= pivot.number` then this is a new stale head; | ||
| // - else the stale head was already included by some previous finalization. |
There was a problem hiding this comment.
This way we also don't need to deal with this as we are guaranteed that stuff only gets displaced once. Sorry for making you waste brain cycles @davxy.
There was a problem hiding this comment.
No worries. Has been very instructive anyway :-)
client/api/src/client.rs
Outdated
| pub header: Block::Header, | ||
| /// Path from the old finalized to new finalized parent (implicitly finalized blocks). | ||
| /// | ||
| /// This maps to the range `[old_finalized, new_finalized]`. |
There was a problem hiding this comment.
| /// This maps to the range `[old_finalized, new_finalized]`. | |
| /// This maps to the range `(old_finalized, new_finalized)`. |
The new_finalized hash is not included in the FinalityNotification::tree_route. Is extracted here when mapping the FinalizeSummary to FinalityNotification.
As has been discussed at the time, the last block is included in the FinalityNotification as a separate explicit field to be more "inline" with the design of the import notification.
The tree_route field should contain from old-finalized (excluded) to new finalized (excluded).
(the prev explicitly finalized block is not included here to prevent duplication, see request for change comment below)
There was a problem hiding this comment.
It already contained the new_finalized, so this suggestion is not really correct.
client/service/src/client/client.rs
Outdated
| let finalized = std::iter::once(last_finalized) | ||
| .chain(route_from_finalized.enacted().iter().map(|elem| elem.hash)) | ||
| .collect::<Vec<_>>(); |
There was a problem hiding this comment.
| let finalized = std::iter::once(last_finalized) | |
| .chain(route_from_finalized.enacted().iter().map(|elem| elem.hash)) | |
| .collect::<Vec<_>>(); | |
| let finalized = | |
| route_from_finalized.enacted().iter().map(|elem| elem.hash).collect::<Vec<_>>(); |
There was a problem hiding this comment.
I didn't included the last_fnalized (i.e. the previous last finalized block) to prevent a double notification for that block:
- 1st notification when it is the last finalized block (explicitly finalized one)
- 2nd notification when it is the fist implicitly finalized block
Indeed in the tests now a2 is repeated twice:
substrate/client/service/test/src/client/mod.rs
Lines 1326 to 1328 in 9ce46c3
Genesis is the only block for which a notification was never sent since it is never explicitly finalized via a block import (implicitly finalized at startup)
In this way if blocks are (as it is usually the "normal" case) finalized one after the other then for each finalized block you'll always receive two finalizations (currently finalized one + prev finalized one)
|
Excluded the requested modification about removing the previous last finalized block from the notification, LGTM and is indeed faster :-) |
|
|
||
| #[test] | ||
| fn finality_notifications_content() { | ||
| sp_tracing::try_init_simple(); |
There was a problem hiding this comment.
Sort of, but also can stay :)
Co-authored-by: André Silva <[email protected]>
|
Nevermind, you've already fixed it :-) |
|
bot merge |
|
Waiting for commit status. |
|
Merge cancelled due to error. Error: Checks failed for 7ed136b |
|
Does this help paritytech/cumulus#432? |
|
No |
|
That is something completely different. |
…h#11200) * Finality notification: Optimize calculation of stale heads While looking into some problem on Versi where a collator seemed to be stuck. I found out that it was not stuck but there was a huge gap between last finalized and best block. This lead to a lot leaves and it was basically trapped inside some loop of reading block headers from the db to find the stale heads. While looking into this I found out that `leaves` already supports the feature to give us the stale heads relative easily. However, the semantics change a little bit. Instead of returning all stale heads of blocks that are not reachable anymore after finalizing a block, we currently only return heads with a number lower than the finalized block. This should be no problem, because these other leaves that are stale will be returned later when a block gets finalized which number is bigger than the block number of these leaves. While doing that, I also changed `tree_route` of the `FinalityNotification` to include the `old_finalized`. Based on the comment I assumed that this was already part of it. However, if wanted, I can revert this change. * FMT * Update client/service/src/client/client.rs Co-authored-by: André Silva <[email protected]> * Do not include the last finalized block * Rename function * FMT * Fix tests * Update figure Co-authored-by: André Silva <[email protected]>
…h#11200) * Finality notification: Optimize calculation of stale heads While looking into some problem on Versi where a collator seemed to be stuck. I found out that it was not stuck but there was a huge gap between last finalized and best block. This lead to a lot leaves and it was basically trapped inside some loop of reading block headers from the db to find the stale heads. While looking into this I found out that `leaves` already supports the feature to give us the stale heads relative easily. However, the semantics change a little bit. Instead of returning all stale heads of blocks that are not reachable anymore after finalizing a block, we currently only return heads with a number lower than the finalized block. This should be no problem, because these other leaves that are stale will be returned later when a block gets finalized which number is bigger than the block number of these leaves. While doing that, I also changed `tree_route` of the `FinalityNotification` to include the `old_finalized`. Based on the comment I assumed that this was already part of it. However, if wanted, I can revert this change. * FMT * Update client/service/src/client/client.rs Co-authored-by: André Silva <[email protected]> * Do not include the last finalized block * Rename function * FMT * Fix tests * Update figure Co-authored-by: André Silva <[email protected]>
While looking into some problem on Versi where a collator seemed to be stuck. I found out that it
was not stuck but there was a huge gap between last finalized and best block. This lead to a lot
leaves and it was basically trapped inside some loop of reading block headers from the db to find
the stale heads. While looking into this I found out that
leavesalready supports the feature togive us the stale heads relative easily. However, the semantics change a little bit. Instead of
returning all stale heads of blocks that are not reachable anymore after finalizing a block, we
currently only return heads with a number lower than the finalized block. This should be no problem,
because these other leaves that are stale will be returned later when a block gets finalized which
number is bigger than the block number of these leaves.
While doing that, I also changed
tree_routeof theFinalityNotificationto include theold_finalized. Based on the comment I assumed that this was already part of it. However, ifwanted, I can revert this change.