mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(trie): read-only root calculation (#2233)
This commit is contained in:
@ -1,8 +1,6 @@
|
||||
use super::TrieCursor;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbCursorRW},
|
||||
tables, Error,
|
||||
};
|
||||
use crate::updates::TrieKey;
|
||||
use reth_db::{cursor::DbCursorRO, tables, Error};
|
||||
use reth_primitives::trie::{BranchNodeCompact, StoredNibbles};
|
||||
|
||||
/// A cursor over the account trie.
|
||||
@ -17,7 +15,7 @@ impl<C> AccountTrieCursor<C> {
|
||||
|
||||
impl<'a, C> TrieCursor<StoredNibbles> for AccountTrieCursor<C>
|
||||
where
|
||||
C: DbCursorRO<'a, tables::AccountsTrie> + DbCursorRW<'a, tables::AccountsTrie>,
|
||||
C: DbCursorRO<'a, tables::AccountsTrie>,
|
||||
{
|
||||
fn seek_exact(
|
||||
&mut self,
|
||||
@ -30,12 +28,8 @@ where
|
||||
Ok(self.0.seek(key)?.map(|value| (value.0.inner.to_vec(), value.1)))
|
||||
}
|
||||
|
||||
fn upsert(&mut self, key: StoredNibbles, value: BranchNodeCompact) -> Result<(), Error> {
|
||||
self.0.upsert(key, value)
|
||||
}
|
||||
|
||||
fn delete_current(&mut self) -> Result<(), Error> {
|
||||
self.0.delete_current()
|
||||
fn current(&mut self) -> Result<Option<TrieKey>, Error> {
|
||||
Ok(self.0.current()?.map(|(k, _)| TrieKey::AccountNode(k)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use super::TrieCursor;
|
||||
use crate::updates::TrieKey;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
tables, Error,
|
||||
};
|
||||
use reth_primitives::{
|
||||
trie::{BranchNodeCompact, StorageTrieEntry, StoredNibblesSubKey},
|
||||
trie::{BranchNodeCompact, StoredNibblesSubKey},
|
||||
H256,
|
||||
};
|
||||
|
||||
@ -24,10 +25,7 @@ impl<C> StorageTrieCursor<C> {
|
||||
|
||||
impl<'a, C> TrieCursor<StoredNibblesSubKey> for StorageTrieCursor<C>
|
||||
where
|
||||
C: DbDupCursorRO<'a, tables::StoragesTrie>
|
||||
+ DbDupCursorRW<'a, tables::StoragesTrie>
|
||||
+ DbCursorRO<'a, tables::StoragesTrie>
|
||||
+ DbCursorRW<'a, tables::StoragesTrie>,
|
||||
C: DbDupCursorRO<'a, tables::StoragesTrie> + DbCursorRO<'a, tables::StoragesTrie>,
|
||||
{
|
||||
fn seek_exact(
|
||||
&mut self,
|
||||
@ -50,28 +48,18 @@ where
|
||||
.map(|value| (value.nibbles.inner.to_vec(), value.node)))
|
||||
}
|
||||
|
||||
fn upsert(&mut self, key: StoredNibblesSubKey, value: BranchNodeCompact) -> Result<(), Error> {
|
||||
if let Some(entry) = self.cursor.seek_by_key_subkey(self.hashed_address, key.clone())? {
|
||||
// "seek exact"
|
||||
if entry.nibbles == key {
|
||||
self.cursor.delete_current()?;
|
||||
}
|
||||
}
|
||||
|
||||
self.cursor.upsert(self.hashed_address, StorageTrieEntry { nibbles: key, node: value })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_current(&mut self) -> Result<(), Error> {
|
||||
self.cursor.delete_current()
|
||||
fn current(&mut self) -> Result<Option<TrieKey>, Error> {
|
||||
Ok(self.cursor.current()?.map(|(k, v)| TrieKey::StorageNode(k, v.nibbles)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use reth_db::{mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut};
|
||||
use reth_primitives::trie::BranchNodeCompact;
|
||||
use reth_db::{
|
||||
cursor::DbCursorRW, mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut,
|
||||
};
|
||||
use reth_primitives::trie::{BranchNodeCompact, StorageTrieEntry};
|
||||
use reth_provider::Transaction;
|
||||
|
||||
// tests that upsert and seek match on the storagetrie cursor
|
||||
@ -79,14 +67,20 @@ mod tests {
|
||||
fn test_storage_cursor_abstraction() {
|
||||
let db = create_test_rw_db();
|
||||
let tx = Transaction::new(db.as_ref()).unwrap();
|
||||
let cursor = tx.cursor_dup_write::<tables::StoragesTrie>().unwrap();
|
||||
|
||||
let mut cursor = StorageTrieCursor::new(cursor, H256::random());
|
||||
let mut cursor = tx.cursor_dup_write::<tables::StoragesTrie>().unwrap();
|
||||
|
||||
let hashed_address = H256::random();
|
||||
let key = vec![0x2, 0x3];
|
||||
let value = BranchNodeCompact::new(1, 1, 1, vec![H256::random()], None);
|
||||
|
||||
cursor.upsert(key.clone().into(), value.clone()).unwrap();
|
||||
assert_eq!(cursor.seek(key.into()).unwrap().unwrap().1, value);
|
||||
cursor
|
||||
.upsert(
|
||||
hashed_address,
|
||||
StorageTrieEntry { nibbles: key.clone().into(), node: value.clone() },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut cursor = StorageTrieCursor::new(cursor, hashed_address);
|
||||
assert_eq!(cursor.seek(key.clone().into()).unwrap().unwrap().1, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use crate::{nodes::CHILD_INDEX_RANGE, Nibbles};
|
||||
use reth_primitives::{trie::BranchNodeCompact, H256};
|
||||
use reth_primitives::{
|
||||
trie::{BranchNodeCompact, StoredSubNode},
|
||||
H256,
|
||||
};
|
||||
|
||||
/// Cursor for iterating over a subtrie.
|
||||
#[derive(Clone)]
|
||||
@ -31,6 +34,23 @@ impl std::fmt::Debug for CursorSubNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredSubNode> for CursorSubNode {
|
||||
fn from(value: StoredSubNode) -> Self {
|
||||
let nibble = match value.nibble {
|
||||
Some(n) => n as i8,
|
||||
None => -1,
|
||||
};
|
||||
Self { key: Nibbles::from(value.key), nibble, node: value.node }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorSubNode> for StoredSubNode {
|
||||
fn from(value: CursorSubNode) -> Self {
|
||||
let nibble = if value.nibble >= 0 { Some(value.nibble as u8) } else { None };
|
||||
Self { key: value.key.hex_data, nibble, node: value.node }
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorSubNode {
|
||||
/// Creates a new `CursorSubNode` from a key and an optional node.
|
||||
pub fn new(key: Nibbles, node: Option<BranchNodeCompact>) -> Self {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::updates::TrieKey;
|
||||
use reth_db::{table::Key, Error};
|
||||
use reth_primitives::trie::BranchNodeCompact;
|
||||
|
||||
@ -9,9 +10,6 @@ pub trait TrieCursor<K: Key> {
|
||||
/// Move the cursor to the key and return a value matching of greater than the key.
|
||||
fn seek(&mut self, key: K) -> Result<Option<(Vec<u8>, BranchNodeCompact)>, Error>;
|
||||
|
||||
/// Upsert the key/value pair.
|
||||
fn upsert(&mut self, key: K, value: BranchNodeCompact) -> Result<(), Error>;
|
||||
|
||||
/// Delete the key/value pair at the current cursor position.
|
||||
fn delete_current(&mut self) -> Result<(), Error>;
|
||||
/// Get the current entry.
|
||||
fn current(&mut self) -> Result<Option<TrieKey>, Error>;
|
||||
}
|
||||
|
||||
@ -5,13 +5,10 @@ use crate::{
|
||||
use reth_primitives::{
|
||||
keccak256,
|
||||
proofs::EMPTY_ROOT,
|
||||
trie::{BranchNodeCompact, TrieMask},
|
||||
trie::{BranchNodeCompact, HashBuilderState, HashBuilderValue, TrieMask},
|
||||
H256,
|
||||
};
|
||||
use std::{fmt::Debug, sync::mpsc};
|
||||
|
||||
mod value;
|
||||
use value::HashBuilderValue;
|
||||
use std::{collections::BTreeMap, fmt::Debug, sync::mpsc};
|
||||
|
||||
/// A type alias for a sender of branch nodes.
|
||||
/// Branch nodes are sent by the Hash Builder to be stored in the database.
|
||||
@ -40,7 +37,7 @@ pub type BranchNodeSender = mpsc::Sender<(Nibbles, BranchNodeCompact)>;
|
||||
/// up, combining the hashes of child nodes and ultimately generating the root hash. The root hash
|
||||
/// can then be used to verify the integrity and authenticity of the trie's data by constructing and
|
||||
/// verifying Merkle proofs.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HashBuilder {
|
||||
key: Nibbles,
|
||||
stack: Vec<Vec<u8>>,
|
||||
@ -52,19 +49,66 @@ pub struct HashBuilder {
|
||||
|
||||
stored_in_database: bool,
|
||||
|
||||
branch_node_sender: Option<BranchNodeSender>,
|
||||
updated_branch_nodes: Option<BTreeMap<Nibbles, BranchNodeCompact>>,
|
||||
}
|
||||
|
||||
impl From<HashBuilderState> for HashBuilder {
|
||||
fn from(state: HashBuilderState) -> Self {
|
||||
Self {
|
||||
key: Nibbles::from(state.key),
|
||||
stack: state.stack,
|
||||
value: state.value,
|
||||
groups: state.groups,
|
||||
tree_masks: state.tree_masks,
|
||||
hash_masks: state.hash_masks,
|
||||
stored_in_database: state.stored_in_database,
|
||||
updated_branch_nodes: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashBuilder> for HashBuilderState {
|
||||
fn from(state: HashBuilder) -> Self {
|
||||
Self {
|
||||
key: state.key.hex_data,
|
||||
stack: state.stack,
|
||||
value: state.value,
|
||||
groups: state.groups,
|
||||
tree_masks: state.tree_masks,
|
||||
hash_masks: state.hash_masks,
|
||||
stored_in_database: state.stored_in_database,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashBuilder {
|
||||
/// Creates a new instance of the Hash Builder.
|
||||
pub fn new(store_tx: Option<BranchNodeSender>) -> Self {
|
||||
Self { branch_node_sender: store_tx, ..Default::default() }
|
||||
/// Enables the Hash Builder to store updated branch nodes.
|
||||
///
|
||||
/// Call [HashBuilder::split] to get the updates to branch nodes.
|
||||
pub fn with_updates(mut self, retain_updates: bool) -> Self {
|
||||
self.set_updates(retain_updates);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a branch node sender on the Hash Builder instance.
|
||||
pub fn with_branch_node_sender(mut self, tx: BranchNodeSender) -> Self {
|
||||
self.branch_node_sender = Some(tx);
|
||||
self
|
||||
/// Enables the Hash Builder to store updated branch nodes.
|
||||
///
|
||||
/// Call [HashBuilder::split] to get the updates to branch nodes.
|
||||
pub fn set_updates(&mut self, retain_updates: bool) {
|
||||
if retain_updates {
|
||||
self.updated_branch_nodes = Some(BTreeMap::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits the [HashBuilder] into a [HashBuilder] and hash builder updates.
|
||||
pub fn split(mut self) -> (Self, BTreeMap<Nibbles, BranchNodeCompact>) {
|
||||
let updates = self.updated_branch_nodes.take();
|
||||
(self, updates.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// The number of total updates accrued.
|
||||
/// Returns `0` if [Self::with_updates] was not called.
|
||||
pub fn updates_len(&self) -> usize {
|
||||
self.updated_branch_nodes.as_ref().map(|u| u.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Print the current stack of the Hash Builder.
|
||||
@ -326,8 +370,8 @@ impl HashBuilder {
|
||||
// other side of the HashBuilder
|
||||
tracing::debug!(target: "trie::hash_builder", node = ?n, "intermediate node");
|
||||
let common_prefix = current.slice(0, len);
|
||||
if let Some(tx) = &self.branch_node_sender {
|
||||
let _ = tx.send((common_prefix, n));
|
||||
if let Some(nodes) = self.updated_branch_nodes.as_mut() {
|
||||
nodes.insert(common_prefix, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,8 +473,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generates_branch_node() {
|
||||
let (sender, recv) = mpsc::channel();
|
||||
let mut hb = HashBuilder::new(Some(sender));
|
||||
let mut hb = HashBuilder::default().with_updates(true);
|
||||
|
||||
// We have 1 branch node update to be stored at 0x01, indicated by the first nibble.
|
||||
// That branch root node has 2 branch node children present at 0x1 and 0x2.
|
||||
@ -477,11 +520,9 @@ mod tests {
|
||||
hb.add_leaf(nibbles, val.as_ref());
|
||||
});
|
||||
let root = hb.root();
|
||||
drop(hb);
|
||||
|
||||
let updates = recv.iter().collect::<Vec<_>>();
|
||||
let (_, updates) = hb.split();
|
||||
|
||||
let updates = updates.iter().cloned().collect::<BTreeMap<_, _>>();
|
||||
let update = updates.get(&Nibbles::from(hex!("01").as_slice())).unwrap();
|
||||
assert_eq!(update.state_mask, TrieMask::new(0b1111)); // 1st nibble: 0, 1, 2, 3
|
||||
assert_eq!(update.tree_mask, TrieMask::new(0));
|
||||
@ -1,40 +0,0 @@
|
||||
use reth_primitives::H256;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum HashBuilderValue {
|
||||
Bytes(Vec<u8>),
|
||||
Hash(H256),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for HashBuilderValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Bytes(bytes) => write!(f, "Bytes({:?})", hex::encode(bytes)),
|
||||
Self::Hash(hash) => write!(f, "Hash({:?})", hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for HashBuilderValue {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self::Bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for HashBuilderValue {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self::Bytes(value.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<H256> for HashBuilderValue {
|
||||
fn from(value: H256) -> Self {
|
||||
Self::Hash(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HashBuilderValue {
|
||||
fn default() -> Self {
|
||||
Self::Bytes(vec![])
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,14 @@ pub use errors::{StateRootError, StorageRootError};
|
||||
|
||||
/// The implementation of the Merkle Patricia Trie.
|
||||
mod trie;
|
||||
pub use trie::{BranchNodeUpdate, BranchNodeUpdateSender, StateRoot, StorageRoot};
|
||||
pub use trie::{StateRoot, StorageRoot};
|
||||
|
||||
/// Buffer for trie updates.
|
||||
pub mod updates;
|
||||
|
||||
/// Utilities for state root checkpoint progress.
|
||||
mod progress;
|
||||
pub use progress::{IntermediateStateRootState, StateRootProgress};
|
||||
|
||||
/// Collection of trie-related test utilities.
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
|
||||
@ -11,7 +11,7 @@ use reth_db::{
|
||||
use reth_primitives::{keccak256, BlockNumber, StorageEntry, H256};
|
||||
use std::{collections::HashMap, ops::RangeInclusive};
|
||||
|
||||
/// A wrapper around a database transaction that loads prefix sets within a given transition range.
|
||||
/// A wrapper around a database transaction that loads prefix sets within a given block range.
|
||||
#[derive(Deref)]
|
||||
pub struct PrefixSetLoader<'a, TX>(&'a TX);
|
||||
|
||||
@ -26,7 +26,7 @@ impl<'a, 'b, TX> PrefixSetLoader<'a, TX>
|
||||
where
|
||||
TX: DbTx<'b>,
|
||||
{
|
||||
/// Load all account and storage changes for the given transition id range.
|
||||
/// Load all account and storage changes for the given block range.
|
||||
pub fn load(
|
||||
self,
|
||||
range: RangeInclusive<BlockNumber>,
|
||||
|
||||
47
crates/trie/src/progress.rs
Normal file
47
crates/trie/src/progress.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::{cursor::CursorSubNode, hash_builder::HashBuilder, updates::TrieUpdates, Nibbles};
|
||||
use reth_primitives::{trie::StoredSubNode, MerkleCheckpoint, H256};
|
||||
|
||||
/// The progress of the state root computation.
|
||||
#[derive(Debug)]
|
||||
pub enum StateRootProgress {
|
||||
/// The complete state root computation with updates and computed root.
|
||||
Complete(H256, TrieUpdates),
|
||||
/// The intermediate progress of state root computation.
|
||||
/// Contains the walker stack, the hash builder and the trie updates.
|
||||
Progress(IntermediateStateRootState, TrieUpdates),
|
||||
}
|
||||
|
||||
/// The intermediate state of the state root computation.
|
||||
#[derive(Debug)]
|
||||
pub struct IntermediateStateRootState {
|
||||
/// Previously constructed hash builder.
|
||||
pub hash_builder: HashBuilder,
|
||||
/// Previously recorded walker stack.
|
||||
pub walker_stack: Vec<CursorSubNode>,
|
||||
/// The last hashed account key processed.
|
||||
pub last_account_key: H256,
|
||||
/// The last walker key processed.
|
||||
pub last_walker_key: Nibbles,
|
||||
}
|
||||
|
||||
impl From<IntermediateStateRootState> for MerkleCheckpoint {
|
||||
fn from(value: IntermediateStateRootState) -> Self {
|
||||
Self {
|
||||
last_account_key: value.last_account_key,
|
||||
last_walker_key: value.last_walker_key.hex_data,
|
||||
walker_stack: value.walker_stack.into_iter().map(StoredSubNode::from).collect(),
|
||||
state: value.hash_builder.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MerkleCheckpoint> for IntermediateStateRootState {
|
||||
fn from(value: MerkleCheckpoint) -> Self {
|
||||
Self {
|
||||
hash_builder: HashBuilder::from(value.state),
|
||||
walker_stack: value.walker_stack.into_iter().map(CursorSubNode::from).collect(),
|
||||
last_account_key: value.last_account_key,
|
||||
last_walker_key: Nibbles::from(value.last_walker_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
151
crates/trie/src/updates.rs
Normal file
151
crates/trie/src/updates.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crate::Nibbles;
|
||||
use derive_more::Deref;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
|
||||
tables,
|
||||
transaction::{DbTx, DbTxMut},
|
||||
};
|
||||
use reth_primitives::{
|
||||
trie::{BranchNodeCompact, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey},
|
||||
H256,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// The key of a trie node.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TrieKey {
|
||||
/// A node in the account trie.
|
||||
AccountNode(StoredNibbles),
|
||||
/// A node in the storage trie.
|
||||
StorageNode(H256, StoredNibblesSubKey),
|
||||
/// Storage trie of an account.
|
||||
StorageTrie(H256),
|
||||
}
|
||||
|
||||
/// The operation to perform on the trie.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TrieOp {
|
||||
/// Delete the node entry.
|
||||
Delete,
|
||||
/// Update the node entry with the provided value.
|
||||
Update(BranchNodeCompact),
|
||||
}
|
||||
|
||||
impl TrieOp {
|
||||
/// Returns `true` if the operation is an update.
|
||||
pub fn is_update(&self) -> bool {
|
||||
matches!(self, TrieOp::Update(..))
|
||||
}
|
||||
}
|
||||
|
||||
/// The aggregation of trie updates.
|
||||
#[derive(Debug, Default, Clone, Deref)]
|
||||
pub struct TrieUpdates {
|
||||
trie_operations: BTreeMap<TrieKey, TrieOp>,
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[(TrieKey, TrieOp); N]> for TrieUpdates {
|
||||
fn from(value: [(TrieKey, TrieOp); N]) -> Self {
|
||||
Self { trie_operations: BTreeMap::from(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TrieUpdates {
|
||||
/// Schedule a delete operation on a trie key.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the key already exists and the operation is an update.
|
||||
pub fn schedule_delete(&mut self, key: TrieKey) {
|
||||
let existing = self.trie_operations.insert(key, TrieOp::Delete);
|
||||
if let Some(op) = existing {
|
||||
assert!(!op.is_update(), "Tried to delete a node that was already updated");
|
||||
}
|
||||
}
|
||||
|
||||
/// Append the updates to the current updates.
|
||||
pub fn append(&mut self, other: &mut Self) {
|
||||
self.trie_operations.append(&mut other.trie_operations);
|
||||
}
|
||||
|
||||
/// Extend the updates with trie updates.
|
||||
pub fn extend(&mut self, updates: impl Iterator<Item = (TrieKey, TrieOp)>) {
|
||||
self.trie_operations.extend(updates);
|
||||
}
|
||||
|
||||
/// Extend the updates with account trie updates.
|
||||
pub fn extend_with_account_updates(&mut self, updates: BTreeMap<Nibbles, BranchNodeCompact>) {
|
||||
self.extend(updates.into_iter().map(|(nibbles, node)| {
|
||||
(TrieKey::AccountNode(nibbles.hex_data.into()), TrieOp::Update(node))
|
||||
}));
|
||||
}
|
||||
|
||||
/// Extend the updates with storage trie updates.
|
||||
pub fn extend_with_storage_updates(
|
||||
&mut self,
|
||||
hashed_address: H256,
|
||||
updates: BTreeMap<Nibbles, BranchNodeCompact>,
|
||||
) {
|
||||
self.extend(updates.into_iter().map(|(nibbles, node)| {
|
||||
(TrieKey::StorageNode(hashed_address, nibbles.hex_data.into()), TrieOp::Update(node))
|
||||
}));
|
||||
}
|
||||
|
||||
/// Flush updates all aggregated updates to the database.
|
||||
pub fn flush<'a, 'tx, TX>(self, tx: &'a TX) -> Result<(), reth_db::Error>
|
||||
where
|
||||
TX: DbTx<'tx> + DbTxMut<'tx>,
|
||||
{
|
||||
if self.trie_operations.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let mut account_trie_cursor = tx.cursor_write::<tables::AccountsTrie>()?;
|
||||
let mut storage_trie_cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;
|
||||
|
||||
for (key, operation) in self.trie_operations {
|
||||
match key {
|
||||
TrieKey::AccountNode(nibbles) => match operation {
|
||||
TrieOp::Delete => {
|
||||
if account_trie_cursor.seek_exact(nibbles)?.is_some() {
|
||||
account_trie_cursor.delete_current()?;
|
||||
}
|
||||
}
|
||||
TrieOp::Update(node) => {
|
||||
if !nibbles.inner.is_empty() {
|
||||
account_trie_cursor.upsert(nibbles, node)?;
|
||||
}
|
||||
}
|
||||
},
|
||||
TrieKey::StorageTrie(hashed_address) => match operation {
|
||||
TrieOp::Delete => {
|
||||
if storage_trie_cursor.seek_exact(hashed_address)?.is_some() {
|
||||
storage_trie_cursor.delete_current_duplicates()?;
|
||||
}
|
||||
}
|
||||
TrieOp::Update(..) => unreachable!("Cannot update full storage trie."),
|
||||
},
|
||||
TrieKey::StorageNode(hashed_address, nibbles) => {
|
||||
if !nibbles.inner.is_empty() {
|
||||
// Delete the old entry if it exists.
|
||||
if storage_trie_cursor
|
||||
.seek_by_key_subkey(hashed_address, nibbles.clone())?
|
||||
.filter(|e| e.nibbles == nibbles)
|
||||
.is_some()
|
||||
{
|
||||
storage_trie_cursor.delete_current()?;
|
||||
}
|
||||
|
||||
// The operation is an update, insert new entry.
|
||||
if let TrieOp::Update(node) = operation {
|
||||
storage_trie_cursor
|
||||
.upsert(hashed_address, StorageTrieEntry { nibbles, node })?;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
cursor::{CursorSubNode, TrieCursor},
|
||||
prefix_set::PrefixSet,
|
||||
updates::TrieUpdates,
|
||||
Nibbles,
|
||||
};
|
||||
use reth_db::{table::Key, Error};
|
||||
@ -20,6 +21,8 @@ pub struct TrieWalker<'a, K, C> {
|
||||
pub can_skip_current_node: bool,
|
||||
/// A `PrefixSet` representing the changes to be applied to the trie.
|
||||
pub changes: PrefixSet,
|
||||
/// The trie updates to be applied to the trie.
|
||||
trie_updates: Option<TrieUpdates>,
|
||||
__phantom: PhantomData<K>,
|
||||
}
|
||||
|
||||
@ -30,8 +33,9 @@ impl<'a, K: Key + From<Vec<u8>>, C: TrieCursor<K>> TrieWalker<'a, K, C> {
|
||||
let mut this = Self {
|
||||
cursor,
|
||||
changes,
|
||||
can_skip_current_node: false,
|
||||
stack: vec![CursorSubNode::default()],
|
||||
can_skip_current_node: false,
|
||||
trie_updates: None,
|
||||
__phantom: PhantomData::default(),
|
||||
};
|
||||
|
||||
@ -45,6 +49,39 @@ impl<'a, K: Key + From<Vec<u8>>, C: TrieCursor<K>> TrieWalker<'a, K, C> {
|
||||
this
|
||||
}
|
||||
|
||||
/// Constructs a new TrieWalker from existing stack and a cursor.
|
||||
pub fn from_stack(cursor: &'a mut C, stack: Vec<CursorSubNode>, changes: PrefixSet) -> Self {
|
||||
let mut this = Self {
|
||||
cursor,
|
||||
changes,
|
||||
stack,
|
||||
can_skip_current_node: false,
|
||||
trie_updates: None,
|
||||
__phantom: PhantomData::default(),
|
||||
};
|
||||
this.update_skip_node();
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets the flag whether the trie updates should be stored.
|
||||
pub fn with_updates(mut self, retain_updates: bool) -> Self {
|
||||
self.set_updates(retain_updates);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flag whether the trie updates should be stored.
|
||||
pub fn set_updates(&mut self, retain_updates: bool) {
|
||||
if retain_updates {
|
||||
self.trie_updates = Some(TrieUpdates::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Split the walker into stack and trie updates.
|
||||
pub fn split(mut self) -> (Vec<CursorSubNode>, TrieUpdates) {
|
||||
let trie_updates = self.trie_updates.take();
|
||||
(self.stack, trie_updates.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Prints the current stack of trie nodes.
|
||||
pub fn print_stack(&self) {
|
||||
println!("====================== STACK ======================");
|
||||
@ -54,6 +91,11 @@ impl<'a, K: Key + From<Vec<u8>>, C: TrieCursor<K>> TrieWalker<'a, K, C> {
|
||||
println!("====================== END STACK ======================\n");
|
||||
}
|
||||
|
||||
/// The current length of the trie updates.
|
||||
pub fn updates_len(&self) -> usize {
|
||||
self.trie_updates.as_ref().map(|u| u.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Advances the walker to the next trie node and updates the skip node flag.
|
||||
///
|
||||
/// # Returns
|
||||
@ -121,7 +163,9 @@ impl<'a, K: Key + From<Vec<u8>>, C: TrieCursor<K>> TrieWalker<'a, K, C> {
|
||||
// Delete the current node if it's included in the prefix set or it doesn't contain the root
|
||||
// hash.
|
||||
if !self.can_skip_current_node || nibble != -1 {
|
||||
self.cursor.delete_current()?;
|
||||
if let Some((updates, key)) = self.trie_updates.as_mut().zip(self.cursor.current()?) {
|
||||
updates.schedule_delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -209,7 +253,10 @@ impl<'a, K: Key + From<Vec<u8>>, C: TrieCursor<K>> TrieWalker<'a, K, C> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cursor::{AccountTrieCursor, StorageTrieCursor};
|
||||
use reth_db::{mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut};
|
||||
use reth_db::{
|
||||
cursor::DbCursorRW, mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut,
|
||||
};
|
||||
use reth_primitives::trie::StorageTrieEntry;
|
||||
use reth_provider::Transaction;
|
||||
|
||||
#[test]
|
||||
@ -237,26 +284,32 @@ mod tests {
|
||||
|
||||
let db = create_test_rw_db();
|
||||
let tx = Transaction::new(db.as_ref()).unwrap();
|
||||
let account_trie =
|
||||
AccountTrieCursor::new(tx.cursor_write::<tables::AccountsTrie>().unwrap());
|
||||
test_cursor(account_trie, &inputs, &expected);
|
||||
let mut account_cursor = tx.cursor_write::<tables::AccountsTrie>().unwrap();
|
||||
for (k, v) in &inputs {
|
||||
account_cursor.upsert(k.clone().into(), v.clone()).unwrap();
|
||||
}
|
||||
let account_trie = AccountTrieCursor::new(account_cursor);
|
||||
test_cursor(account_trie, &expected);
|
||||
|
||||
let storage_trie = StorageTrieCursor::new(
|
||||
tx.cursor_dup_write::<tables::StoragesTrie>().unwrap(),
|
||||
H256::random(),
|
||||
);
|
||||
test_cursor(storage_trie, &inputs, &expected);
|
||||
let hashed_address = H256::random();
|
||||
let mut storage_cursor = tx.cursor_dup_write::<tables::StoragesTrie>().unwrap();
|
||||
for (k, v) in &inputs {
|
||||
storage_cursor
|
||||
.upsert(
|
||||
hashed_address,
|
||||
StorageTrieEntry { nibbles: k.clone().into(), node: v.clone() },
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let storage_trie = StorageTrieCursor::new(storage_cursor, hashed_address);
|
||||
test_cursor(storage_trie, &expected);
|
||||
}
|
||||
|
||||
fn test_cursor<K, T>(mut trie: T, inputs: &[(Vec<u8>, BranchNodeCompact)], expected: &[Vec<u8>])
|
||||
fn test_cursor<K, T>(mut trie: T, expected: &[Vec<u8>])
|
||||
where
|
||||
K: Key + From<Vec<u8>>,
|
||||
T: TrieCursor<K>,
|
||||
{
|
||||
for (k, v) in inputs {
|
||||
trie.upsert(k.clone().into(), v.clone()).unwrap();
|
||||
}
|
||||
|
||||
let mut walker = TrieWalker::new(&mut trie, Default::default());
|
||||
assert!(walker.key().unwrap().is_empty());
|
||||
|
||||
@ -275,11 +328,7 @@ mod tests {
|
||||
fn cursor_rootnode_with_changesets() {
|
||||
let db = create_test_rw_db();
|
||||
let tx = Transaction::new(db.as_ref()).unwrap();
|
||||
|
||||
let mut trie = StorageTrieCursor::new(
|
||||
tx.cursor_dup_write::<tables::StoragesTrie>().unwrap(),
|
||||
H256::random(),
|
||||
);
|
||||
let mut cursor = tx.cursor_dup_write::<tables::StoragesTrie>().unwrap();
|
||||
|
||||
let nodes = vec![
|
||||
(
|
||||
@ -306,10 +355,13 @@ mod tests {
|
||||
),
|
||||
];
|
||||
|
||||
let hashed_address = H256::random();
|
||||
for (k, v) in nodes {
|
||||
trie.upsert(k.into(), v).unwrap();
|
||||
cursor.upsert(hashed_address, StorageTrieEntry { nibbles: k.into(), node: v }).unwrap();
|
||||
}
|
||||
|
||||
let mut trie = StorageTrieCursor::new(cursor, hashed_address);
|
||||
|
||||
// No changes
|
||||
let mut cursor = TrieWalker::new(&mut trie, Default::default());
|
||||
assert_eq!(cursor.key(), Some(Nibbles::from(vec![]))); // root
|
||||
|
||||
Reference in New Issue
Block a user