Skip to content

Commit

Permalink
perf: introduce BlockValidationKind (paradigmxyz#5195)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse authored Oct 27, 2023
1 parent fc4fc93 commit fbdf744
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 63 deletions.
68 changes: 44 additions & 24 deletions crates/blockchain-tree/src/blockchain_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use reth_db::database::Database;
use reth_interfaces::{
blockchain_tree::{
error::{BlockchainTreeError, CanonicalError, InsertBlockError, InsertBlockErrorKind},
BlockStatus, CanonicalOutcome, InsertPayloadOk,
BlockStatus, BlockValidationKind, CanonicalOutcome, InsertPayloadOk,
},
consensus::{Consensus, ConsensusError},
executor::{BlockExecutionError, BlockValidationError},
Expand Down Expand Up @@ -286,12 +286,13 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {

/// Try inserting a validated [Self::validate_block] block inside the tree.
///
/// If blocks does not have parent [`BlockStatus::Disconnected`] would be returned, in which
/// case it is buffered for future inclusion.
/// If the block's parent block is unknown, this returns [`BlockStatus::Disconnected`] and the
/// block will be buffered until the parent block is inserted and then attached.
#[instrument(level = "trace", skip_all, fields(block = ?block.num_hash()), target = "blockchain_tree", ret)]
fn try_insert_validated_block(
&mut self,
block: SealedBlockWithSenders,
block_validation_kind: BlockValidationKind,
) -> Result<BlockStatus, InsertBlockError> {
debug_assert!(self.validate_block(&block).is_ok(), "Block must be validated");

Expand All @@ -300,15 +301,15 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
// check if block parent can be found in Tree
if let Some(chain_id) = self.block_indices().get_blocks_chain_id(&parent.hash) {
// found parent in side tree, try to insert there
return self.try_insert_block_into_side_chain(block, chain_id)
return self.try_insert_block_into_side_chain(block, chain_id, block_validation_kind)
}

// if not found, check if the parent can be found inside canonical chain.
if self
.is_block_hash_canonical(&parent.hash)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?
{
return self.try_append_canonical_chain(block)
return self.try_append_canonical_chain(block, block_validation_kind)
}

// this is another check to ensure that if the block points to a canonical block its block
Expand Down Expand Up @@ -362,6 +363,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
fn try_append_canonical_chain(
&mut self,
block: SealedBlockWithSenders,
block_validation_kind: BlockValidationKind,
) -> Result<BlockStatus, InsertBlockError> {
let parent = block.parent_num_hash();
let block_num_hash = block.num_hash();
Expand Down Expand Up @@ -417,8 +419,15 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
canonical_chain.inner(),
parent,
&self.externals,
block_validation_kind,
)?;
(BlockStatus::Valid, chain)
let status = if block_validation_kind.is_exhaustive() {
BlockStatus::Valid
} else {
BlockStatus::Accepted
};

(status, chain)
} else {
let chain = AppendableChain::new_canonical_fork(
block,
Expand All @@ -444,6 +453,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
&mut self,
block: SealedBlockWithSenders,
chain_id: BlockChainId,
block_validation_kind: BlockValidationKind,
) -> Result<BlockStatus, InsertBlockError> {
debug!(target: "blockchain_tree", "Inserting block into side chain");
let block_num_hash = block.num_hash();
Expand Down Expand Up @@ -495,11 +505,12 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
&self.externals,
canonical_fork,
block_kind,
block_validation_kind,
)?;

self.block_indices_mut().insert_non_fork_block(block_number, block_hash, chain_id);

if block_kind.extends_canonical_head() {
if block_kind.extends_canonical_head() && block_validation_kind.is_exhaustive() {
// if the block can be traced back to the canonical head, we were able to fully
// validate it
Ok(BlockStatus::Valid)
Expand Down Expand Up @@ -602,7 +613,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
block: SealedBlock,
) -> Result<InsertPayloadOk, InsertBlockError> {
match block.try_seal_with_senders() {
Ok(block) => self.insert_block(block),
Ok(block) => self.insert_block(block, BlockValidationKind::Exhaustive),
Err(block) => Err(InsertBlockError::sender_recovery_error(block)),
}
}
Expand Down Expand Up @@ -681,13 +692,17 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
/// This means that if the block becomes canonical, we need to fetch the missing blocks over
/// P2P.
///
/// If the [BlockValidationKind::SkipStateRootValidation] is provided the state root is not
/// validated.
///
/// # Note
///
/// If the senders have not already been recovered, call
/// [`BlockchainTree::insert_block_without_senders`] instead.
pub fn insert_block(
&mut self,
block: SealedBlockWithSenders,
block_validation_kind: BlockValidationKind,
) -> Result<InsertPayloadOk, InsertBlockError> {
// check if we already have this block
match self.is_block_known(block.num_hash()) {
Expand All @@ -701,7 +716,9 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
return Err(InsertBlockError::consensus_error(err, block.block))
}

Ok(InsertPayloadOk::Inserted(self.try_insert_validated_block(block)?))
Ok(InsertPayloadOk::Inserted(
self.try_insert_validated_block(block, block_validation_kind)?,
))
}

/// Finalize blocks up until and including `finalized_block`, and remove them from the tree.
Expand Down Expand Up @@ -803,17 +820,20 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
fn try_connect_buffered_blocks(&mut self, new_block: BlockNumHash) {
trace!(target: "blockchain_tree", ?new_block, "try_connect_buffered_blocks");

// first remove all the children of the new block from the buffer
let include_blocks = self.state.buffered_blocks.remove_with_children(new_block);
// insert block children
// then try to reinsert them into the tree
for block in include_blocks.into_iter() {
// dont fail on error, just ignore the block.
let _ = self.try_insert_validated_block(block).map_err(|err| {
debug!(
target: "blockchain_tree", ?err,
"Failed to insert buffered block",
);
err
});
let _ = self
.try_insert_validated_block(block, BlockValidationKind::SkipStateRootValidation)
.map_err(|err| {
debug!(
target: "blockchain_tree", ?err,
"Failed to insert buffered block",
);
err
});
}
}

Expand Down Expand Up @@ -1314,7 +1334,7 @@ mod tests {

// block 2 parent is not known, block2 is buffered.
assert_eq!(
tree.insert_block(block2.clone()).unwrap(),
tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::Inserted(BlockStatus::Disconnected {
missing_ancestor: block2.parent_num_hash()
})
Expand Down Expand Up @@ -1346,7 +1366,7 @@ mod tests {

// insert block1 and buffered block2 is inserted
assert_eq!(
tree.insert_block(block1.clone()).unwrap(),
tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::Inserted(BlockStatus::Valid)
);

Expand All @@ -1369,13 +1389,13 @@ mod tests {

// already inserted block will `InsertPayloadOk::AlreadySeen(_)`
assert_eq!(
tree.insert_block(block1.clone()).unwrap(),
tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::AlreadySeen(BlockStatus::Valid)
);

// block two is already inserted.
assert_eq!(
tree.insert_block(block2.clone()).unwrap(),
tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::AlreadySeen(BlockStatus::Valid)
);

Expand Down Expand Up @@ -1415,7 +1435,7 @@ mod tests {

// reinsert two blocks that point to canonical chain
assert_eq!(
tree.insert_block(block1a.clone()).unwrap(),
tree.insert_block(block1a.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::Inserted(BlockStatus::Accepted)
);

Expand All @@ -1430,7 +1450,7 @@ mod tests {
.assert(&tree);

assert_eq!(
tree.insert_block(block2a.clone()).unwrap(),
tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::Inserted(BlockStatus::Accepted)
);
// Trie state:
Expand Down Expand Up @@ -1620,7 +1640,7 @@ mod tests {
block2b.parent_hash = B256::new([0x88; 32]);

assert_eq!(
tree.insert_block(block2b.clone()).unwrap(),
tree.insert_block(block2b.clone(), BlockValidationKind::Exhaustive).unwrap(),
InsertPayloadOk::Inserted(BlockStatus::Disconnected {
missing_ancestor: block2b.parent_num_hash()
})
Expand Down
56 changes: 27 additions & 29 deletions crates/blockchain-tree/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use super::externals::TreeExternals;
use crate::BundleStateDataRef;
use reth_db::database::Database;
use reth_interfaces::{
blockchain_tree::error::{BlockchainTreeError, InsertBlockError},
blockchain_tree::{
error::{BlockchainTreeError, InsertBlockError},
BlockValidationKind,
},
consensus::{Consensus, ConsensusError},
RethResult,
};
Expand Down Expand Up @@ -54,15 +57,17 @@ impl AppendableChain {
self.chain
}

/// Create a new chain that forks off the canonical.
/// Create a new chain that forks off the canonical chain.
///
/// This will also verify the state root of the block extending the canonical chain.
/// if [BlockValidationKind::Exhaustive] is provides this will verify the state root of the
/// block extending the canonical chain.
pub fn new_canonical_head_fork<DB, EF>(
block: SealedBlockWithSenders,
parent_header: &SealedHeader,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, EF>,
block_validation_kind: BlockValidationKind,
) -> Result<Self, InsertBlockError>
where
DB: Database,
Expand All @@ -78,11 +83,13 @@ impl AppendableChain {
canonical_fork,
};

let bundle_state = Self::validate_and_execute_canonical_head_descendant(
let bundle_state = Self::validate_and_execute(
block.clone(),
parent_header,
state_provider,
externals,
BlockKind::ExtendsCanonicalHead,
block_validation_kind,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;

Expand Down Expand Up @@ -170,13 +177,21 @@ impl AppendableChain {
}

/// Validate and execute the given block that _extends the canonical chain_, validating its
/// state root after execution.
/// state root after execution if possible and requested.
///
/// Note: State root validation is limited to blocks that extend the canonical chain and is
/// optional, see [BlockValidationKind]. So this function takes two parameters to determine
/// if the state can and should be validated.
/// - [BlockKind] represents if the block extends the canonical chain, and thus if the state
/// root __can__ be validated.
/// - [BlockValidationKind] determines if the state root __should__ be validated.
fn validate_and_execute<BSDP, DB, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
post_state_data_provider: BSDP,
externals: &TreeExternals<DB, EF>,
block_kind: BlockKind,
block_validation_kind: BlockValidationKind,
) -> RethResult<BundleStateWithReceipts>
where
BSDP: BundleStateDataProvider,
Expand All @@ -200,8 +215,9 @@ impl AppendableChain {
executor.execute_and_verify_receipt(&block, U256::MAX, Some(senders))?;
let bundle_state = executor.take_output_state();

// check state root if the block extends the canonical chain.
if block_kind.extends_canonical_head() {
// check state root if the block extends the canonical chain __and__ if state root
// validation was requested.
if block_kind.extends_canonical_head() && block_validation_kind.is_exhaustive() {
// check state root
let state_root = provider.state_root(&bundle_state)?;
if block.state_root != state_root {
Expand All @@ -216,28 +232,6 @@ impl AppendableChain {
Ok(bundle_state)
}

/// Validate and execute the given block that _extends the canonical chain_, validating its
/// state root after execution.
fn validate_and_execute_canonical_head_descendant<BSDP, DB, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
post_state_data_provider: BSDP,
externals: &TreeExternals<DB, EF>,
) -> RethResult<BundleStateWithReceipts>
where
BSDP: BundleStateDataProvider,
DB: Database,
EF: ExecutorFactory,
{
Self::validate_and_execute(
block,
parent_block,
post_state_data_provider,
externals,
BlockKind::ExtendsCanonicalHead,
)
}

/// Validate and execute the given sidechain block, skipping state root validation.
fn validate_and_execute_sidechain<BSDP, DB, EF>(
block: SealedBlockWithSenders,
Expand All @@ -256,6 +250,7 @@ impl AppendableChain {
post_state_data_provider,
externals,
BlockKind::ForksHistoricalBlock,
BlockValidationKind::SkipStateRootValidation,
)
}

Expand All @@ -271,6 +266,7 @@ impl AppendableChain {
/// is the canonical head, or: state root check can't be performed if the given canonical is
/// __not__ the canonical head.
#[track_caller]
#[allow(clippy::too_many_arguments)]
pub(crate) fn append_block<DB, EF>(
&mut self,
block: SealedBlockWithSenders,
Expand All @@ -279,6 +275,7 @@ impl AppendableChain {
externals: &TreeExternals<DB, EF>,
canonical_fork: ForkBlock,
block_kind: BlockKind,
block_validation_kind: BlockValidationKind,
) -> Result<(), InsertBlockError>
where
DB: Database,
Expand All @@ -299,6 +296,7 @@ impl AppendableChain {
post_state_data,
externals,
block_kind,
block_validation_kind,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
// extend the state.
Expand Down
4 changes: 3 additions & 1 deletion crates/blockchain-tree/src/noop.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use reth_interfaces::{
blockchain_tree::{
error::{BlockchainTreeError, InsertBlockError},
BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome, InsertPayloadOk,
BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome,
InsertPayloadOk,
},
RethResult,
};
Expand Down Expand Up @@ -30,6 +31,7 @@ impl BlockchainTreeEngine for NoopBlockchainTree {
fn insert_block(
&self,
block: SealedBlockWithSenders,
_validation_kind: BlockValidationKind,
) -> Result<InsertPayloadOk, InsertBlockError> {
Err(InsertBlockError::tree_error(
BlockchainTreeError::BlockHashNotFoundInChain { block_hash: block.hash },
Expand Down
Loading

0 comments on commit fbdf744

Please sign in to comment.