Skip to content

Commit

Permalink
feat(trie): historical & sidechain state root (#6131)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
  • Loading branch information
rkrasiuk and mattsse authored Jan 25, 2024
1 parent e78ae77 commit 2de10a1
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 334 deletions.
265 changes: 103 additions & 162 deletions crates/blockchain-tree/src/blockchain_tree.rs

Large diffs are not rendered by default.

145 changes: 35 additions & 110 deletions crates/blockchain-tree/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::BundleStateDataRef;
use reth_db::database::Database;
use reth_interfaces::{
blockchain_tree::{
error::{BlockchainTreeError, InsertBlockError},
BlockValidationKind,
error::{BlockchainTreeError, InsertBlockErrorKind},
BlockAttachment, BlockValidationKind,
},
consensus::{Consensus, ConsensusError},
RethResult,
Expand Down Expand Up @@ -59,18 +59,19 @@ impl AppendableChain {
self.chain
}

/// Create a new chain that forks off the canonical chain.
/// Create a new chain that forks off of 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>(
/// if [BlockValidationKind::Exhaustive] is specified, the method will verify the state root of
/// the block.
pub fn new_canonical_fork<DB, EF>(
block: SealedBlockWithSenders,
parent_header: &SealedHeader,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, EF>,
block_attachment: BlockAttachment,
block_validation_kind: BlockValidationKind,
) -> Result<Self, InsertBlockError>
) -> Result<Self, InsertBlockErrorKind>
where
DB: Database,
EF: ExecutorFactory,
Expand All @@ -90,47 +91,13 @@ impl AppendableChain {
parent_header,
state_provider,
externals,
BlockKind::ExtendsCanonicalHead,
block_attachment,
block_validation_kind,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
)?;

Ok(Self { chain: Chain::new(vec![block], bundle_state, trie_updates) })
}

/// Create a new chain that forks off of the canonical chain.
pub fn new_canonical_fork<DB, EF>(
block: SealedBlockWithSenders,
parent_header: &SealedHeader,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, EF>,
) -> Result<Self, InsertBlockError>
where
DB: Database,
EF: ExecutorFactory,
{
let state = BundleStateWithReceipts::default();
let empty = BTreeMap::new();

let state_provider = BundleStateDataRef {
state: &state,
sidechain_block_hashes: &empty,
canonical_block_hashes,
canonical_fork,
};

let bundle_state = Self::validate_and_execute_sidechain(
block.clone(),
parent_header,
state_provider,
externals,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;

Ok(Self { chain: Chain::new(vec![block], bundle_state, None) })
}

/// Create a new chain that forks off of an existing sidechain.
///
/// This differs from [AppendableChain::new_canonical_fork] in that this starts a new fork.
Expand All @@ -141,18 +108,16 @@ impl AppendableChain {
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, EF>,
) -> Result<Self, InsertBlockError>
block_validation_kind: BlockValidationKind,
) -> Result<Self, InsertBlockErrorKind>
where
DB: Database,
EF: ExecutorFactory,
{
let parent_number = block.number - 1;
let parent = self.blocks().get(&parent_number).ok_or_else(|| {
InsertBlockError::tree_error(
BlockchainTreeError::BlockNumberNotFoundInChain { block_number: parent_number },
block.block.clone(),
)
})?;
let parent = self.blocks().get(&parent_number).ok_or(
BlockchainTreeError::BlockNumberNotFoundInChain { block_number: parent_number },
)?;

let mut state = self.state().clone();

Expand All @@ -166,13 +131,14 @@ impl AppendableChain {
canonical_block_hashes,
canonical_fork,
};
let block_state = Self::validate_and_execute_sidechain(
let (block_state, _) = Self::validate_and_execute(
block.clone(),
parent,
bundle_state_data,
externals,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
BlockAttachment::HistoricalFork,
block_validation_kind,
)?;
// extending will also optimize few things, mostly related to selfdestruct and wiping of
// storage.
state.extend(block_state);
Expand All @@ -193,15 +159,15 @@ impl AppendableChain {
/// 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.
/// - [BlockAttachment] represents if the block extends the canonical chain, and thus we can
/// cache the trie state updates.
/// - [BlockValidationKind] determines if the state root __should__ be validated.
fn validate_and_execute<BSDP, DB, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
bundle_state_data_provider: BSDP,
externals: &TreeExternals<DB, EF>,
block_kind: BlockKind,
block_attachment: BlockAttachment,
block_validation_kind: BlockValidationKind,
) -> RethResult<(BundleStateWithReceipts, Option<TrieUpdates>)>
where
Expand All @@ -226,45 +192,28 @@ impl AppendableChain {

// 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() {
if block_validation_kind.is_exhaustive() {
// check state root
let (state_root, trie_updates) = provider.state_root_with_updates(&bundle_state)?;
let (state_root, trie_updates) = if block_attachment.is_canonical() {
provider
.state_root_with_updates(&bundle_state)
.map(|(root, updates)| (root, Some(updates)))?
} else {
(provider.state_root(&bundle_state)?, None)
};
if block.state_root != state_root {
return Err(ConsensusError::BodyStateRootDiff(
GotExpected { got: state_root, expected: block.state_root }.into(),
)
.into())
}

Ok((bundle_state, Some(trie_updates)))
Ok((bundle_state, trie_updates))
} else {
Ok((bundle_state, None))
}
}

/// Validate and execute the given sidechain block, skipping state root validation.
fn validate_and_execute_sidechain<BSDP, DB, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
bundle_state_data_provider: BSDP,
externals: &TreeExternals<DB, EF>,
) -> RethResult<BundleStateWithReceipts>
where
BSDP: BundleStateDataProvider,
DB: Database,
EF: ExecutorFactory,
{
let (state, _) = Self::validate_and_execute(
block,
parent_block,
bundle_state_data_provider,
externals,
BlockKind::ForksHistoricalBlock,
BlockValidationKind::SkipStateRootValidation,
)?;
Ok(state)
}

/// Validate and execute the given block, and append it to this chain.
///
/// This expects that the block's ancestors can be traced back to the `canonical_fork` (the
Expand All @@ -285,9 +234,9 @@ impl AppendableChain {
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
externals: &TreeExternals<DB, EF>,
canonical_fork: ForkBlock,
block_kind: BlockKind,
block_attachment: BlockAttachment,
block_validation_kind: BlockValidationKind,
) -> Result<(), InsertBlockError>
) -> Result<(), InsertBlockErrorKind>
where
DB: Database,
EF: ExecutorFactory,
Expand All @@ -306,36 +255,12 @@ impl AppendableChain {
parent_block,
bundle_state_data,
externals,
block_kind,
block_attachment,
block_validation_kind,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
)?;
// extend the state.
self.chain.append_block(block, block_state, trie_updates);

Ok(())
}
}

/// Represents what kind of block is being executed and validated.
///
/// This is required because the state root check can only be performed if the targeted block can be
/// traced back to the canonical __head__.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BlockKind {
/// The `block` is a descendant of the canonical head:
///
/// [`head..(block.parent)*,block`]
ExtendsCanonicalHead,
/// The block can be traced back to an ancestor of the canonical head: a historical block, but
/// this chain does __not__ include the canonical head.
ForksHistoricalBlock,
}

impl BlockKind {
/// Returns `true` if the block is a descendant of the canonical head.
#[inline]
pub(crate) fn extends_canonical_head(&self) -> bool {
matches!(self, BlockKind::ExtendsCanonicalHead)
}
}
3 changes: 1 addition & 2 deletions crates/blockchain-tree/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ impl BlockchainTreeConfig {
/// It is calculated as the maximum of `max_reorg_depth` (which is the number of blocks required
/// for the deepest reorg possible according to the consensus protocol) and
/// `num_of_additional_canonical_block_hashes` (which is the number of block hashes needed to
/// satisfy the `BLOCKHASH` opcode in the EVM. See [`crate::BundleStateDataRef`] and
/// [`crate::AppendableChain::new_canonical_head_fork`] where it's used).
/// satisfy the `BLOCKHASH` opcode in the EVM. See [`crate::BundleStateDataRef`]).
pub fn num_of_canonical_hashes(&self) -> u64 {
self.max_reorg_depth.max(self.num_of_additional_canonical_block_hashes)
}
Expand Down
48 changes: 20 additions & 28 deletions crates/consensus/beacon/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,14 +1263,19 @@ where
let mut latest_valid_hash = None;
let block = Arc::new(block);
let status = match status {
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
InsertPayloadOk::Inserted(BlockStatus::Valid(attachment)) => {
latest_valid_hash = Some(block_hash);
self.listeners.notify(BeaconConsensusEngineEvent::CanonicalBlockAdded(block));
let event = if attachment.is_canonical() {
BeaconConsensusEngineEvent::CanonicalBlockAdded(block)
} else {
BeaconConsensusEngineEvent::ForkBlockAdded(block)
};
self.listeners.notify(event);
PayloadStatusEnum::Valid
}
InsertPayloadOk::Inserted(BlockStatus::Accepted) => {
self.listeners.notify(BeaconConsensusEngineEvent::ForkBlockAdded(block));
PayloadStatusEnum::Accepted
InsertPayloadOk::AlreadySeen(BlockStatus::Valid(_)) => {
latest_valid_hash = Some(block_hash);
PayloadStatusEnum::Valid
}
InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) |
InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
Expand All @@ -1284,11 +1289,6 @@ where
// not known to be invalid, but we don't know anything else
PayloadStatusEnum::Syncing
}
InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
latest_valid_hash = Some(block_hash);
PayloadStatusEnum::Valid
}
InsertPayloadOk::AlreadySeen(BlockStatus::Accepted) => PayloadStatusEnum::Accepted,
};
Ok(PayloadStatus::new(status, latest_valid_hash))
}
Expand Down Expand Up @@ -1351,20 +1351,14 @@ where
///
/// ## [BlockStatus::Valid]
///
/// The block is connected to the current canonical head and is valid.
/// If the engine is still SYNCING, then we can try again to make the chain canonical.
///
/// ## [BlockStatus::Accepted]
///
/// All ancestors are known, but the block is not connected to the current canonical _head_. If
/// the block is an ancestor of the current forkchoice head, then we can try again to make the
/// chain canonical, which would trigger a reorg in this case since the new head is therefore
/// not connected to the current head.
/// The block is connected to the current canonical chain and is valid.
/// If the block is an ancestor of the current forkchoice head, then we can try again to make
/// the chain canonical.
///
/// ## [BlockStatus::Disconnected]
///
/// The block is not connected to the canonical head, and we need to download the missing parent
/// first.
/// The block is not connected to the canonical chain, and we need to download the missing
/// parent first.
///
/// ## Insert Error
///
Expand All @@ -1386,12 +1380,10 @@ where
{
Ok(status) => {
match status {
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
// block is connected to the current canonical head and is valid.
self.try_make_sync_target_canonical(downloaded_num_hash);
}
InsertPayloadOk::Inserted(BlockStatus::Accepted) => {
// block is connected to the canonical chain, but not the current head
InsertPayloadOk::Inserted(BlockStatus::Valid(_)) => {
// block is connected to the canonical chain and is valid.
// if it's not connected to current canonical head, the state root
// has not been validated.
self.try_make_sync_target_canonical(downloaded_num_hash);
}
InsertPayloadOk::Inserted(BlockStatus::Disconnected {
Expand Down Expand Up @@ -1469,7 +1461,7 @@ where
/// Attempt to form a new canonical chain based on the current sync target.
///
/// This is invoked when we successfully __downloaded__ a new block from the network which
/// resulted in either [BlockStatus::Accepted] or [BlockStatus::Valid].
/// resulted in [BlockStatus::Valid].
///
/// Note: This will not succeed if the sync target has changed since the block download request
/// was issued and the new target is still disconnected and additional missing blocks are
Expand Down
Loading

0 comments on commit 2de10a1

Please sign in to comment.