feat: add state root validation on new insert (#2543)

Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Matthias Seitz
2023-05-19 14:04:10 +02:00
committed by GitHub
parent fba6a03f64
commit 13a7209069
2 changed files with 143 additions and 33 deletions

View File

@ -158,11 +158,10 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
// check db if block is finalized.
if block.number <= last_finalized_block {
// check if block is canonical
if let Some(hash) = self.canonical_chain().canonical_hash(&block.number) {
if hash == block.hash {
return Ok(Some(BlockStatus::Valid))
}
if self.is_block_hash_canonical(&block.hash)? {
return Ok(Some(BlockStatus::Valid))
}
// check if block is inside database
if self.externals.database().block_number(block.hash)?.is_some() {
return Ok(Some(BlockStatus::Valid))
@ -315,11 +314,16 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
.map_err(|err| InsertBlockError::consensus_error(err, block.block.clone()))?;
}
// insert block inside unconnected block buffer. Delaying it execution.
// insert block inside unconnected block buffer. Delaying its execution.
self.buffered_blocks.insert_block(block);
Ok(BlockStatus::Disconnected)
}
/// This tries to append the given block to the canonical chain.
///
/// WARNING: this expects that the block is part of the canonical chain, see
/// [Self::is_block_hash_canonical]. Hence, it is expected that the block can be traced back
/// to the current canonical block.
#[instrument(skip_all, target = "blockchain_tree")]
fn try_append_canonical_chain(
&mut self,
@ -354,13 +358,6 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
))
}
let canonical_chain = self.canonical_chain();
let block_status = if block.parent_hash == canonical_chain.tip().hash {
BlockStatus::Valid
} else {
BlockStatus::Accepted
};
let parent_header = db
.header(&block.parent_hash)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?
@ -372,19 +369,36 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
})?
.seal(block.parent_hash);
let chain = AppendableChain::new_canonical_fork(
block,
&parent_header,
canonical_chain.inner(),
parent,
&self.externals,
)?;
let canonical_chain = self.canonical_chain();
let (block_status, chain) = if block.parent_hash == canonical_chain.tip().hash {
let chain = AppendableChain::new_canonical_fork_extend(
block,
&parent_header,
canonical_chain.inner(),
parent,
&self.externals,
)?;
(BlockStatus::Valid, chain)
} else {
let chain = AppendableChain::new_canonical_fork(
block,
&parent_header,
canonical_chain.inner(),
parent,
&self.externals,
)?;
(BlockStatus::Accepted, chain)
};
self.insert_chain(chain);
self.try_connect_buffered_blocks(block_num_hash);
Ok(block_status)
}
/// Try inserting a block into the given side chain.
///
/// WARNING: This expects a valid side chain id, see [BlockIndices::get_blocks_chain_id]
#[instrument(skip_all, target = "blockchain_tree")]
fn try_insert_block_into_side_chain(
&mut self,
@ -979,7 +993,7 @@ mod tests {
transaction::DbTxMut,
};
use reth_interfaces::test_utils::TestConsensus;
use reth_primitives::{proofs::EMPTY_ROOT, ChainSpecBuilder, H256, MAINNET};
use reth_primitives::{proofs::EMPTY_ROOT, ChainSpecBuilder, StageCheckpoint, H256, MAINNET};
use reth_provider::{
insert_block,
post_state::PostState,
@ -1018,6 +1032,7 @@ mod tests {
for i in 0..10 {
tx_mut.put::<tables::CanonicalHeaders>(i, H256([100 + i as u8; 32])).unwrap();
}
tx_mut.put::<tables::SyncStage>("Finish".to_string(), StageCheckpoint::new(10)).unwrap();
tx_mut.commit().unwrap();
}

View File

@ -6,7 +6,7 @@ use crate::{post_state::PostState, PostStateDataRef};
use reth_db::database::Database;
use reth_interfaces::{
blockchain_tree::error::{BlockchainTreeError, InsertBlockError},
consensus::Consensus,
consensus::{Consensus, ConsensusError},
Error,
};
use reth_primitives::{
@ -14,6 +14,7 @@ use reth_primitives::{
};
use reth_provider::{
providers::PostStateProvider, BlockExecutor, Chain, ExecutorFactory, PostStateDataProvider,
StateRootProvider,
};
use std::{
collections::BTreeMap,
@ -57,6 +58,42 @@ impl AppendableChain {
self.chain
}
/// Create a new chain that _extends the canonical chain_.
///
/// This will also verify the state root of the block extending the canonical chain.
pub fn new_canonical_fork_extend<DB, C, EF>(
block: SealedBlockWithSenders,
parent_header: &SealedHeader,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, C, EF>,
) -> Result<Self, InsertBlockError>
where
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
let state = PostState::default();
let empty = BTreeMap::new();
let state_provider = PostStateDataRef {
state: &state,
sidechain_block_hashes: &empty,
canonical_block_hashes,
canonical_fork,
};
let changeset = Self::validate_and_execute_canonical(
block.clone(),
parent_header,
state_provider,
externals,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
Ok(Self { chain: Chain::new(vec![(block, changeset)]) })
}
/// Create a new chain that forks off of the canonical chain.
pub fn new_canonical_fork<DB, C, EF>(
block: SealedBlockWithSenders,
@ -80,14 +117,20 @@ impl AppendableChain {
canonical_fork,
};
let changeset =
Self::validate_and_execute(block.clone(), parent_header, state_provider, externals)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
let changeset = 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, changeset)]) })
}
/// 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.
pub(crate) fn new_chain_fork<DB, C, EF>(
&self,
block: SealedBlockWithSenders,
@ -122,7 +165,7 @@ impl AppendableChain {
canonical_fork,
};
let block_state =
Self::validate_and_execute(block.clone(), parent, post_state_data, externals)
Self::validate_and_execute_sidechain(block.clone(), parent, post_state_data, externals)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
state.extend(block_state);
@ -133,8 +176,9 @@ impl AppendableChain {
Ok(chain)
}
/// Validate and execute the given block.
fn validate_and_execute<PSDP, DB, C, EF>(
/// Validate and execute the given block that _extends the canonical chain_, validating its
/// state root after execution.
fn validate_and_execute_canonical<PSDP, DB, C, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
post_state_data_provider: PSDP,
@ -149,8 +193,8 @@ impl AppendableChain {
// some checks are done before blocks comes here.
externals.consensus.validate_header_against_parent(&block, parent_block)?;
let (unseal, senders) = block.into_components();
let unseal = unseal.unseal();
let (block, senders) = block.into_components();
let block = block.unseal();
//get state provider.
let db = externals.database();
@ -162,10 +206,57 @@ impl AppendableChain {
let provider = PostStateProvider::new(state_provider, post_state_data_provider);
let mut executor = externals.executor_factory.with_sp(&provider);
executor.execute_and_verify_receipt(&unseal, U256::MAX, Some(senders)).map_err(Into::into)
let post_state = executor.execute_and_verify_receipt(&block, U256::MAX, Some(senders))?;
// check state root
let state_root = provider.state_root(post_state.clone())?;
if block.state_root != state_root {
return Err(ConsensusError::BodyStateRootDiff {
got: state_root,
expected: block.state_root,
}
.into())
}
Ok(post_state)
}
/// Validate and execute the given sidechain block, skipping state root validation.
fn validate_and_execute_sidechain<PSDP, DB, C, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
post_state_data_provider: PSDP,
externals: &TreeExternals<DB, C, EF>,
) -> Result<PostState, Error>
where
PSDP: PostStateDataProvider,
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
// some checks are done before blocks comes here.
externals.consensus.validate_header_against_parent(&block, parent_block)?;
let (block, senders) = block.into_components();
let block = block.unseal();
//get state provider.
let db = externals.database();
// TODO, small perf can check if caonical fork is the latest state.
let canonical_fork = post_state_data_provider.canonical_fork();
let history_provider = db.history_by_block_number(canonical_fork.number)?;
let state_provider = history_provider;
let provider = PostStateProvider::new(state_provider, post_state_data_provider);
let mut executor = externals.executor_factory.with_sp(&provider);
let post_state = executor.execute_and_verify_receipt(&block, U256::MAX, Some(senders))?;
Ok(post_state)
}
/// Validate and execute the given block, and append it to this chain.
#[track_caller]
pub(crate) fn append_block<DB, C, EF>(
&mut self,
block: SealedBlockWithSenders,
@ -188,9 +279,13 @@ impl AppendableChain {
canonical_fork,
};
let block_state =
Self::validate_and_execute(block.clone(), parent_block, post_state_data, externals)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
let block_state = Self::validate_and_execute_sidechain(
block.clone(),
parent_block,
post_state_data,
externals,
)
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
self.state.extend(block_state);
self.blocks.insert(block.number, block);
Ok(())