-
Notifications
You must be signed in to change notification settings - Fork 788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chainHead/follow: Provide multiple block hashes to the initialized event #3445
Changes from 9 commits
21f0661
2c33377
4869964
2b4f2fd
543bc92
7b66662
c993cec
3960b27
fd877ed
c8ff561
3aa6c60
cbeecb2
626a33a
1989d41
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 |
---|---|---|
|
@@ -42,7 +42,14 @@ use sp_blockchain::{ | |
Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, Info, | ||
}; | ||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; | ||
use std::{collections::HashSet, sync::Arc}; | ||
use std::{ | ||
collections::{HashSet, VecDeque}, | ||
sync::Arc, | ||
}; | ||
|
||
/// The maximum number of finalized blocks provided by the | ||
/// `Initialized` event. | ||
const MAX_FINALIZED_BLOCKS: usize = 16; | ||
|
||
use super::subscription::InsertedSubscriptionData; | ||
|
||
|
@@ -95,6 +102,8 @@ struct InitialBlocks<Block: BlockT> { | |
/// | ||
/// It is a tuple of (block hash, parent hash). | ||
finalized_block_descendants: Vec<(Block::Hash, Block::Hash)>, | ||
/// Hashes of the last finalized blocks | ||
finalized_block_hashes: VecDeque<Block::Hash>, | ||
/// Blocks that should not be reported as pruned by the `Finalized` event. | ||
/// | ||
/// Substrate database will perform the pruning of height N at | ||
|
@@ -178,13 +187,14 @@ where | |
} | ||
|
||
/// Get the in-memory blocks of the client, starting from the provided finalized hash. | ||
/// | ||
/// The reported blocks are pinned by this function. | ||
fn get_init_blocks_with_forks( | ||
&self, | ||
startup_point: &StartupPoint<Block>, | ||
finalized: Block::Hash, | ||
) -> Result<InitialBlocks<Block>, SubscriptionManagementError> { | ||
let blockchain = self.backend.blockchain(); | ||
let leaves = blockchain.leaves()?; | ||
let finalized = startup_point.finalized_hash; | ||
let mut pruned_forks = HashSet::new(); | ||
let mut finalized_block_descendants = Vec::new(); | ||
let mut unique_descendants = HashSet::new(); | ||
|
@@ -202,13 +212,40 @@ where | |
|
||
for pair in blocks.zip(parents) { | ||
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. Unrelated to this PR but I don't understand the logic with Parents = blocks.len() + 1 Then the Example what I mean: let mut a = vec![1, 2];
let mut tmp = vec![3, 4];
let b = std::iter::once(99).chain(tmp);
let res: Vec<(usize, usize)> = a.into_iter().zip(b).collect();
assert_eq!(res, vec![(1, 99), (2, 3)]); 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. Yep, that makes sense! Indeed we are ignoring the last element of the We are interested in grouping every block reported by For example: let blocks = vec!["A", "B", "C"];
let parents = vec!["gensiss", "A", "B", "C"];
for pair in blocks.iter().zip(parents.iter()) {
println!("block={} parent={}", pair.0, pair.1);
} This should produce the following result: block=A parent=gensiss
block=B parent=A
block=C parent=B Let me have a go at this to remove the |
||
if unique_descendants.insert(pair) { | ||
// The finalized block is pinned below. | ||
self.sub_handle.pin_block(&self.sub_id, pair.0)?; | ||
finalized_block_descendants.push(pair); | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(InitialBlocks { finalized_block_descendants, pruned_forks }) | ||
let mut current_block = finalized; | ||
// The header of the finalized block must not be pruned. | ||
let Some(header) = blockchain.header(current_block)? else { | ||
return Err(SubscriptionManagementError::BlockHeaderAbsent); | ||
}; | ||
|
||
// Report at most `MAX_FINALIZED_BLOCKS`. Note: The node might not have that many blocks. | ||
let mut finalized_block_hashes = VecDeque::with_capacity(MAX_FINALIZED_BLOCKS); | ||
|
||
// Pin the finalized block. | ||
self.sub_handle.pin_block(&self.sub_id, current_block)?; | ||
finalized_block_hashes.push_front(current_block); | ||
current_block = *header.parent_hash(); | ||
|
||
for _ in 0..MAX_FINALIZDED_BLOCKS - 1 { | ||
niklasad1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let Ok(Some(header)) = blockchain.header(current_block) else { break }; | ||
// Block cannot be reported if pinning fails. | ||
if self.sub_handle.pin_block(&self.sub_id, current_block).is_err() { | ||
break | ||
}; | ||
|
||
finalized_block_hashes.push_front(current_block); | ||
current_block = *header.parent_hash(); | ||
} | ||
|
||
Ok(InitialBlocks { finalized_block_descendants, finalized_block_hashes, pruned_forks }) | ||
} | ||
|
||
/// Generate the initial events reported by the RPC `follow` method. | ||
|
@@ -220,18 +257,17 @@ where | |
startup_point: &StartupPoint<Block>, | ||
) -> Result<(Vec<FollowEvent<Block::Hash>>, HashSet<Block::Hash>), SubscriptionManagementError> | ||
{ | ||
let init = self.get_init_blocks_with_forks(startup_point)?; | ||
let init = self.get_init_blocks_with_forks(startup_point.finalized_hash)?; | ||
|
||
// The initialized event is the first one sent. | ||
let initial_blocks = init.finalized_block_descendants; | ||
let finalized_block_hashes = init.finalized_block_hashes; | ||
|
||
// The initialized event is the first one sent. | ||
let finalized_block_hash = startup_point.finalized_hash; | ||
self.sub_handle.pin_block(&self.sub_id, finalized_block_hash)?; | ||
|
||
let finalized_block_runtime = self.generate_runtime_event(finalized_block_hash, None); | ||
|
||
let initialized_event = FollowEvent::Initialized(Initialized { | ||
finalized_block_hash, | ||
finalized_block_hashes: finalized_block_hashes.into(), | ||
finalized_block_runtime, | ||
with_runtime: self.with_runtime, | ||
}); | ||
|
@@ -240,8 +276,6 @@ where | |
|
||
finalized_block_descendants.push(initialized_event); | ||
for (child, parent) in initial_blocks.into_iter() { | ||
self.sub_handle.pin_block(&self.sub_id, child)?; | ||
|
||
let new_runtime = self.generate_runtime_event(child, Some(parent)); | ||
|
||
let event = FollowEvent::NewBlock(NewBlock { | ||
|
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.
Not a comment related to this PR, but could we do some kind of sanity check of the distance between leaf and finalized block? This code assumes that they are close together, as they should be. However, on parachains for example, the finalization of para blocks is dependent on the relay chain.
If the embedded relay chain node syncs slower while the parachain node is already at the tip, between a leaf and the known finalized block could be millions of blocks (until relay chain has caught up).
This would lead to:
Maybe we should add some kind of safeguard against such situations.
Certainly an edge case however.
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.
Have create this PR to handle that edge-case: #3562 🙏