From fd71f9ee7af1b50420950aafb95af1ef3cdb4bd1 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 20 Nov 2024 18:32:04 +0000 Subject: [PATCH 01/17] feat(trie): sparse trie methods for trie task integration --- crates/trie/sparse/src/state.rs | 31 +++++++++++++++++++++++++++++-- crates/trie/sparse/src/trie.rs | 12 ++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 0b0db1401150..4080d7b51d0d 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -42,7 +42,7 @@ impl SparseStateTrie { account: B256, proof: impl IntoIterator, ) -> SparseStateTrieResult<()> { - if self.revealed.contains_key(&account) { + if self.is_account_revealed(&account) { return Ok(()); } @@ -73,7 +73,7 @@ impl SparseStateTrie { slot: B256, proof: impl IntoIterator, ) -> SparseStateTrieResult<()> { - if self.revealed.get(&account).is_some_and(|v| v.contains(&slot)) { + if self.is_storage_slot_revealed(&account, &slot) { return Ok(()); } @@ -124,11 +124,38 @@ impl SparseStateTrie { Ok(()) } + /// Remove the leaf node. + pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseStateTrieResult<()> { + self.state.remove_leaf(path)?; + Ok(()) + } + /// Returns sparse trie root if the trie has been revealed. pub fn root(&mut self) -> Option { self.state.root() } + /// Calculates the hashes of the nodes below the provided level. + pub fn calculate_below_level(&mut self, level: usize) { + self.state.calculate_below_level(level); + } + + /// Update the leaf node of a storage trie at the provided address. + pub fn update_storage_leaf( + &mut self, + address: B256, + slot: Nibbles, + value: Vec, + ) -> SparseStateTrieResult<()> { + self.storages.entry(address).or_default().update_leaf(slot, value)?; + Ok(()) + } + + /// Wipe the storage trie at the provided address. + pub fn wipe_storage(&mut self, address: B256) { + self.storages.remove(&address); + } + /// Returns storage sparse trie root if the trie has been revealed. pub fn storage_root(&mut self, account: B256) -> Option { self.storages.get_mut(&account).and_then(|trie| trie.root()) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 696934d3edb9..c5b3968e21e1 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -63,10 +63,22 @@ impl SparseTrie { Ok(()) } + /// Remove the leaf node. + pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseTrieResult<()> { + let revealed = self.as_revealed_mut().ok_or(SparseTrieError::Blind)?; + revealed.remove_leaf(path)?; + Ok(()) + } + /// Calculates and returns the trie root if the trie has been revealed. pub fn root(&mut self) -> Option { Some(self.as_revealed_mut()?.root()) } + + /// Calculates the hashes of the nodes below the provided level. + pub fn calculate_below_level(&mut self, level: usize) { + self.as_revealed_mut().unwrap().update_rlp_node_level(level); + } } /// The representation of revealed sparse trie. From caa8dac5ed84aee9a3ca76835d5d4e6344c5a1e4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 21 Nov 2024 10:17:38 +0000 Subject: [PATCH 02/17] rename update/remove leaf methods --- crates/trie/sparse/src/state.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 4080d7b51d0d..adbaaedbff63 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -118,14 +118,18 @@ impl SparseStateTrie { Ok(Some(root_node)) } - /// Update the leaf node. - pub fn update_leaf(&mut self, path: Nibbles, value: Vec) -> SparseStateTrieResult<()> { + /// Update the account leaf node. + pub fn update_account_leaf( + &mut self, + path: Nibbles, + value: Vec, + ) -> SparseStateTrieResult<()> { self.state.update_leaf(path, value)?; Ok(()) } - /// Remove the leaf node. - pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseStateTrieResult<()> { + /// Remove the account leaf node. + pub fn remove_account_leaf(&mut self, path: &Nibbles) -> SparseStateTrieResult<()> { self.state.remove_leaf(path)?; Ok(()) } From ecfa317ab8cc5c65da9baac3fdf19b35ec457e9c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 21 Nov 2024 10:35:10 +0000 Subject: [PATCH 03/17] wipe storage by removing all nodes --- crates/trie/sparse/src/state.rs | 5 +++-- crates/trie/sparse/src/trie.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index adbaaedbff63..0854df5d7624 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -156,8 +156,9 @@ impl SparseStateTrie { } /// Wipe the storage trie at the provided address. - pub fn wipe_storage(&mut self, address: B256) { - self.storages.remove(&address); + pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> { + let Some(trie) = self.storages.get_mut(&address) else { return Ok(()) }; + trie.wipe().map_err(Into::into) } /// Returns storage sparse trie root if the trie has been revealed. diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index c5b3968e21e1..aa58d60cc894 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -70,6 +70,13 @@ impl SparseTrie { Ok(()) } + /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. + pub fn wipe(&mut self) -> SparseTrieResult<()> { + let revealed = self.as_revealed_mut().ok_or(SparseTrieError::Blind)?; + revealed.wipe()?; + Ok(()) + } + /// Calculates and returns the trie root if the trie has been revealed. pub fn root(&mut self) -> Option { Some(self.as_revealed_mut()?.root()) @@ -565,6 +572,14 @@ impl RevealedSparseTrie { Ok(nodes) } + /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. + pub fn wipe(&mut self) -> SparseTrieResult<()> { + self.prefix_set.extend_keys(self.nodes.drain().map(|(k, _)| k)); + self.values.clear(); + self.nodes.insert(Nibbles::default(), SparseNode::Empty); + Ok(()) + } + /// Return the root of the sparse trie. /// Updates all remaining dirty nodes before calculating the root. pub fn root(&mut self) -> B256 { From e5e052d75dd34ef8dd26999adabbc32f0439aafd Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 21 Nov 2024 10:45:30 +0000 Subject: [PATCH 04/17] wipe properly --- crates/trie/sparse/src/trie.rs | 49 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index aa58d60cc894..13050bb076d3 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -574,9 +574,12 @@ impl RevealedSparseTrie { /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. pub fn wipe(&mut self) -> SparseTrieResult<()> { - self.prefix_set.extend_keys(self.nodes.drain().map(|(k, _)| k)); - self.values.clear(); - self.nodes.insert(Nibbles::default(), SparseNode::Empty); + let paths = self.values.drain().map(|(k, _)| k).collect::>(); + // TODO: can we optimize the batch deletion here? + for path in paths { + self.remove_leaf(&path)?; + } + debug_assert_eq!(self.nodes, HashMap::from_iter([(Nibbles::default(), SparseNode::Empty)])); Ok(()) } @@ -1698,4 +1701,44 @@ mod tests { ] ); } + + #[test] + fn sparse_trie_wipe() { + let mut sparse = RevealedSparseTrie::default(); + + let value = alloy_rlp::encode_fixed_size(&U256::ZERO).to_vec(); + + // Extension (Key = 5) – Level 0 + // └── Branch (Mask = 1011) – Level 1 + // ├── 0 -> Extension (Key = 23) – Level 2 + // │ └── Branch (Mask = 0101) – Level 3 + // │ ├── 1 -> Leaf (Key = 1, Path = 50231) – Level 4 + // │ └── 3 -> Leaf (Key = 3, Path = 50233) – Level 4 + // ├── 2 -> Leaf (Key = 013, Path = 52013) – Level 2 + // └── 3 -> Branch (Mask = 0101) – Level 2 + // ├── 1 -> Leaf (Key = 3102, Path = 53102) – Level 3 + // └── 3 -> Branch (Mask = 1010) – Level 3 + // ├── 0 -> Leaf (Key = 3302, Path = 53302) – Level 4 + // └── 2 -> Leaf (Key = 3320, Path = 53320) – Level 4 + sparse + .update_leaf(Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x1]), value.clone()) + .unwrap(); + sparse + .update_leaf(Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x3]), value.clone()) + .unwrap(); + sparse + .update_leaf(Nibbles::from_nibbles([0x5, 0x2, 0x0, 0x1, 0x3]), value.clone()) + .unwrap(); + sparse + .update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x1, 0x0, 0x2]), value.clone()) + .unwrap(); + sparse + .update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x3, 0x0, 0x2]), value.clone()) + .unwrap(); + sparse.update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x3, 0x2, 0x0]), value).unwrap(); + + sparse.wipe().unwrap(); + + assert_eq!(sparse.root(), EMPTY_ROOT_HASH); + } } From 0faf0f2753c51e528bb03c77588fa475213d1d6e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 22 Nov 2024 13:03:55 +0000 Subject: [PATCH 05/17] simplify wipe --- crates/trie/sparse/src/trie.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index cf8b3f9be83a..6e166ed7bb11 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -601,12 +601,19 @@ impl RevealedSparseTrie { /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. pub fn wipe(&mut self) -> SparseTrieResult<()> { - let paths = self.values.drain().map(|(k, _)| k).collect::>(); - // TODO: can we optimize the batch deletion here? - for path in paths { - self.remove_leaf(&path)?; - } - debug_assert_eq!(self.nodes, HashMap::from_iter([(Nibbles::default(), SparseNode::Empty)])); + let updates = self.updates.as_ref().map(|_| { + let mut updates = SparseTrieUpdates::default(); + for (path, node) in self.nodes.drain() { + if matches!(node, SparseNode::Branch { .. }) { + updates.removed_nodes.insert(path); + } + } + + updates + }); + *self = Self::default(); + self.prefix_set = PrefixSetMut::all(); + self.updates = updates; Ok(()) } @@ -2026,7 +2033,7 @@ mod tests { #[test] fn sparse_trie_wipe() { - let mut sparse = RevealedSparseTrie::default(); + let mut sparse = RevealedSparseTrie::default().with_updates(true); let value = alloy_rlp::encode_fixed_size(&U256::ZERO).to_vec(); @@ -2062,5 +2069,17 @@ mod tests { sparse.wipe().unwrap(); assert_eq!(sparse.root(), EMPTY_ROOT_HASH); + assert_eq!( + sparse.take_updates(), + SparseTrieUpdates { + removed_nodes: HashSet::from_iter([ + Nibbles::from_nibbles([0x5]), + Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3]), + Nibbles::from_nibbles([0x5, 0x3]), + Nibbles::from_nibbles([0x5, 0x3, 0x3]) + ]), + updated_nodes: Default::default() + } + ); } } From 9550d9363abff791e08afb03ad4b599cc2bea4be Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 22 Nov 2024 13:30:36 +0000 Subject: [PATCH 06/17] roman's approach --- crates/trie/sparse/src/trie.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 6e166ed7bb11..c2797047ed21 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -601,19 +601,8 @@ impl RevealedSparseTrie { /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. pub fn wipe(&mut self) -> SparseTrieResult<()> { - let updates = self.updates.as_ref().map(|_| { - let mut updates = SparseTrieUpdates::default(); - for (path, node) in self.nodes.drain() { - if matches!(node, SparseNode::Branch { .. }) { - updates.removed_nodes.insert(path); - } - } - - updates - }); *self = Self::default(); self.prefix_set = PrefixSetMut::all(); - self.updates = updates; Ok(()) } @@ -2069,17 +2058,5 @@ mod tests { sparse.wipe().unwrap(); assert_eq!(sparse.root(), EMPTY_ROOT_HASH); - assert_eq!( - sparse.take_updates(), - SparseTrieUpdates { - removed_nodes: HashSet::from_iter([ - Nibbles::from_nibbles([0x5]), - Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3]), - Nibbles::from_nibbles([0x5, 0x3]), - Nibbles::from_nibbles([0x5, 0x3, 0x3]) - ]), - updated_nodes: Default::default() - } - ); } } From 829dfd54b5786d51227bb72b1e034c0a595c0994 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 10:27:41 +0000 Subject: [PATCH 07/17] take trie updates --- crates/trie/sparse/src/state.rs | 41 +++++++++++++++++++++++++++++---- crates/trie/sparse/src/trie.rs | 4 ++-- crates/trie/trie/src/updates.rs | 12 +++++----- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 0854df5d7624..eac7d0cd0522 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -6,17 +6,22 @@ use alloy_primitives::{ Bytes, B256, }; use alloy_rlp::Decodable; -use reth_trie::{Nibbles, TrieNode}; +use reth_trie::{ + updates::{StorageTrieUpdates, TrieUpdates}, + Nibbles, TrieNode, +}; /// Sparse state trie representing lazy-loaded Ethereum state trie. #[derive(Default, Debug)] pub struct SparseStateTrie { /// Sparse account trie. - pub(crate) state: SparseTrie, + state: SparseTrie, /// Sparse storage tries. - pub(crate) storages: HashMap, + storages: HashMap, /// Collection of revealed account and storage keys. - pub(crate) revealed: HashMap>, + revealed: HashMap>, + /// Collection of addresses that had their storage tries wiped. + wiped_storages: HashSet, } impl SparseStateTrie { @@ -158,6 +163,7 @@ impl SparseStateTrie { /// Wipe the storage trie at the provided address. pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> { let Some(trie) = self.storages.get_mut(&address) else { return Ok(()) }; + self.wiped_storages.insert(address); trie.wipe().map_err(Into::into) } @@ -165,6 +171,33 @@ impl SparseStateTrie { pub fn storage_root(&mut self, account: B256) -> Option { self.storages.get_mut(&account).and_then(|trie| trie.root()) } + + /// Returns [`TrieUpdates`] by taking the updates from the revealed sparse tries. + /// + /// Returns `None` if the accounts trie is not revealed. + pub fn take_trie_updates(&mut self) -> Option { + self.state.as_revealed_mut().map(|state| { + let updates = state.take_updates(); + TrieUpdates { + account_nodes: HashMap::from_iter(updates.updated_nodes), + removed_nodes: HashSet::from_iter(updates.removed_nodes), + storage_tries: self + .storages + .iter_mut() + .map(|(address, trie)| { + let trie = trie.as_revealed_mut().unwrap(); + let updates = trie.take_updates(); + let updates = StorageTrieUpdates { + is_deleted: self.wiped_storages.contains(address), + storage_nodes: HashMap::from_iter(updates.updated_nodes), + removed_nodes: HashSet::from_iter(updates.removed_nodes), + }; + (*address, updates) + }) + .collect(), + } + }) + } } #[cfg(test)] diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index c2797047ed21..c6d20624d7c2 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -1024,8 +1024,8 @@ impl RlpNodeBuffers { /// The aggregation of sparse trie updates. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct SparseTrieUpdates { - updated_nodes: HashMap, - removed_nodes: HashSet, + pub(crate) updated_nodes: HashMap, + pub(crate) removed_nodes: HashSet, } #[cfg(test)] diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 6d1bcab63d8f..716f9962fa68 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -7,10 +7,10 @@ use std::collections::{HashMap, HashSet}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdates { #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_map"))] - pub(crate) account_nodes: HashMap, + pub account_nodes: HashMap, #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_set"))] - pub(crate) removed_nodes: HashSet, - pub(crate) storage_tries: HashMap, + pub removed_nodes: HashSet, + pub storage_tries: HashMap, } impl TrieUpdates { @@ -113,13 +113,13 @@ impl TrieUpdates { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StorageTrieUpdates { /// Flag indicating whether the trie was deleted. - pub(crate) is_deleted: bool, + pub is_deleted: bool, /// Collection of updated storage trie nodes. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_map"))] - pub(crate) storage_nodes: HashMap, + pub storage_nodes: HashMap, /// Collection of removed storage trie nodes. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_set"))] - pub(crate) removed_nodes: HashSet, + pub removed_nodes: HashSet, } #[cfg(feature = "test-utils")] From 2f6ca727499bb3de6cdee3956a65ef9d148bfc18 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 10:37:27 +0000 Subject: [PATCH 08/17] add comments --- crates/trie/trie/src/updates.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 716f9962fa68..e7bc490647cc 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -6,10 +6,13 @@ use std::collections::{HashMap, HashSet}; #[derive(PartialEq, Eq, Clone, Default, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdates { + /// Collection of updated intermediate account nodes indexed by full path. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_map"))] pub account_nodes: HashMap, + /// Collection of removed intermediate account nodes indexed by full path. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_set"))] pub removed_nodes: HashSet, + /// Collection of updated storage tries indexed by the hashed address. pub storage_tries: HashMap, } From 1d989ba16386f250c8defc5c684b444648f0837c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 10:57:05 +0000 Subject: [PATCH 09/17] perf(engine): sparse trie calculation for state root task --- Cargo.lock | 2 + Cargo.toml | 1 + crates/engine/tree/Cargo.toml | 26 +++++---- crates/engine/tree/src/tree/root.rs | 88 +++++++++++++++++++++++++++-- crates/trie/common/src/proofs.rs | 32 +++++++++-- 5 files changed, 129 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6463fd11a016..1270cfb9f0b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7279,7 +7279,9 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-trie", + "reth-trie-common", "reth-trie-parallel", + "reth-trie-sparse", "revm-primitives", "thiserror 1.0.69", "tokio", diff --git a/Cargo.toml b/Cargo.toml index ad17ea4ad0ca..113d0661f3ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -419,6 +419,7 @@ reth-trie = { path = "crates/trie/trie" } reth-trie-common = { path = "crates/trie/common" } reth-trie-db = { path = "crates/trie/db" } reth-trie-parallel = { path = "crates/trie/parallel" } +reth-trie-sparse = { path = "crates/trie/sparse" } # revm revm = { version = "18.0.0", features = ["std"], default-features = false } diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index 70be84a9f799..cb3da5e8308e 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -13,41 +13,44 @@ workspace = true [dependencies] # reth reth-beacon-consensus.workspace = true -reth-blockchain-tree.workspace = true reth-blockchain-tree-api.workspace = true +reth-blockchain-tree.workspace = true reth-chain-state.workspace = true -reth-consensus.workspace = true reth-chainspec.workspace = true +reth-consensus.workspace = true reth-engine-primitives.workspace = true reth-errors.workspace = true reth-evm.workspace = true reth-network-p2p.workspace = true -reth-payload-builder.workspace = true reth-payload-builder-primitives.workspace = true +reth-payload-builder.workspace = true reth-payload-primitives.workspace = true reth-payload-validator.workspace = true -reth-primitives.workspace = true reth-primitives-traits.workspace = true +reth-primitives.workspace = true reth-provider.workspace = true reth-prune.workspace = true reth-revm.workspace = true reth-stages-api.workspace = true reth-tasks.workspace = true -reth-trie.workspace = true +reth-trie-common.workspace = true reth-trie-parallel.workspace = true +reth-trie-sparse.workspace = true +reth-trie.workspace = true # alloy -alloy-primitives.workspace = true +alloy-consensus.workspace = true alloy-eips.workspace = true +alloy-primitives.workspace = true +alloy-rlp.workspace = true alloy-rpc-types-engine.workspace = true -alloy-consensus.workspace = true revm-primitives.workspace = true # common futures.workspace = true -tokio = { workspace = true, features = ["macros", "sync"] } thiserror.workspace = true +tokio = { workspace = true, features = ["macros", "sync"] } # metrics metrics.workspace = true @@ -64,20 +67,21 @@ reth-tracing = { workspace = true, optional = true } [dev-dependencies] # reth -reth-db = { workspace = true, features = ["test-utils"] } reth-chain-state = { workspace = true, features = ["test-utils"] } +reth-chainspec.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } reth-ethereum-engine-primitives.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } reth-exex-types.workspace = true reth-network-p2p = { workspace = true, features = ["test-utils"] } -reth-prune.workspace = true reth-prune-types.workspace = true +reth-prune.workspace = true reth-rpc-types-compat.workspace = true reth-stages = { workspace = true, features = ["test-utils"] } reth-static-file.workspace = true reth-tracing.workspace = true -reth-chainspec.workspace = true +# alloy alloy-rlp.workspace = true assert_matches.workspace = true diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index 45cf5a780310..ca7956879ee0 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -1,12 +1,21 @@ //! State root task related functionality. +use alloy_primitives::map::FbHashMap; +use alloy_rlp::{BufMut, Encodable}; use reth_provider::providers::ConsistentDbView; -use reth_trie::{updates::TrieUpdates, TrieInput}; +use reth_trie::{ + updates::TrieUpdates, HashedPostState, MultiProof, Nibbles, TrieAccount, TrieInput, + EMPTY_ROOT_HASH, +}; use reth_trie_parallel::root::ParallelStateRootError; -use revm_primitives::{EvmState, B256}; -use std::sync::{ - mpsc::{self, Receiver, RecvError}, - Arc, +use reth_trie_sparse::{SparseStateTrie, SparseStateTrieResult}; +use revm_primitives::{map::FbHashSet, EvmState, B256}; +use std::{ + sync::{ + mpsc::{self, Receiver, RecvError}, + Arc, + }, + time::{Duration, Instant}, }; use tracing::debug; @@ -133,6 +142,75 @@ where } } +fn calculate_state_root_with_sparse( + mut trie: Box, + multiproof: MultiProof, + targets: FbHashMap<32, FbHashSet<32>>, + state: HashedPostState, +) -> SparseStateTrieResult<(Box, Duration)> { + let started_at = Instant::now(); + + // Reveal new accounts and storage slots. + for (address, slots) in targets { + let path = Nibbles::unpack(address); + trie.reveal_account(address, multiproof.account_proof_nodes(&path))?; + + let storage_proofs = multiproof.storage_proof_nodes(address, slots); + + for (slot, proof) in storage_proofs { + trie.reveal_storage_slot(address, slot, proof)?; + } + } + + // Update storage slots with new values and calculate storage roots. + let mut storage_roots = FbHashMap::default(); + for (address, storage) in state.storages { + if storage.wiped { + // TODO(alexey): wipe storage + // trie.wipe_storage(address); + storage_roots.insert(address, EMPTY_ROOT_HASH); + } + + for (slot, value) in storage.storage { + let slot_path = Nibbles::unpack(slot); + // TODO(alexey): update storage leaf + // trie.update_storage_leaf( + // address, + // slot_path, + // alloy_rlp::encode_fixed_size(&value).to_vec(), + // )?; + } + + storage_roots.insert(address, trie.storage_root(address).unwrap()); + } + + // Update accounts with new values and include updated storage roots + for (address, account) in state.accounts { + let path = Nibbles::unpack(address); + + if let Some(account) = account { + let storage_root = storage_roots + .remove(&address) + .map(Some) + .unwrap_or_else(|| trie.storage_root(address)) + .unwrap_or(EMPTY_ROOT_HASH); + + let mut encoded = Vec::with_capacity(128); + TrieAccount::from((account, storage_root)).encode(&mut encoded as &mut dyn BufMut); + trie.update_leaf(path, encoded)?; + } else { + // TODO(alexey): remove account leaf + // trie.remove_account_leaf(&path)?; + } + } + + // TODO(alexey): calculate below level + // trie.calculate_below_level(2); + let elapsed = started_at.elapsed(); + + Ok((trie, elapsed)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/trie/common/src/proofs.rs b/crates/trie/common/src/proofs.rs index d0a5cd220420..8e014f6d8c6d 100644 --- a/crates/trie/common/src/proofs.rs +++ b/crates/trie/common/src/proofs.rs @@ -26,6 +26,31 @@ pub struct MultiProof { } impl MultiProof { + /// Return the account proof nodes for the given account path. + pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> { + self.account_subtree.matching_nodes_sorted(path) + } + + /// Return the storage proof nodes for the given storage slots of the account path. + pub fn storage_proof_nodes( + &self, + hashed_address: B256, + slots: impl IntoIterator, + ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> { + self.storages + .get(&hashed_address) + .map(|storage_mp| { + slots + .into_iter() + .map(|slot| { + let nibbles = Nibbles::unpack(slot); + (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles)) + }) + .collect() + }) + .unwrap_or_default() + } + /// Construct the account proof from the multiproof. pub fn account_proof( &self, @@ -37,10 +62,9 @@ impl MultiProof { // Retrieve the account proof. let proof = self - .account_subtree - .matching_nodes_iter(&nibbles) - .sorted_by(|a, b| a.0.cmp(b.0)) - .map(|(_, node)| node.clone()) + .account_proof_nodes(&nibbles) + .into_iter() + .map(|(_, node)| node) .collect::>(); // Inspect the last node in the proof. If it's a leaf node with matching suffix, From ae2d0dfb9a3e73780c62f3b8b4a03434be5a27b6 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 10:58:16 +0000 Subject: [PATCH 10/17] remove todos --- crates/engine/tree/src/tree/root.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index ca7956879ee0..611df9ce1877 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -166,19 +166,17 @@ fn calculate_state_root_with_sparse( let mut storage_roots = FbHashMap::default(); for (address, storage) in state.storages { if storage.wiped { - // TODO(alexey): wipe storage - // trie.wipe_storage(address); + trie.wipe_storage(address); storage_roots.insert(address, EMPTY_ROOT_HASH); } for (slot, value) in storage.storage { let slot_path = Nibbles::unpack(slot); - // TODO(alexey): update storage leaf - // trie.update_storage_leaf( - // address, - // slot_path, - // alloy_rlp::encode_fixed_size(&value).to_vec(), - // )?; + trie.update_storage_leaf( + address, + slot_path, + alloy_rlp::encode_fixed_size(&value).to_vec(), + )?; } storage_roots.insert(address, trie.storage_root(address).unwrap()); @@ -197,15 +195,13 @@ fn calculate_state_root_with_sparse( let mut encoded = Vec::with_capacity(128); TrieAccount::from((account, storage_root)).encode(&mut encoded as &mut dyn BufMut); - trie.update_leaf(path, encoded)?; + trie.update_account_leaf(path, encoded)?; } else { - // TODO(alexey): remove account leaf - // trie.remove_account_leaf(&path)?; + trie.remove_account_leaf(&path)?; } } - // TODO(alexey): calculate below level - // trie.calculate_below_level(2); + trie.calculate_below_level(2); let elapsed = started_at.elapsed(); Ok((trie, elapsed)) From 61a3777ef796956fba882dd11c072772f318153a Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 11:04:23 +0000 Subject: [PATCH 11/17] fix clippy --- crates/engine/tree/src/tree/root.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index 611df9ce1877..e3ac9dd1004e 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -142,6 +142,7 @@ where } } +#[allow(dead_code)] fn calculate_state_root_with_sparse( mut trie: Box, multiproof: MultiProof, @@ -166,7 +167,7 @@ fn calculate_state_root_with_sparse( let mut storage_roots = FbHashMap::default(); for (address, storage) in state.storages { if storage.wiped { - trie.wipe_storage(address); + trie.wipe_storage(address)?; storage_roots.insert(address, EMPTY_ROOT_HASH); } From b420f4daee0b1df028d0409b0a0eb5f21ea3c970 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 11:06:37 +0000 Subject: [PATCH 12/17] fix feature propagation --- Cargo.lock | 1 - crates/engine/tree/Cargo.toml | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1270cfb9f0b3..8e972a8e2255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7279,7 +7279,6 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-trie", - "reth-trie-common", "reth-trie-parallel", "reth-trie-sparse", "revm-primitives", diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index cb3da5e8308e..5242268b1758 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -33,7 +33,6 @@ reth-prune.workspace = true reth-revm.workspace = true reth-stages-api.workspace = true reth-tasks.workspace = true -reth-trie-common.workspace = true reth-trie-parallel.workspace = true reth-trie-sparse.workspace = true reth-trie.workspace = true @@ -94,23 +93,23 @@ harness = false [features] test-utils = [ - "reth-db/test-utils", - "reth-chain-state/test-utils", - "reth-network-p2p/test-utils", - "reth-prune-types", - "reth-stages/test-utils", - "reth-static-file", - "reth-tracing", "reth-blockchain-tree/test-utils", + "reth-chain-state/test-utils", "reth-chainspec/test-utils", "reth-consensus/test-utils", + "reth-db/test-utils", "reth-evm/test-utils", + "reth-network-p2p/test-utils", "reth-payload-builder/test-utils", + "reth-primitives-traits/test-utils", "reth-primitives/test-utils", + "reth-provider/test-utils", + "reth-prune-types", + "reth-prune-types?/test-utils", "reth-revm/test-utils", "reth-stages-api/test-utils", - "reth-provider/test-utils", + "reth-stages/test-utils", + "reth-static-file", + "reth-tracing", "reth-trie/test-utils", - "reth-prune-types?/test-utils", - "reth-primitives-traits/test-utils", ] From 50d7d61092d11c3df3314f03eae5523383e80f06 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 11:19:41 +0000 Subject: [PATCH 13/17] with_updates on state struct --- crates/trie/sparse/src/state.rs | 15 +++++++++++++-- crates/trie/sparse/src/trie.rs | 20 ++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index eac7d0cd0522..ca2487fe8d96 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -14,6 +14,7 @@ use reth_trie::{ /// Sparse state trie representing lazy-loaded Ethereum state trie. #[derive(Default, Debug)] pub struct SparseStateTrie { + retain_updates: bool, /// Sparse account trie. state: SparseTrie, /// Sparse storage tries. @@ -30,6 +31,12 @@ impl SparseStateTrie { Self { state, ..Default::default() } } + /// Set the retention of branch node updates and deletions. + pub const fn with_updates(mut self, retain_updates: bool) -> Self { + self.retain_updates = retain_updates; + self + } + /// Returns `true` if account was already revealed. pub fn is_account_revealed(&self, account: &B256) -> bool { self.revealed.contains_key(account) @@ -56,7 +63,7 @@ impl SparseStateTrie { let Some(root_node) = self.validate_proof(&mut proof)? else { return Ok(()) }; // Reveal root node if it wasn't already. - let trie = self.state.reveal_root(root_node)?; + let trie = self.state.reveal_root(root_node, self.retain_updates)?; // Reveal the remaining proof nodes. for (path, bytes) in proof { @@ -87,7 +94,11 @@ impl SparseStateTrie { let Some(root_node) = self.validate_proof(&mut proof)? else { return Ok(()) }; // Reveal root node if it wasn't already. - let trie = self.storages.entry(account).or_default().reveal_root(root_node)?; + let trie = self + .storages + .entry(account) + .or_default() + .reveal_root(root_node, self.retain_updates)?; // Reveal the remaining proof nodes. for (path, bytes) in proof { diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index c6d20624d7c2..8b2a966a6937 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -53,9 +53,13 @@ impl SparseTrie { /// # Returns /// /// Mutable reference to [`RevealedSparseTrie`]. - pub fn reveal_root(&mut self, root: TrieNode) -> SparseTrieResult<&mut RevealedSparseTrie> { + pub fn reveal_root( + &mut self, + root: TrieNode, + retain_updates: bool, + ) -> SparseTrieResult<&mut RevealedSparseTrie> { if self.is_blind() { - *self = Self::Revealed(Box::new(RevealedSparseTrie::from_root(root)?)) + *self = Self::Revealed(Box::new(RevealedSparseTrie::from_root(root, retain_updates)?)) } Ok(self.as_revealed_mut().unwrap()) } @@ -139,19 +143,20 @@ impl Default for RevealedSparseTrie { impl RevealedSparseTrie { /// Create new revealed sparse trie from the given root node. - pub fn from_root(node: TrieNode) -> SparseTrieResult { + pub fn from_root(node: TrieNode, retain_updates: bool) -> SparseTrieResult { let mut this = Self { nodes: HashMap::default(), values: HashMap::default(), prefix_set: PrefixSetMut::default(), rlp_buf: Vec::new(), updates: None, - }; + } + .with_updates(retain_updates); this.reveal_node(Nibbles::default(), node)?; Ok(this) } - /// Makes the sparse trie to store updated branch nodes. + /// Set the retention of branch node updates and deletions. pub fn with_updates(mut self, retain_updates: bool) -> Self { if retain_updates { self.updates = Some(SparseTrieUpdates::default()); @@ -1586,7 +1591,7 @@ mod tests { TrieMask::new(0b11), )); - let mut sparse = RevealedSparseTrie::from_root(branch.clone()).unwrap(); + let mut sparse = RevealedSparseTrie::from_root(branch.clone(), false).unwrap(); // Reveal a branch node and one of its children // @@ -1748,6 +1753,7 @@ mod tests { .take_proof_nodes(); let mut sparse = RevealedSparseTrie::from_root( TrieNode::decode(&mut &proof_nodes.nodes_sorted()[0].1[..]).unwrap(), + false, ) .unwrap(); @@ -1822,6 +1828,7 @@ mod tests { .take_proof_nodes(); let mut sparse = RevealedSparseTrie::from_root( TrieNode::decode(&mut &proof_nodes.nodes_sorted()[0].1[..]).unwrap(), + false, ) .unwrap(); @@ -1892,6 +1899,7 @@ mod tests { .take_proof_nodes(); let mut sparse = RevealedSparseTrie::from_root( TrieNode::decode(&mut &proof_nodes.nodes_sorted()[0].1[..]).unwrap(), + false, ) .unwrap(); From 691db9f40374542194cc858d3e1ad5553d3f0cd0 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 12:43:21 +0000 Subject: [PATCH 14/17] simple test --- Cargo.lock | 1 + crates/trie/sparse/Cargo.toml | 1 + crates/trie/sparse/src/state.rs | 167 +++++++++++++++++++++++++++++++- crates/trie/sparse/src/trie.rs | 3 +- 4 files changed, 168 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6463fd11a016..7a49e173f4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9453,6 +9453,7 @@ version = "1.1.2" dependencies = [ "alloy-primitives", "alloy-rlp", + "arbitrary", "assert_matches", "criterion", "itertools 0.13.0", diff --git a/crates/trie/sparse/Cargo.toml b/crates/trie/sparse/Cargo.toml index 3301975961e2..4c64bf716de9 100644 --- a/crates/trie/sparse/Cargo.toml +++ b/crates/trie/sparse/Cargo.toml @@ -32,6 +32,7 @@ reth-testing-utils.workspace = true reth-trie = { workspace = true, features = ["test-utils"] } reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } +arbitrary.workspace = true assert_matches.workspace = true criterion.workspace = true itertools.workspace = true diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index ca2487fe8d96..b76da7937105 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -205,6 +205,7 @@ impl SparseStateTrie { }; (*address, updates) }) + .filter(|(_, updates)| !updates.is_empty()) .collect(), } }) @@ -214,10 +215,17 @@ impl SparseStateTrie { #[cfg(test)] mod tests { use super::*; - use alloy_primitives::Bytes; + use alloy_primitives::{b256, Bytes, U256}; use alloy_rlp::EMPTY_STRING_CODE; + use arbitrary::Arbitrary; use assert_matches::assert_matches; - use reth_trie::HashBuilder; + use itertools::Itertools; + use rand::{rngs::StdRng, Rng, SeedableRng}; + use reth_primitives_traits::Account; + use reth_trie::{ + updates::StorageTrieUpdates, BranchNodeCompact, HashBuilder, TrieAccount, TrieMask, + EMPTY_ROOT_HASH, + }; use reth_trie_common::proof::ProofRetainer; #[test] @@ -275,4 +283,159 @@ mod tests { HashMap::from_iter([(Default::default(), SparseTrie::revealed_empty())]) ); } + + #[test] + fn take_trie_updates() { + reth_tracing::init_test_tracing(); + + // let mut rng = generators::rng(); + let mut rng = StdRng::seed_from_u64(1); + + let mut bytes = [0u8; 1024]; + rng.fill(bytes.as_mut_slice()); + + let slot_1 = b256!("1000000000000000000000000000000000000000000000000000000000000000"); + let slot_path_1 = Nibbles::unpack(slot_1); + let value_1 = U256::from(rng.gen::()); + let slot_2 = b256!("1100000000000000000000000000000000000000000000000000000000000000"); + let slot_path_2 = Nibbles::unpack(slot_2); + let value_2 = U256::from(rng.gen::()); + let slot_3 = b256!("2000000000000000000000000000000000000000000000000000000000000000"); + let slot_path_3 = Nibbles::unpack(slot_3); + let value_3 = U256::from(rng.gen::()); + + let mut storage_hash_builder = + HashBuilder::default().with_proof_retainer(ProofRetainer::from_iter([ + slot_path_1.clone(), + slot_path_2.clone(), + ])); + storage_hash_builder.add_leaf(slot_path_1.clone(), &alloy_rlp::encode_fixed_size(&value_1)); + storage_hash_builder.add_leaf(slot_path_2.clone(), &alloy_rlp::encode_fixed_size(&value_2)); + + let storage_root = storage_hash_builder.root(); + let proof_nodes = storage_hash_builder.take_proof_nodes(); + let storage_proof_1 = proof_nodes + .iter() + .filter(|(path, _)| path.is_empty() || slot_path_1.common_prefix_length(path) > 0) + .map(|(path, proof)| (path.clone(), proof.clone())) + .sorted_by_key(|(path, _)| path.clone()) + .collect::>(); + let storage_proof_2 = proof_nodes + .iter() + .filter(|(path, _)| path.is_empty() || slot_path_2.common_prefix_length(path) > 0) + .map(|(path, proof)| (path.clone(), proof.clone())) + .sorted_by_key(|(path, _)| path.clone()) + .collect::>(); + + let address_1 = b256!("1000000000000000000000000000000000000000000000000000000000000000"); + let address_path_1 = Nibbles::unpack(address_1); + let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + let mut trie_account_1 = TrieAccount::from((account_1, storage_root)); + let address_2 = b256!("1100000000000000000000000000000000000000000000000000000000000000"); + let address_path_2 = Nibbles::unpack(address_2); + let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + let mut trie_account_2 = TrieAccount::from((account_2, EMPTY_ROOT_HASH)); + + let mut hash_builder = + HashBuilder::default().with_proof_retainer(ProofRetainer::from_iter([ + address_path_1.clone(), + address_path_2.clone(), + ])); + hash_builder.add_leaf(address_path_1.clone(), &alloy_rlp::encode(trie_account_1)); + hash_builder.add_leaf(address_path_2.clone(), &alloy_rlp::encode(trie_account_2)); + + let root = hash_builder.root(); + let proof_nodes = hash_builder.take_proof_nodes(); + let proof_1 = proof_nodes + .iter() + .filter(|(path, _)| path.is_empty() || address_path_1.common_prefix_length(path) > 0) + .map(|(path, proof)| (path.clone(), proof.clone())) + .sorted_by_key(|(path, _)| path.clone()) + .collect::>(); + let proof_2 = proof_nodes + .iter() + .filter(|(path, _)| path.is_empty() || address_path_2.common_prefix_length(path) > 0) + .map(|(path, proof)| (path.clone(), proof.clone())) + .sorted_by_key(|(path, _)| path.clone()) + .collect::>(); + + let mut sparse = SparseStateTrie::default().with_updates(true); + sparse.reveal_account(address_1, proof_1).unwrap(); + sparse.reveal_account(address_2, proof_2).unwrap(); + sparse.reveal_storage_slot(address_1, slot_1, storage_proof_1.clone()).unwrap(); + sparse.reveal_storage_slot(address_1, slot_2, storage_proof_2.clone()).unwrap(); + sparse.reveal_storage_slot(address_2, slot_1, storage_proof_1).unwrap(); + sparse.reveal_storage_slot(address_2, slot_2, storage_proof_2).unwrap(); + + assert_eq!(sparse.root(), Some(root)); + + let address_3 = b256!("2000000000000000000000000000000000000000000000000000000000000000"); + let address_path_3 = Nibbles::unpack(address_3); + let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 }; + let trie_account_3 = TrieAccount::from((account_3, EMPTY_ROOT_HASH)); + + sparse.update_account_leaf(address_path_3, alloy_rlp::encode(trie_account_3)).unwrap(); + + sparse.update_storage_leaf(address_1, slot_path_3, alloy_rlp::encode(value_3)).unwrap(); + trie_account_1.storage_root = sparse.storage_root(address_1).unwrap(); + sparse.update_account_leaf(address_path_1, alloy_rlp::encode(trie_account_1)).unwrap(); + + sparse.wipe_storage(address_2).unwrap(); + trie_account_2.storage_root = sparse.storage_root(address_2).unwrap(); + sparse.update_account_leaf(address_path_2, alloy_rlp::encode(trie_account_2)).unwrap(); + + sparse.root(); + + let sparse_updates = sparse.take_trie_updates().unwrap(); + // TODO(alexey): assert against real state root calculation updates + pretty_assertions::assert_eq!( + sparse_updates, + TrieUpdates { + account_nodes: HashMap::from_iter([ + ( + Nibbles::default(), + BranchNodeCompact { + state_mask: TrieMask::new(0b110), + tree_mask: TrieMask::new(0b000), + hash_mask: TrieMask::new(0b010), + hashes: vec![b256!( + "4c4ffbda3569fcf2c24ea2000b4cec86ef8b92cbf9ff415db43184c0f75a212e" + )], + root_hash: Some(b256!( + "60944bd29458529c3065d19f63c6e3d5269596fd3b04ca2e7b318912dc89ca4c" + )) + }, + ), + ]), + storage_tries: HashMap::from_iter([ + ( + b256!("1000000000000000000000000000000000000000000000000000000000000000"), + StorageTrieUpdates { + is_deleted: false, + storage_nodes: HashMap::from_iter([( + Nibbles::default(), + BranchNodeCompact { + state_mask: TrieMask::new(0b110), + tree_mask: TrieMask::new(0b000), + hash_mask: TrieMask::new(0b010), + hashes: vec![b256!("5bc8b4fdf51839c1e18b8d6a4bd3e2e52c9f641860f0e4d197b68c2679b0e436")], + root_hash: Some(b256!("c44abf1a9e1a92736ac479b20328e8d7998aa8838b6ef52620324c9ce85e3201")) + } + )]), + removed_nodes: HashSet::default() + } + ), + ( + b256!("1100000000000000000000000000000000000000000000000000000000000000"), + StorageTrieUpdates { + is_deleted: true, + storage_nodes: HashMap::default(), + removed_nodes: HashSet::default() + } + ) + ]), + removed_nodes: HashSet::default() + } + ); + } } diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 8b2a966a6937..a0153278795b 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -804,8 +804,7 @@ impl RevealedSparseTrie { } // Set the hash mask. If a child node has a hash value AND is a - // branch node, set the hash mask - // and save the hash. + // branch node, set the hash mask and save the hash. let hash = child.as_hash().filter(|_| node_type.is_branch()); hash_mask_values.push(hash.is_some()); if let Some(hash) = hash { From e1d219d2f4f354012961c38a5b3891ec658c2d9c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 12:59:03 +0000 Subject: [PATCH 15/17] no need for result here --- crates/trie/sparse/src/trie.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index a0153278795b..bab166d7831c 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -81,7 +81,7 @@ impl SparseTrie { /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. pub fn wipe(&mut self) -> SparseTrieResult<()> { let revealed = self.as_revealed_mut().ok_or(SparseTrieError::Blind)?; - revealed.wipe()?; + revealed.wipe(); Ok(()) } @@ -605,10 +605,9 @@ impl RevealedSparseTrie { } /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. - pub fn wipe(&mut self) -> SparseTrieResult<()> { + pub fn wipe(&mut self) { *self = Self::default(); self.prefix_set = PrefixSetMut::all(); - Ok(()) } /// Return the root of the sparse trie. @@ -2062,7 +2061,7 @@ mod tests { .unwrap(); sparse.update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x3, 0x2, 0x0]), value).unwrap(); - sparse.wipe().unwrap(); + sparse.wipe(); assert_eq!(sparse.root(), EMPTY_ROOT_HASH); } From 347ad869ae5253649b6b18a57c82511490f1adb4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 13:48:42 +0000 Subject: [PATCH 16/17] comment for fn --- crates/engine/tree/src/tree/root.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index e3ac9dd1004e..6ebcca7a9297 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -142,8 +142,10 @@ where } } +/// Updates the sparse trie with the given proofs and state, and returns the updated trie and the +/// time it took. #[allow(dead_code)] -fn calculate_state_root_with_sparse( +fn update_sparse_trie( mut trie: Box, multiproof: MultiProof, targets: FbHashMap<32, FbHashSet<32>>, From 4a6f24755408ebaea6ef759fa5725f8f1c7c4aa5 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 25 Nov 2024 13:59:11 +0000 Subject: [PATCH 17/17] move to const --- crates/engine/tree/src/tree/root.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index 6ebcca7a9297..27f835ec754b 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -19,6 +19,9 @@ use std::{ }; use tracing::debug; +/// The level below which the sparse trie hashes are calculated in [`update_sparse_trie`]. +const SPARSE_TRIE_INCREMENTAL_LEVEL: usize = 2; + /// Result of the state root calculation pub(crate) type StateRootResult = Result<(B256, TrieUpdates), ParallelStateRootError>; @@ -204,7 +207,7 @@ fn update_sparse_trie( } } - trie.calculate_below_level(2); + trie.calculate_below_level(SPARSE_TRIE_INCREMENTAL_LEVEL); let elapsed = started_at.elapsed(); Ok((trie, elapsed))