mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 17:18:08 -05:00
feat(trie): historical & sidechain state root (#6131)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
use crate::{
|
||||
canonical_chain::CanonicalChain,
|
||||
chain::BlockKind,
|
||||
metrics::{MakeCanonicalAction, MakeCanonicalDurationsRecorder, TreeMetrics},
|
||||
state::{BlockChainId, TreeState},
|
||||
AppendableChain, BlockIndices, BlockchainTreeConfig, BundleStateData, TreeExternals,
|
||||
@@ -11,7 +10,7 @@ use reth_db::{database::Database, DatabaseError};
|
||||
use reth_interfaces::{
|
||||
blockchain_tree::{
|
||||
error::{BlockchainTreeError, CanonicalError, InsertBlockError, InsertBlockErrorKind},
|
||||
BlockStatus, BlockValidationKind, CanonicalOutcome, InsertPayloadOk,
|
||||
BlockAttachment, BlockStatus, BlockValidationKind, CanonicalOutcome, InsertPayloadOk,
|
||||
},
|
||||
consensus::{Consensus, ConsensusError},
|
||||
executor::{BlockExecutionError, BlockValidationError},
|
||||
@@ -132,7 +131,6 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
/// Function will check:
|
||||
/// * if block is inside database returns [BlockStatus::Valid].
|
||||
/// * if block is inside buffer returns [BlockStatus::Disconnected].
|
||||
/// * if block is part of a side chain returns [BlockStatus::Accepted].
|
||||
/// * if block is part of the canonical returns [BlockStatus::Valid].
|
||||
///
|
||||
/// Returns an error if
|
||||
@@ -147,12 +145,12 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
if block.number <= last_finalized_block {
|
||||
// check if block is canonical
|
||||
if self.is_block_hash_canonical(&block.hash)? {
|
||||
return Ok(Some(BlockStatus::Valid))
|
||||
return Ok(Some(BlockStatus::Valid(BlockAttachment::Canonical)))
|
||||
}
|
||||
|
||||
// check if block is inside database
|
||||
if self.externals.provider_factory.provider()?.block_number(block.hash)?.is_some() {
|
||||
return Ok(Some(BlockStatus::Valid))
|
||||
return Ok(Some(BlockStatus::Valid(BlockAttachment::Canonical)))
|
||||
}
|
||||
|
||||
return Err(BlockchainTreeError::PendingBlockIsFinalized {
|
||||
@@ -163,12 +161,12 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
|
||||
// check if block is part of canonical chain
|
||||
if self.is_block_hash_canonical(&block.hash)? {
|
||||
return Ok(Some(BlockStatus::Valid))
|
||||
return Ok(Some(BlockStatus::Valid(BlockAttachment::Canonical)))
|
||||
}
|
||||
|
||||
// is block inside chain
|
||||
if let Some(status) = self.is_block_inside_chain(&block) {
|
||||
return Ok(Some(status))
|
||||
if let Some(attachment) = self.is_block_inside_chain(&block) {
|
||||
return Ok(Some(BlockStatus::Valid(attachment)))
|
||||
}
|
||||
|
||||
// check if block is disconnected
|
||||
@@ -289,7 +287,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
&mut self,
|
||||
block: SealedBlockWithSenders,
|
||||
block_validation_kind: BlockValidationKind,
|
||||
) -> Result<BlockStatus, InsertBlockError> {
|
||||
) -> Result<BlockStatus, InsertBlockErrorKind> {
|
||||
debug_assert!(self.validate_block(&block).is_ok(), "Block must be validated");
|
||||
|
||||
let parent = block.parent_num_hash();
|
||||
@@ -301,11 +299,8 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
}
|
||||
|
||||
// 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, block_validation_kind)
|
||||
if self.is_block_hash_canonical(&parent.hash)? {
|
||||
return self.try_append_canonical_chain(block.clone(), block_validation_kind)
|
||||
}
|
||||
|
||||
// this is another check to ensure that if the block points to a canonical block its block
|
||||
@@ -315,22 +310,17 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
{
|
||||
// we found the parent block in canonical chain
|
||||
if canonical_parent_number != parent.number {
|
||||
return Err(InsertBlockError::consensus_error(
|
||||
ConsensusError::ParentBlockNumberMismatch {
|
||||
parent_block_number: canonical_parent_number,
|
||||
block_number: block.number,
|
||||
},
|
||||
block.block,
|
||||
))
|
||||
return Err(ConsensusError::ParentBlockNumberMismatch {
|
||||
parent_block_number: canonical_parent_number,
|
||||
block_number: block.number,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a parent inside the buffer, validate against it.
|
||||
if let Some(buffered_parent) = self.state.buffered_blocks.block(&parent.hash) {
|
||||
self.externals
|
||||
.consensus
|
||||
.validate_header_against_parent(&block, buffered_parent)
|
||||
.map_err(|err| InsertBlockError::consensus_error(err, block.block.clone()))?;
|
||||
self.externals.consensus.validate_header_against_parent(&block, buffered_parent)?;
|
||||
}
|
||||
|
||||
// insert block inside unconnected block buffer. Delaying its execution.
|
||||
@@ -341,10 +331,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
// shouldn't happen right after insertion
|
||||
let lowest_ancestor =
|
||||
self.state.buffered_blocks.lowest_ancestor(&block.hash).ok_or_else(|| {
|
||||
InsertBlockError::tree_error(
|
||||
BlockchainTreeError::BlockBufferingFailed { block_hash: block.hash },
|
||||
block.block,
|
||||
)
|
||||
BlockchainTreeError::BlockBufferingFailed { block_hash: block.hash }
|
||||
})?;
|
||||
|
||||
Ok(BlockStatus::Disconnected { missing_ancestor: lowest_ancestor.parent_num_hash() })
|
||||
@@ -360,91 +347,59 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
&mut self,
|
||||
block: SealedBlockWithSenders,
|
||||
block_validation_kind: BlockValidationKind,
|
||||
) -> Result<BlockStatus, InsertBlockError> {
|
||||
) -> Result<BlockStatus, InsertBlockErrorKind> {
|
||||
let parent = block.parent_num_hash();
|
||||
let block_num_hash = block.num_hash();
|
||||
debug!(target: "blockchain_tree", head = ?block_num_hash.hash, ?parent, "Appending block to canonical chain");
|
||||
// create new chain that points to that block
|
||||
//return self.fork_canonical_chain(block.clone());
|
||||
// TODO save pending block to database
|
||||
// https://github.com/paradigmxyz/reth/issues/1713
|
||||
|
||||
let (block_status, chain) = {
|
||||
let provider = self
|
||||
.externals
|
||||
.provider_factory
|
||||
.provider()
|
||||
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?;
|
||||
let provider = self.externals.provider_factory.provider()?;
|
||||
|
||||
// Validate that the block is post merge
|
||||
let parent_td = provider
|
||||
.header_td(&block.parent_hash)
|
||||
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?
|
||||
.ok_or_else(|| {
|
||||
InsertBlockError::tree_error(
|
||||
BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash },
|
||||
block.block.clone(),
|
||||
)
|
||||
})?;
|
||||
// Validate that the block is post merge
|
||||
let parent_td = provider
|
||||
.header_td(&block.parent_hash)?
|
||||
.ok_or_else(|| BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash })?;
|
||||
|
||||
// Pass the parent total difficulty to short-circuit unnecessary calculations.
|
||||
if !self
|
||||
.externals
|
||||
.provider_factory
|
||||
.chain_spec()
|
||||
.fork(Hardfork::Paris)
|
||||
.active_at_ttd(parent_td, U256::ZERO)
|
||||
{
|
||||
return Err(InsertBlockError::execution_error(
|
||||
BlockValidationError::BlockPreMerge { hash: block.hash }.into(),
|
||||
block.block,
|
||||
))
|
||||
}
|
||||
// Pass the parent total difficulty to short-circuit unnecessary calculations.
|
||||
if !self
|
||||
.externals
|
||||
.provider_factory
|
||||
.chain_spec()
|
||||
.fork(Hardfork::Paris)
|
||||
.active_at_ttd(parent_td, U256::ZERO)
|
||||
{
|
||||
return Err(BlockExecutionError::Validation(BlockValidationError::BlockPreMerge {
|
||||
hash: block.hash,
|
||||
})
|
||||
.into())
|
||||
}
|
||||
|
||||
let parent_header = provider
|
||||
.header(&block.parent_hash)
|
||||
.map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?
|
||||
.ok_or_else(|| {
|
||||
InsertBlockError::tree_error(
|
||||
BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash },
|
||||
block.block.clone(),
|
||||
)
|
||||
})?
|
||||
.seal(block.parent_hash);
|
||||
let parent_header = provider
|
||||
.header(&block.parent_hash)?
|
||||
.ok_or_else(|| BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash })?
|
||||
.seal(block.parent_hash);
|
||||
|
||||
let canonical_chain = self.canonical_chain();
|
||||
let canonical_chain = self.canonical_chain();
|
||||
|
||||
if block.parent_hash == canonical_chain.tip().hash {
|
||||
let chain = AppendableChain::new_canonical_head_fork(
|
||||
block,
|
||||
&parent_header,
|
||||
canonical_chain.inner(),
|
||||
parent,
|
||||
&self.externals,
|
||||
block_validation_kind,
|
||||
)?;
|
||||
let status = if block_validation_kind.is_exhaustive() {
|
||||
BlockStatus::Valid
|
||||
} else {
|
||||
BlockStatus::Accepted
|
||||
};
|
||||
|
||||
(status, chain)
|
||||
} else {
|
||||
let chain = AppendableChain::new_canonical_fork(
|
||||
block,
|
||||
&parent_header,
|
||||
canonical_chain.inner(),
|
||||
parent,
|
||||
&self.externals,
|
||||
)?;
|
||||
(BlockStatus::Accepted, chain)
|
||||
}
|
||||
let block_attachment = if block.parent_hash == canonical_chain.tip().hash {
|
||||
BlockAttachment::Canonical
|
||||
} else {
|
||||
BlockAttachment::HistoricalFork
|
||||
};
|
||||
|
||||
let chain = AppendableChain::new_canonical_fork(
|
||||
block,
|
||||
&parent_header,
|
||||
canonical_chain.inner(),
|
||||
parent,
|
||||
&self.externals,
|
||||
block_attachment,
|
||||
block_validation_kind,
|
||||
)?;
|
||||
|
||||
self.insert_chain(chain);
|
||||
self.try_connect_buffered_blocks(block_num_hash);
|
||||
Ok(block_status)
|
||||
|
||||
Ok(BlockStatus::Valid(block_attachment))
|
||||
}
|
||||
|
||||
/// Try inserting a block into the given side chain.
|
||||
@@ -456,7 +411,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
block: SealedBlockWithSenders,
|
||||
chain_id: BlockChainId,
|
||||
block_validation_kind: BlockValidationKind,
|
||||
) -> Result<BlockStatus, InsertBlockError> {
|
||||
) -> Result<BlockStatus, InsertBlockErrorKind> {
|
||||
debug!(target: "blockchain_tree", "Inserting block into side chain");
|
||||
let block_num_hash = block.num_hash();
|
||||
// Create a new sidechain by forking the given chain, or append the block if the parent
|
||||
@@ -464,37 +419,25 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
let block_hashes = self.all_chain_hashes(chain_id);
|
||||
|
||||
// get canonical fork.
|
||||
let canonical_fork = match self.canonical_fork(chain_id) {
|
||||
None => {
|
||||
return Err(InsertBlockError::tree_error(
|
||||
BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() },
|
||||
block.block,
|
||||
))
|
||||
}
|
||||
Some(fork) => fork,
|
||||
};
|
||||
let canonical_fork = self.canonical_fork(chain_id).ok_or_else(|| {
|
||||
BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() }
|
||||
})?;
|
||||
|
||||
// get chain that block needs to join to.
|
||||
let parent_chain = match self.state.chains.get_mut(&chain_id) {
|
||||
Some(parent_chain) => parent_chain,
|
||||
None => {
|
||||
return Err(InsertBlockError::tree_error(
|
||||
BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() },
|
||||
block.block,
|
||||
))
|
||||
}
|
||||
};
|
||||
let parent_chain = self.state.chains.get_mut(&chain_id).ok_or_else(|| {
|
||||
BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() }
|
||||
})?;
|
||||
|
||||
let chain_tip = parent_chain.tip().hash();
|
||||
let canonical_chain = self.state.block_indices.canonical_chain();
|
||||
|
||||
// append the block if it is continuing the side chain.
|
||||
let status = if chain_tip == block.parent_hash {
|
||||
let block_attachment = if chain_tip == block.parent_hash {
|
||||
// check if the chain extends the currently tracked canonical head
|
||||
let block_kind = if canonical_fork.hash == canonical_chain.tip().hash {
|
||||
BlockKind::ExtendsCanonicalHead
|
||||
let block_attachment = if canonical_fork.hash == canonical_chain.tip().hash {
|
||||
BlockAttachment::Canonical
|
||||
} else {
|
||||
BlockKind::ForksHistoricalBlock
|
||||
BlockAttachment::HistoricalFork
|
||||
};
|
||||
|
||||
debug!(target: "blockchain_tree", "Appending block to side chain");
|
||||
@@ -506,19 +449,12 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
canonical_chain.inner(),
|
||||
&self.externals,
|
||||
canonical_fork,
|
||||
block_kind,
|
||||
block_attachment,
|
||||
block_validation_kind,
|
||||
)?;
|
||||
|
||||
self.block_indices_mut().insert_non_fork_block(block_number, block_hash, chain_id);
|
||||
|
||||
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)
|
||||
} else {
|
||||
Ok(BlockStatus::Accepted)
|
||||
}
|
||||
block_attachment
|
||||
} else {
|
||||
debug!(target: "blockchain_tree", ?canonical_fork, "Starting new fork from side chain");
|
||||
// the block starts a new fork
|
||||
@@ -528,15 +464,16 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
canonical_chain.inner(),
|
||||
canonical_fork,
|
||||
&self.externals,
|
||||
block_validation_kind,
|
||||
)?;
|
||||
self.insert_chain(chain);
|
||||
Ok(BlockStatus::Accepted)
|
||||
BlockAttachment::HistoricalFork
|
||||
};
|
||||
|
||||
// After we inserted the block, we try to connect any buffered blocks
|
||||
self.try_connect_buffered_blocks(block_num_hash);
|
||||
|
||||
status
|
||||
Ok(BlockStatus::Valid(block_attachment))
|
||||
}
|
||||
|
||||
/// Get all block hashes from a sidechain that are not part of the canonical chain.
|
||||
@@ -724,21 +661,21 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if block is found inside chain and if the chain extends the canonical chain.
|
||||
/// Check if block is found inside chain and its attachment.
|
||||
///
|
||||
/// if it does extend the canonical chain, return `BlockStatus::Valid`
|
||||
/// if it does not extend the canonical chain, return `BlockStatus::Accepted`
|
||||
/// if it is canonical or extends the canonical chain, return [BlockAttachment::Canonical]
|
||||
/// if it does not extend the canonical chain, return [BlockAttachment::HistoricalFork]
|
||||
#[track_caller]
|
||||
fn is_block_inside_chain(&self, block: &BlockNumHash) -> Option<BlockStatus> {
|
||||
fn is_block_inside_chain(&self, block: &BlockNumHash) -> Option<BlockAttachment> {
|
||||
// check if block known and is already in the tree
|
||||
if let Some(chain_id) = self.block_indices().get_blocks_chain_id(&block.hash) {
|
||||
// find the canonical fork of this chain
|
||||
let canonical_fork = self.canonical_fork(chain_id).expect("Chain id is valid");
|
||||
// if the block's chain extends canonical chain
|
||||
return if canonical_fork == self.block_indices().canonical_tip() {
|
||||
Some(BlockStatus::Valid)
|
||||
Some(BlockAttachment::Canonical)
|
||||
} else {
|
||||
Some(BlockStatus::Accepted)
|
||||
Some(BlockAttachment::HistoricalFork)
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -783,9 +720,10 @@ 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, block_validation_kind)?,
|
||||
))
|
||||
let status = self
|
||||
.try_insert_validated_block(block.clone(), block_validation_kind)
|
||||
.map_err(|kind| InsertBlockError::new(block.block, kind))?;
|
||||
Ok(InsertPayloadOk::Inserted(status))
|
||||
}
|
||||
|
||||
/// Finalize blocks up until and including `finalized_block`, and remove them from the tree.
|
||||
@@ -1587,7 +1525,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(canonical_block_1.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1597,12 +1535,12 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(canonical_block_2.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(sidechain_block_1.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1617,7 +1555,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(sidechain_block_2.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1627,7 +1565,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(canonical_block_3.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1644,7 +1582,7 @@ mod tests {
|
||||
let genesis = data.genesis;
|
||||
|
||||
// test pops execution results from vector, so order is from last to first.
|
||||
let externals = setup_externals(vec![exec2.clone(), exec1.clone(), exec2, exec1]);
|
||||
let externals = setup_externals(vec![exec2.clone(), exec2, exec1]);
|
||||
|
||||
// last finalized block would be number 9.
|
||||
setup_genesis(&externals.provider_factory, genesis);
|
||||
@@ -1660,12 +1598,12 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
// we have one chain that has two blocks.
|
||||
@@ -1690,7 +1628,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
|
||||
// fork chain.
|
||||
@@ -1783,7 +1721,7 @@ mod tests {
|
||||
// insert block1 and buffered block2 is inserted
|
||||
assert_eq!(
|
||||
tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
// Buffered blocks: []
|
||||
@@ -1806,13 +1744,13 @@ mod tests {
|
||||
// already inserted block will `InsertPayloadOk::AlreadySeen(_)`
|
||||
assert_eq!(
|
||||
tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::AlreadySeen(BlockStatus::Valid)
|
||||
InsertPayloadOk::AlreadySeen(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
// block two is already inserted.
|
||||
assert_eq!(
|
||||
tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::AlreadySeen(BlockStatus::Valid)
|
||||
InsertPayloadOk::AlreadySeen(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
// make block1 canonical
|
||||
@@ -1852,7 +1790,7 @@ mod tests {
|
||||
// reinsert two blocks that point to canonical chain
|
||||
assert_eq!(
|
||||
tree.insert_block(block1a.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
|
||||
TreeTester::default()
|
||||
@@ -1867,7 +1805,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(),
|
||||
InsertPayloadOk::Inserted(BlockStatus::Accepted)
|
||||
InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork))
|
||||
);
|
||||
// Trie state:
|
||||
// b2 b2a (side chain)
|
||||
@@ -2069,7 +2007,10 @@ mod tests {
|
||||
// update canonical block to b2, this would make b2a be removed
|
||||
assert_eq!(tree.connect_buffered_blocks_to_canonical_hashes_and_finalize(12), Ok(()));
|
||||
|
||||
assert_eq!(tree.is_block_known(block2.num_hash()).unwrap(), Some(BlockStatus::Valid));
|
||||
assert_eq!(
|
||||
tree.is_block_known(block2.num_hash()).unwrap(),
|
||||
Some(BlockStatus::Valid(BlockAttachment::Canonical))
|
||||
);
|
||||
|
||||
// Trie state:
|
||||
// b2 (finalized)
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -226,9 +192,15 @@ 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(),
|
||||
@@ -236,35 +208,12 @@ impl AppendableChain {
|
||||
.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
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 { .. }) => {
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
///
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
@@ -182,17 +182,16 @@ impl CanonicalOutcome {
|
||||
|
||||
/// From Engine API spec, block inclusion can be valid, accepted or invalid.
|
||||
/// Invalid case is already covered by error, but we need to make distinction
|
||||
/// between if it is valid (extends canonical chain) or just accepted (is side chain).
|
||||
/// If we don't know the block parent we are returning Disconnected status
|
||||
/// as we can't make a claim if block is valid or not.
|
||||
/// between valid blocks that extend canonical chain and the ones that fork off
|
||||
/// into side chains (see [BlockAttachment]). If we don't know the block
|
||||
/// parent we are returning Disconnected status as we can't make a claim if
|
||||
/// block is valid or not.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum BlockStatus {
|
||||
/// If block validation is valid and block extends canonical chain.
|
||||
/// In BlockchainTree sense it forks on canonical tip.
|
||||
Valid,
|
||||
/// If the block is valid, but it does not extend canonical chain.
|
||||
/// (It is side chain) or hasn't been fully validated but ancestors of a payload are known.
|
||||
Accepted,
|
||||
/// If block is valid and block extends canonical chain.
|
||||
/// In BlockchainTree terms, it forks off canonical tip.
|
||||
Valid(BlockAttachment),
|
||||
/// If block is valid and block forks off canonical chain.
|
||||
/// If blocks is not connected to canonical chain.
|
||||
Disconnected {
|
||||
/// The lowest ancestor block that is not connected to the canonical chain.
|
||||
@@ -200,6 +199,31 @@ pub enum BlockStatus {
|
||||
},
|
||||
}
|
||||
|
||||
/// Represents what kind of block is being executed and validated.
|
||||
///
|
||||
/// This is required to:
|
||||
/// - differentiate whether trie state updates should be cached.
|
||||
/// - inform other
|
||||
/// 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 enum BlockAttachment {
|
||||
/// The `block` is canonical or a descendant of the canonical head.
|
||||
/// ([`head..(block.parent)*,block`])
|
||||
Canonical,
|
||||
/// 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.
|
||||
HistoricalFork,
|
||||
}
|
||||
|
||||
impl BlockAttachment {
|
||||
/// Returns `true` if the block is canonical or a descendant of the canonical head.
|
||||
#[inline]
|
||||
pub const fn is_canonical(&self) -> bool {
|
||||
matches!(self, BlockAttachment::Canonical)
|
||||
}
|
||||
}
|
||||
|
||||
/// How a payload was inserted if it was valid.
|
||||
///
|
||||
/// If the payload was valid, but has already been seen, [`InsertPayloadOk::AlreadySeen(_)`] is
|
||||
|
||||
@@ -12,9 +12,10 @@ use reth_db::{
|
||||
};
|
||||
use reth_interfaces::provider::ProviderResult;
|
||||
use reth_primitives::{
|
||||
trie::AccountProof, Account, Address, BlockNumber, Bytecode, StorageKey, StorageValue, B256,
|
||||
constants::EPOCH_SLOTS, trie::AccountProof, Account, Address, BlockNumber, Bytecode,
|
||||
StorageKey, StorageValue, B256,
|
||||
};
|
||||
use reth_trie::updates::TrieUpdates;
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
||||
|
||||
/// State provider for a given block number which takes a tx reference.
|
||||
///
|
||||
@@ -95,6 +96,31 @@ impl<'b, TX: DbTx> HistoricalStateProviderRef<'b, TX> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieve revert hashed state for this history provider.
|
||||
fn revert_state(&self) -> ProviderResult<HashedPostState> {
|
||||
if !self.lowest_available_blocks.is_account_history_available(self.block_number) ||
|
||||
!self.lowest_available_blocks.is_storage_history_available(self.block_number)
|
||||
{
|
||||
return Err(ProviderError::StateAtBlockPruned(self.block_number))
|
||||
}
|
||||
|
||||
let (tip, _) = self
|
||||
.tx
|
||||
.cursor_read::<tables::CanonicalHeaders>()?
|
||||
.last()?
|
||||
.ok_or(ProviderError::BestBlockNotFound)?;
|
||||
|
||||
if tip.saturating_sub(self.block_number) > EPOCH_SLOTS {
|
||||
tracing::warn!(
|
||||
target: "provider::historical_sp",
|
||||
target = self.block_number,
|
||||
"Attempt to calculate state root for an old block might result in OOM, tread carefully"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(HashedPostState::from_revert_range(self.tx, self.block_number..=tip)?)
|
||||
}
|
||||
|
||||
fn history_info<T, K>(
|
||||
&self,
|
||||
key: K,
|
||||
@@ -199,15 +225,23 @@ impl<'b, TX: DbTx> BlockHashReader for HistoricalStateProviderRef<'b, TX> {
|
||||
}
|
||||
|
||||
impl<'b, TX: DbTx> StateRootProvider for HistoricalStateProviderRef<'b, TX> {
|
||||
fn state_root(&self, _bundle_state: &BundleStateWithReceipts) -> ProviderResult<B256> {
|
||||
Err(ProviderError::StateRootNotAvailableForHistoricalBlock)
|
||||
fn state_root(&self, state: &BundleStateWithReceipts) -> ProviderResult<B256> {
|
||||
let mut revert_state = self.revert_state()?;
|
||||
revert_state.extend(state.hash_state_slow());
|
||||
revert_state.sort();
|
||||
revert_state.state_root(self.tx).map_err(|err| ProviderError::Database(err.into()))
|
||||
}
|
||||
|
||||
fn state_root_with_updates(
|
||||
&self,
|
||||
_bundle_state: &BundleStateWithReceipts,
|
||||
state: &BundleStateWithReceipts,
|
||||
) -> ProviderResult<(B256, TrieUpdates)> {
|
||||
Err(ProviderError::StateRootNotAvailableForHistoricalBlock)
|
||||
let mut revert_state = self.revert_state()?;
|
||||
revert_state.extend(state.hash_state_slow());
|
||||
revert_state.sort();
|
||||
revert_state
|
||||
.state_root_with_updates(self.tx)
|
||||
.map_err(|err| ProviderError::Database(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ where
|
||||
// If the storage has been wiped at any point
|
||||
storage.wiped &&
|
||||
// and the current storage does not contain any non-zero values
|
||||
storage.non_zero_valued_storage.is_empty()
|
||||
storage.non_zero_valued_slots.is_empty()
|
||||
}
|
||||
None => self.cursor.seek_exact(key)?.is_none(),
|
||||
};
|
||||
@@ -294,12 +294,11 @@ where
|
||||
if let Some(storage) = self.post_state.storages.get(&account) {
|
||||
debug_assert!(storage.sorted, "`HashedStorage` must be pre-sorted");
|
||||
|
||||
post_state_entry = storage.non_zero_valued_storage.get(self.post_state_storage_index);
|
||||
post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index);
|
||||
|
||||
while post_state_entry.map(|(slot, _)| slot < &subkey).unwrap_or_default() {
|
||||
self.post_state_storage_index += 1;
|
||||
post_state_entry =
|
||||
storage.non_zero_valued_storage.get(self.post_state_storage_index);
|
||||
post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,11 +373,10 @@ where
|
||||
if let Some(storage) = self.post_state.storages.get(&account) {
|
||||
debug_assert!(storage.sorted, "`HashedStorage` must be pre-sorted");
|
||||
|
||||
post_state_entry = storage.non_zero_valued_storage.get(self.post_state_storage_index);
|
||||
post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index);
|
||||
while post_state_entry.map(|(slot, _)| slot <= last_slot).unwrap_or_default() {
|
||||
self.post_state_storage_index += 1;
|
||||
post_state_entry =
|
||||
storage.non_zero_valued_storage.get(self.post_state_storage_index);
|
||||
post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ impl HashedPostState {
|
||||
/// NOTE: In order to have the resulting [HashedPostState] be a correct
|
||||
/// overlay of the plain state, the end of the range must be the current tip.
|
||||
pub fn from_revert_range<TX: DbTx>(
|
||||
tx: TX,
|
||||
tx: &TX,
|
||||
range: RangeInclusive<BlockNumber>,
|
||||
) -> Result<Self, DatabaseError> {
|
||||
// A single map for aggregating state changes where each map value is a tuple
|
||||
@@ -119,7 +119,7 @@ impl HashedPostState {
|
||||
this.insert_account(hashed_address, account_change);
|
||||
}
|
||||
|
||||
// The `wiped`` flag indicates only whether previous storage entries should be looked
|
||||
// The `wiped` flag indicates only whether previous storage entries should be looked
|
||||
// up in db or not. For reverts it's a noop since all wiped changes had been written as
|
||||
// storage reverts.
|
||||
let mut hashed_storage = HashedStorage::new(false);
|
||||
@@ -132,6 +132,40 @@ impl HashedPostState {
|
||||
Ok(this.sorted())
|
||||
}
|
||||
|
||||
/// Extend this hashed post state with contents of another.
|
||||
/// Entries in the second hashed post state take precedence.
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
// Merge accounts and insert them into extended state.
|
||||
let mut accounts: HashMap<B256, Option<Account>> = HashMap::from_iter(
|
||||
self.accounts
|
||||
.drain(..)
|
||||
.map(|(hashed_address, account)| (hashed_address, Some(account)))
|
||||
.chain(
|
||||
self.destroyed_accounts.drain().map(|hashed_address| (hashed_address, None)),
|
||||
),
|
||||
);
|
||||
for (hashed_address, account) in other.accounts {
|
||||
accounts.insert(hashed_address, Some(account));
|
||||
}
|
||||
for hashed_address in other.destroyed_accounts {
|
||||
accounts.insert(hashed_address, None);
|
||||
}
|
||||
for (hashed_address, account) in accounts {
|
||||
self.insert_account(hashed_address, account);
|
||||
}
|
||||
|
||||
for (hashed_address, storage) in other.storages {
|
||||
match self.storages.entry(hashed_address) {
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(storage);
|
||||
}
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().extend(storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort and return self.
|
||||
pub fn sorted(mut self) -> Self {
|
||||
self.sort();
|
||||
@@ -204,7 +238,7 @@ impl HashedPostState {
|
||||
account_prefix_set.insert(Nibbles::unpack(hashed_address));
|
||||
|
||||
let storage_prefix_set_entry = storage_prefix_set.entry(*hashed_address).or_default();
|
||||
for (hashed_slot, _) in &hashed_storage.non_zero_valued_storage {
|
||||
for (hashed_slot, _) in &hashed_storage.non_zero_valued_slots {
|
||||
storage_prefix_set_entry.insert(Nibbles::unpack(hashed_slot));
|
||||
}
|
||||
for hashed_slot in &hashed_storage.zero_valued_slots {
|
||||
@@ -223,6 +257,7 @@ impl HashedPostState {
|
||||
&self,
|
||||
tx: &'a TX,
|
||||
) -> StateRoot<&'a TX, HashedPostStateCursorFactory<'a, '_, TX>> {
|
||||
assert!(self.sorted, "Hashed post state must be sorted for state root calculation");
|
||||
let (account_prefix_set, storage_prefix_set) = self.construct_prefix_sets();
|
||||
let hashed_cursor_factory = HashedPostStateCursorFactory::new(tx, self);
|
||||
StateRoot::from_tx(tx)
|
||||
@@ -277,10 +312,10 @@ impl HashedPostState {
|
||||
}
|
||||
|
||||
/// The post state account storage with hashed slots.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct HashedStorage {
|
||||
/// Hashed storage slots with non-zero.
|
||||
pub(crate) non_zero_valued_storage: Vec<(B256, U256)>,
|
||||
pub(crate) non_zero_valued_slots: Vec<(B256, U256)>,
|
||||
/// Slots that have been zero valued.
|
||||
pub(crate) zero_valued_slots: AHashSet<B256>,
|
||||
/// Whether the storage was wiped or not.
|
||||
@@ -293,13 +328,32 @@ impl HashedStorage {
|
||||
/// Create new instance of [HashedStorage].
|
||||
pub fn new(wiped: bool) -> Self {
|
||||
Self {
|
||||
non_zero_valued_storage: Vec::new(),
|
||||
non_zero_valued_slots: Vec::new(),
|
||||
zero_valued_slots: AHashSet::new(),
|
||||
wiped,
|
||||
sorted: true, // empty is sorted
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend hashed storage with contents of other.
|
||||
/// The entries in second hashed storage take precedence.
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
let mut entries: HashMap<B256, U256> =
|
||||
HashMap::from_iter(self.non_zero_valued_slots.drain(..).chain(
|
||||
self.zero_valued_slots.drain().map(|hashed_slot| (hashed_slot, U256::ZERO)),
|
||||
));
|
||||
for (hashed_slot, value) in other.non_zero_valued_slots {
|
||||
entries.insert(hashed_slot, value);
|
||||
}
|
||||
for hashed_slot in other.zero_valued_slots {
|
||||
entries.insert(hashed_slot, U256::ZERO);
|
||||
}
|
||||
for (hashed_slot, value) in entries {
|
||||
self.insert_slot(hashed_slot, value);
|
||||
}
|
||||
self.wiped |= other.wiped;
|
||||
}
|
||||
|
||||
/// Returns `true` if the storage was wiped.
|
||||
pub fn wiped(&self) -> bool {
|
||||
self.wiped
|
||||
@@ -310,13 +364,13 @@ impl HashedStorage {
|
||||
self.zero_valued_slots
|
||||
.iter()
|
||||
.map(|slot| (*slot, U256::ZERO))
|
||||
.chain(self.non_zero_valued_storage.iter().cloned())
|
||||
.chain(self.non_zero_valued_slots.iter().cloned())
|
||||
}
|
||||
|
||||
/// Sorts the non zero value storage entries.
|
||||
pub fn sort_storage(&mut self) {
|
||||
if !self.sorted {
|
||||
self.non_zero_valued_storage.sort_unstable_by_key(|(slot, _)| *slot);
|
||||
self.non_zero_valued_slots.sort_unstable_by_key(|(slot, _)| *slot);
|
||||
self.sorted = true;
|
||||
}
|
||||
}
|
||||
@@ -327,7 +381,7 @@ impl HashedStorage {
|
||||
if value.is_zero() {
|
||||
self.zero_valued_slots.insert(slot);
|
||||
} else {
|
||||
self.non_zero_valued_storage.push((slot, value));
|
||||
self.non_zero_valued_slots.push((slot, value));
|
||||
self.sorted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1164,7 +1164,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn account_trie_around_extension_node_with_dbtrie() {
|
||||
let factory = create_test_provider_factory();
|
||||
let tx = factory.provider_rw().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user