Skip to content
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

Fix NonFinalizedTree::finality_checkpoints and add caches #1127

Merged
merged 7 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions lib/src/chain/blocks_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ use crate::{
header,
};

use alloc::{collections::BTreeMap, format, sync::Arc, vec::Vec};
use alloc::{
collections::{BTreeMap, BTreeSet},
format,
sync::Arc,
vec::Vec,
};
use core::{cmp, fmt, mem, num::NonZeroU64, ops, time::Duration};
use hashbrown::HashMap;

Expand Down Expand Up @@ -113,6 +118,8 @@ pub struct NonFinalizedTree<T> {
finalized_block_header: Vec<u8>,
/// Hash of [`NonFinalizedTree::finalized_block_header`].
finalized_block_hash: [u8; 32],
/// Number of [`NonFinalizedTree::finalized_block_header`].
finalized_block_number: u64,
/// State of the chain finality engine.
finality: Finality,
/// State of the consensus of the finalized block.
Expand All @@ -136,6 +143,10 @@ pub struct NonFinalizedTree<T> {
/// Blocks indexed by the value in [`Block::best_score`]. The best block is the one with the
/// highest score.
blocks_by_best_score: BTreeMap<BestScore, fork_tree::NodeIndex>,
/// Subset of [`NonFinalizedTree::blocks`] whose [`BlockFinality::Grandpa::triggers_change`]
/// is `true`, indexed by the value in
/// [`BlockFinality::Grandpa::prev_auth_change_trigger_number`].
blocks_trigger_gp_change: BTreeSet<(Option<u64>, fork_tree::NodeIndex)>,
/// See [`Config::block_number_bytes`].
block_number_bytes: usize,
/// See [`Config::allow_unknown_consensus_engines`].
Expand All @@ -153,15 +164,14 @@ impl<T> NonFinalizedTree<T> {
let chain_information: chain_information::ChainInformation =
config.chain_information.into();

let finalized_block_hash = chain_information
.finalized_block_header
.hash(config.block_number_bytes);

NonFinalizedTree {
finalized_block_number: chain_information.finalized_block_header.number,
finalized_block_hash: chain_information
.finalized_block_header
.hash(config.block_number_bytes),
finalized_block_header: chain_information
.finalized_block_header
.scale_encoding_vec(config.block_number_bytes),
finalized_block_hash,
finality: match chain_information.finality {
chain_information::ChainInformationFinality::Outsourced => Finality::Outsourced,
chain_information::ChainInformationFinality::Grandpa {
Expand Down Expand Up @@ -210,6 +220,7 @@ impl<T> NonFinalizedTree<T> {
Default::default(),
),
blocks_by_best_score: BTreeMap::new(),
blocks_trigger_gp_change: BTreeSet::new(),
block_number_bytes: config.block_number_bytes,
allow_unknown_consensus_engines: config.allow_unknown_consensus_engines,
}
Expand All @@ -220,6 +231,7 @@ impl<T> NonFinalizedTree<T> {
self.blocks.clear();
self.blocks_by_hash.clear();
self.blocks_by_best_score.clear();
self.blocks_trigger_gp_change.clear();
}

/// Returns true if there isn't any non-finalized block in the chain.
Expand Down Expand Up @@ -560,6 +572,8 @@ struct Block<T> {
/// Cache of the hash of the block. Always equal to the hash of the header stored in this
/// same struct.
hash: [u8; 32],
/// Number contained in [`Block::header`].
number: u64,
/// Changes to the consensus made by the block.
consensus: BlockConsensus,
/// Information about finality attached to each block.
Expand Down
93 changes: 45 additions & 48 deletions lib/src/chain/blocks_tree/finality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use super::*;
use crate::finality::{grandpa, justification};

use core::{cmp, iter};
use core::cmp;

impl<T> NonFinalizedTree<T> {
/// Returns a list of blocks (by their height and hash) that need to be finalized before any
Expand All @@ -31,38 +31,30 @@ impl<T> NonFinalizedTree<T> {
/// [`NonFinalizedTree::verify_grandpa_commit_message`], unless they descend from any of the
/// blocks returned by this function, in which case that block must be finalized beforehand.
pub fn finality_checkpoints(&self) -> impl Iterator<Item = (u64, &[u8; 32])> {
match &self.finality {
Finality::Outsourced => {
// No checkpoint means all blocks allowed.
either::Left(iter::empty())
}
Finality::Grandpa { .. } => {
// TODO: O(n), could add a cache to make it O(1)
let iter = self
.blocks
.iter_unordered()
.filter(move |(_, block)| {
if let BlockFinality::Grandpa {
triggers_change, ..
} = &block.finality
{
*triggers_change
} else {
unreachable!()
}
})
.map(|(_, block)| {
(
header::decode(&block.header, self.block_number_bytes)
.unwrap()
.number,
&block.hash,
)
});
// Note that the code below assumes that GrandPa is the only finality algorithm currently
// supported.
debug_assert!(
self.blocks_trigger_gp_change.is_empty()
|| !matches!(self.finality, Finality::Outsourced)
);

either::Right(iter)
}
}
self.blocks_trigger_gp_change
.range((
ops::Bound::Excluded((
Some(self.finalized_block_number),
fork_tree::NodeIndex::max_value(),
)),
ops::Bound::Unbounded,
))
.map(|(_prev_auth_change_trigger_number, block_index)| {
debug_assert!(_prev_auth_change_trigger_number
.map_or(false, |n| n > self.finalized_block_number));
let block = self
.blocks
.get(*block_index)
.unwrap_or_else(|| unreachable!());
(block.number, &block.hash)
})
}

/// Verifies the given justification.
Expand Down Expand Up @@ -252,12 +244,7 @@ impl<T> NonFinalizedTree<T> {
finalized_scheduled_change,
finalized_triggered_authorities,
} => {
let finalized_block_number =
header::decode(&self.finalized_block_header, self.block_number_bytes)
.unwrap()
.number;

match target_number.cmp(&finalized_block_number) {
match target_number.cmp(&self.finalized_block_number) {
cmp::Ordering::Equal if *target_hash == self.finalized_block_hash => {
return Err(FinalityVerifyError::EqualToFinalized)
}
Expand Down Expand Up @@ -288,7 +275,7 @@ impl<T> NonFinalizedTree<T> {
} = self.blocks.get(block_index).unwrap().finality
{
if let Some(prev_auth_change_trigger_number) = prev_auth_change_trigger_number {
if *prev_auth_change_trigger_number > finalized_block_number {
if *prev_auth_change_trigger_number > self.finalized_block_number {
return Err(FinalityVerifyError::TooFarAhead {
justification_block_number: target_number,
justification_block_hash: *target_hash,
Expand Down Expand Up @@ -354,11 +341,7 @@ impl<T> NonFinalizedTree<T> {
);
debug_assert!(scheduled_change
.as_ref()
.map(|(n, _)| *n
> header::decode(&new_finalized_block.header, self.block_number_bytes)
.unwrap()
.number)
.unwrap_or(true));
.map_or(true, |(n, _)| *n > new_finalized_block.number));

*after_finalized_block_authorities_set_id = *after_block_authorities_set_id;
*finalized_triggered_authorities = triggered_authorities.clone();
Expand Down Expand Up @@ -422,22 +405,24 @@ impl<T> NonFinalizedTree<T> {
_ => unreachable!(),
}

// Update `self.finalized_block_header`, `self.finalized_block_hash`, and
// `self.finalized_best_score`.
// Update `self.finalized_block_header`, `self.finalized_block_hash`,
// `self.finalized_block_number`, and `self.finalized_best_score`.
mem::swap(
&mut self.finalized_block_header,
&mut new_finalized_block.header,
);
self.finalized_block_hash =
header::hash_from_scale_encoded_header(&self.finalized_block_header);
self.finalized_block_hash = new_finalized_block.hash;
self.finalized_block_number = new_finalized_block.number;
self.finalized_best_score = new_finalized_block.best_score;

debug_assert_eq!(self.blocks.len(), self.blocks_by_hash.len());
debug_assert_eq!(self.blocks.len(), self.blocks_by_best_score.len());
debug_assert!(self.blocks.len() >= self.blocks_trigger_gp_change.len());
SetFinalizedBlockIter {
iter: self.blocks.prune_ancestors(block_index_to_finalize),
blocks_by_hash: &mut self.blocks_by_hash,
blocks_by_best_score: &mut self.blocks_by_best_score,
blocks_trigger_gp_change: &mut self.blocks_trigger_gp_change,
updates_best_block,
}
}
Expand Down Expand Up @@ -575,6 +560,7 @@ pub struct SetFinalizedBlockIter<'a, T> {
iter: fork_tree::PruneAncestorsIter<'a, Block<T>>,
blocks_by_hash: &'a mut HashMap<[u8; 32], fork_tree::NodeIndex, fnv::FnvBuildHasher>,
blocks_by_best_score: &'a mut BTreeMap<BestScore, fork_tree::NodeIndex>,
blocks_trigger_gp_change: &'a mut BTreeSet<(Option<u64>, fork_tree::NodeIndex)>,
updates_best_block: bool,
}

Expand All @@ -597,6 +583,17 @@ 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 let BlockFinality::Grandpa {
prev_auth_change_trigger_number,
triggers_change: true,
..
} = pruned.user_data.finality
{
let _removed = self
.blocks_trigger_gp_change
.remove(&(prev_auth_change_trigger_number, pruned.index));
debug_assert!(_removed);
}
break Some(RemovedBlock {
block_hash: pruned.user_data.hash,
scale_encoded_header: pruned.user_data.header,
Expand Down
19 changes: 19 additions & 0 deletions lib/src/chain/blocks_tree/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ impl<T> NonFinalizedTree<T> {

Ok(HeaderVerifySuccess::Verified {
verified_header: VerifiedHeader {
number: decoded_header.number,
scale_encoded_header,
consensus_update,
finality_update,
Expand Down Expand Up @@ -498,11 +499,23 @@ impl<T> NonFinalizedTree<T> {
insertion_counter: self.blocks_insertion_counter,
};

let prev_auth_change_trigger_number_if_trigger = if let BlockFinality::Grandpa {
prev_auth_change_trigger_number,
triggers_change: true,
..
} = verified_header.finality_update
{
Some(prev_auth_change_trigger_number)
} else {
None
};

let new_node_index = self.blocks.insert(
parent_tree_index,
Block {
header: verified_header.scale_encoded_header,
hash: verified_header.hash,
number: verified_header.number,
consensus: verified_header.consensus_update,
finality: verified_header.finality_update,
best_score,
Expand All @@ -518,6 +531,11 @@ impl<T> NonFinalizedTree<T> {

self.blocks_by_best_score.insert(best_score, new_node_index);

if let Some(prev_auth_change_trigger_number) = prev_auth_change_trigger_number_if_trigger {
self.blocks_trigger_gp_change
.insert((prev_auth_change_trigger_number, new_node_index));
}

// An overflow here would break the logic of the module. It is better to panic than to
// continue running.
self.blocks_insertion_counter = self.blocks_insertion_counter.checked_add(1).unwrap();
Expand All @@ -532,6 +550,7 @@ pub struct VerifiedHeader {
best_score_num_primary_slots: u64,
best_score_num_secondary_slots: u64,
hash: [u8; 32],
number: u64,
}

impl VerifiedHeader {
Expand Down
12 changes: 12 additions & 0 deletions lib/src/chain/fork_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,18 @@ pub struct PrunedNode<T> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeIndex(usize);

impl NodeIndex {
/// Returns the value that compares inferior or equal to any possible [`NodeIndex`̀].
pub fn min_value() -> Self {
NodeIndex(usize::min_value())
}

/// Returns the value that compares superior or equal to any possible [`NodeIndex`̀].
pub fn max_value() -> Self {
NodeIndex(usize::max_value())
}
}

#[cfg(test)]
mod tests {
use super::ForkTree;
Expand Down
4 changes: 4 additions & 0 deletions wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixed

- Justifications are no longer downloaded for blocks that can't be finalized because an earlier block needs to be finalized first. ([#1127](https://github.com/smol-dot/smoldot/pull/1127))

## 2.0.1 - 2023-09-08

### Fixed
Expand Down
Loading