Skip to content

Commit

Permalink
Yield pruned blocks from sync state machines and consensus service no…
Browse files Browse the repository at this point in the history
…tifications (#1120)
  • Loading branch information
tomaka authored Sep 8, 2023
1 parent e467848 commit 752d699
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 39 deletions.
40 changes: 25 additions & 15 deletions full-node/src/consensus_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,16 +471,15 @@ pub struct SubscriptionId(u64);
pub enum Notification {
/// A non-finalized block has been finalized.
Finalized {
/// BLAKE2 hash of the block that has been finalized.
/// BLAKE2 hash of the blocks that have been finalized, in increasing block number. In
/// other words, each block in this list is a child of the previous one. The first block
/// in this list is a child of the previous finalized block. The last block in this list
/// is the new finalized block.
///
/// A block with this hash is guaranteed to have earlier been reported in a
/// [`BlockNotification`], either in [`SubscribeAll::non_finalized_blocks_ancestry_order`]
/// or in a [`Notification::Block`].
///
/// It is, however, not guaranteed that this block is a child of the previously-finalized
/// block. In other words, if multiple blocks are finalized at the same time, only one
/// [`Notification::Finalized`] is generated and contains the highest finalized block.
hash: [u8; 32],
finalized_blocks_hashes: Vec<[u8; 32]>,

/// Hash of the best block after the finalization.
///
Expand All @@ -492,6 +491,10 @@ pub enum Notification {
/// [`BlockNotification`], either in [`SubscribeAll::non_finalized_blocks_ancestry_order`]
/// or in a [`Notification::Block`].
best_block_hash: [u8; 32],

/// List of BLAKE2 hashes of blocks that are no longer part of the canonical chain. In
/// unspecified order.
pruned_blocks_hashes: Vec<[u8; 32]>,
},

/// A new block has been added to the list of unfinalized blocks.
Expand Down Expand Up @@ -1899,7 +1902,8 @@ impl SyncBackground {
(
sync_out,
all::FinalityProofVerifyOutcome::NewFinalized {
mut finalized_blocks,
finalized_blocks,
pruned_blocks,
updates_best_block,
},
) => {
Expand All @@ -1922,14 +1926,15 @@ impl SyncBackground {
self.block_authoring = None;
}

let finalized_block = finalized_blocks.pop().unwrap();
let NonFinalizedBlock::Verified { runtime } = finalized_block.user_data
else {
unreachable!()
self.finalized_runtime = match &finalized_blocks.last().unwrap().user_data {
NonFinalizedBlock::Verified { runtime } => runtime.clone(),
_ => unreachable!(),
};
self.finalized_runtime = runtime;
let new_finalized_hash =
finalized_block.header.hash(self.sync.block_number_bytes());
let new_finalized_hash = finalized_blocks
.last()
.unwrap()
.header
.hash(self.sync.block_number_bytes());
// TODO: what if best block changed?
self.database
.with_database_detached(move |database| {
Expand All @@ -1942,7 +1947,12 @@ impl SyncBackground {
let subscription = self.blocks_notifications.swap_remove(index);
if subscription
.try_send(Notification::Finalized {
hash: new_finalized_hash,
finalized_blocks_hashes: finalized_blocks
.iter()
.map(|b| b.header.hash(self.sync.block_number_bytes()))
.rev()
.collect::<Vec<_>>(),
pruned_blocks_hashes: pruned_blocks.clone(),
best_block_hash: self.sync.best_block_hash(),
})
.is_err()
Expand Down
51 changes: 38 additions & 13 deletions lib/src/chain/blocks_tree/finality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,20 +201,17 @@ impl<T> NonFinalizedTree<T> {
///
/// The block must have been passed to [`NonFinalizedTree::verify_header`].
///
/// Returns an iterator containing the now-finalized blocks in decreasing block numbers. In
/// other words, the first element of the iterator is always the block whose hash is the
/// `block_hash` passed as parameter.
/// Returns an iterator containing the now-finalized blocks and the pruned blocks in "reverse
/// hierarchical order". Each block is yielded by the iterator before its parent.
///
/// > **Note**: This function returns blocks in decreasing block number, because any other
/// > ordering would incur a performance cost. While returning blocks in increasing
/// > block number would often be more convenient, the overhead of doing so is
/// > moved to the user.
/// > **Note**: This function returns blocks in this order, because any other ordering would
/// > incur a performance cost. While returning blocks in hierarchical order would
/// > often be more convenient, the overhead of doing so is moved to the user.
///
/// The pruning is completely performed, even if the iterator is dropped eagerly.
///
/// If necessary, the current best block will be updated to be a descendant of the
/// newly-finalized block.
// TODO: should return the pruned blocks as well
pub fn set_finalized_block(
&mut self,
block_hash: &[u8; 32],
Expand Down Expand Up @@ -589,7 +586,7 @@ impl<'a, T> SetFinalizedBlockIter<'a, T> {
}

impl<'a, T> Iterator for SetFinalizedBlockIter<'a, T> {
type Item = T;
type Item = RemovedBlock<T>;

fn next(&mut self) -> Option<Self::Item> {
loop {
Expand All @@ -600,10 +597,16 @@ impl<'a, T> Iterator for SetFinalizedBlockIter<'a, T> {
.blocks_by_best_score
.remove(&pruned.user_data.best_score);
debug_assert_eq!(_removed, Some(pruned.index));
if !pruned.is_prune_target_ancestor {
continue;
}
break Some(pruned.user_data.user_data);
break Some(RemovedBlock {
block_hash: pruned.user_data.hash,
scale_encoded_header: pruned.user_data.header,
user_data: pruned.user_data.user_data,
ty: if pruned.is_prune_target_ancestor {
RemovedBlockType::Finalized
} else {
RemovedBlockType::Pruned
},
});
}
}

Expand All @@ -625,3 +628,25 @@ pub enum SetFinalizedError {
/// Block must have been passed to [`NonFinalizedTree::insert_verified_header`] in the past.
UnknownBlock,
}

/// Block removed from the [`NonFinalizedTree`] by a [`SetFinalizedBlockIter`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RemovedBlock<T> {
/// Hash of the block.
pub block_hash: [u8; 32],
/// User data that was associated with that block in the [`NonFinalizedTree`].
pub user_data: T,
/// Reason why the block was removed.
pub ty: RemovedBlockType,
/// SCALE-encoded header of the block.
pub scale_encoded_header: Vec<u8>,
}

/// Reason why a block was removed from the [`NonFinalizedTree`] by a [`SetFinalizedBlockIter`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum RemovedBlockType {
/// Block is now part of the finalized chain.
Finalized,
/// Block is not a descendant of the new finalized block.
Pruned,
}
6 changes: 3 additions & 3 deletions lib/src/chain/fork_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ impl<T> ForkTree<T> {
/// Returns an iterator containing the removed elements.
/// All elements are removed from the tree even if the returned iterator is dropped eagerly.
///
/// Elements are returned in an unspecified order. However, all the elements for which
/// [`PrunedNode::is_prune_target_ancestor`] is `true` are guaranteed to be returned from
/// child to parent.
/// Elements are returned in an unspecified order. However, each element yielded is guaranteed
/// to be yielded *before* its parent gets yielded.
/// This can be more or less called "reverse hierarchical order".
///
/// In other words, doing `prune_ancestors(...).filter(|n| n.is_prune_target_ancestor)` is
/// guaranteed to return the elements in the same order as [`ForkTree::node_to_root_path`]
Expand Down
10 changes: 9 additions & 1 deletion lib/src/sync/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2697,6 +2697,7 @@ impl<TRq, TSrc, TBl> FinalityProofVerify<TRq, TSrc, TBl> {
sync,
all_forks::FinalityProofVerifyOutcome::NewFinalized {
finalized_blocks,
pruned_blocks,
updates_best_block,
},
) => (
Expand All @@ -2711,6 +2712,10 @@ impl<TRq, TSrc, TBl> FinalityProofVerify<TRq, TSrc, TBl> {
user_data: b.1.unwrap(),
})
.collect(),
pruned_blocks: pruned_blocks
.into_iter()
.map(|b| b.0.hash(self.shared.block_number_bytes))
.collect(),
updates_best_block,
},
),
Expand Down Expand Up @@ -2753,6 +2758,7 @@ impl<TRq, TSrc, TBl> FinalityProofVerify<TRq, TSrc, TBl> {
full: b.full.map(|b| BlockFull { body: b.body }),
})
.collect(),
pruned_blocks: Vec::new(),
updates_best_block: false,
},
),
Expand All @@ -2775,7 +2781,9 @@ pub enum FinalityProofVerifyOutcome<TBl> {
NewFinalized {
/// List of finalized blocks, in decreasing block number.
finalized_blocks: Vec<Block<TBl>>,
// TODO: missing pruned blocks
/// List of hashes of blocks that are no longer descendant of the finalized block, in
/// an unspecified order.
pruned_blocks: Vec<[u8; 32]>,
/// If `true`, this operation modifies the best block of the non-finalized chain.
/// This can happen if the previous best block isn't a descendant of the now finalized
/// block.
Expand Down
34 changes: 27 additions & 7 deletions lib/src/sync/all_forks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ enum FinalityProof {
}

struct Block<TBl> {
// TODO: redundant with NonFinalizedTree
header: header::Header,
user_data: TBl,
}
Expand Down Expand Up @@ -2163,15 +2164,24 @@ impl<TBl, TRq, TSrc> FinalityProofVerify<TBl, TRq, TSrc> {
// TODO: DRY
let finalized_blocks_iter = success.apply();
let updates_best_block = finalized_blocks_iter.updates_best_block();
let finalized_blocks = finalized_blocks_iter
.map(|b| (b.header, b.user_data))
.collect::<Vec<_>>();
let mut finalized_blocks = Vec::new();
let mut pruned_blocks = Vec::new();
for block in finalized_blocks_iter {
if matches!(block.ty, blocks_tree::RemovedBlockType::Finalized) {
finalized_blocks
.push((block.user_data.header, block.user_data.user_data));
} else {
pruned_blocks
.push((block.user_data.header, block.user_data.user_data));
}
}
let _finalized_blocks =
self.parent.inner.blocks.set_finalized_block_height(
finalized_blocks.last().unwrap().0.number,
);
FinalityProofVerifyOutcome::NewFinalized {
finalized_blocks,
pruned_blocks,
updates_best_block,
}
}
Expand Down Expand Up @@ -2218,15 +2228,24 @@ impl<TBl, TRq, TSrc> FinalityProofVerify<TBl, TRq, TSrc> {
Ok(success) => {
let finalized_blocks_iter = success.apply();
let updates_best_block = finalized_blocks_iter.updates_best_block();
let finalized_blocks = finalized_blocks_iter
.map(|b| (b.header, b.user_data))
.collect::<Vec<_>>();
let mut finalized_blocks = Vec::new();
let mut pruned_blocks = Vec::new();
for block in finalized_blocks_iter {
if matches!(block.ty, blocks_tree::RemovedBlockType::Finalized) {
finalized_blocks
.push((block.user_data.header, block.user_data.user_data));
} else {
pruned_blocks
.push((block.user_data.header, block.user_data.user_data));
}
}
let _finalized_blocks =
self.parent.inner.blocks.set_finalized_block_height(
finalized_blocks.last().unwrap().0.number,
);
FinalityProofVerifyOutcome::NewFinalized {
finalized_blocks,
pruned_blocks,
updates_best_block,
}
}
Expand Down Expand Up @@ -2319,7 +2338,8 @@ pub enum FinalityProofVerifyOutcome<TBl> {
/// List of finalized blocks, in decreasing block number.
// TODO: use `Vec<u8>` instead of `Header`?
finalized_blocks: Vec<(header::Header, TBl)>,
// TODO: missing pruned blocks
/// List of blocks that aren't descendant of the latest finalized block, in an unspecified order.
pruned_blocks: Vec<(header::Header, TBl)>,
/// If `true`, this operation modifies the best block of the non-finalized chain.
/// This can happen if the previous best block isn't a descendant of the now finalized
/// block.
Expand Down
2 changes: 2 additions & 0 deletions lib/src/sync/optimistic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,8 @@ impl<TRq, TSrc, TBl> JustificationVerify<TRq, TSrc, TBl> {
// complexity to avoid it is probably not worth the speed gain.
let finalized_blocks = apply
.apply()
.filter(|b| matches!(b.ty, blocks_tree::RemovedBlockType::Finalized))
.map(|b| b.user_data)
.collect::<Vec<_>>()
.into_iter()
.rev()
Expand Down

0 comments on commit 752d699

Please sign in to comment.