Files
reth/crates/trie/db/src/state.rs
2024-09-16 11:48:04 +00:00

306 lines
12 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, PrefixSetLoader};
use alloy_primitives::{keccak256, Address, BlockNumber, B256, U256};
use reth_db::tables;
use reth_db_api::{
cursor::DbCursorRO,
models::{AccountBeforeTx, BlockNumberAddress},
transaction::DbTx,
};
use reth_execution_errors::StateRootError;
use reth_primitives::Account;
use reth_storage_errors::db::DatabaseError;
use reth_trie::{
hashed_cursor::HashedPostStateCursorFactory, trie_cursor::InMemoryTrieCursorFactory,
updates::TrieUpdates, HashedPostState, HashedStorage, StateRoot, StateRootProgress, TrieInput,
};
use std::{
collections::{hash_map, HashMap},
ops::RangeInclusive,
};
use tracing::debug;
/// Extends [`StateRoot`] with operations specific for working with a database transaction.
pub trait DatabaseStateRoot<'a, TX>: Sized {
/// Create a new [`StateRoot`] instance.
fn from_tx(tx: &'a TX) -> Self;
/// Given a block number range, identifies all the accounts and storage keys that
/// have changed.
///
/// # Returns
///
/// An instance of state root calculator with account and storage prefixes loaded.
fn incremental_root_calculator(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<Self, StateRootError>;
/// Computes the state root of the trie with the changed account and storage prefixes and
/// existing trie nodes.
///
/// # Returns
///
/// The updated state root.
fn incremental_root(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<B256, StateRootError>;
/// Computes the state root of the trie with the changed account and storage prefixes and
/// existing trie nodes collecting updates in the process.
///
/// Ignores the threshold.
///
/// # Returns
///
/// The updated state root and the trie updates.
fn incremental_root_with_updates(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<(B256, TrieUpdates), StateRootError>;
/// Computes the state root of the trie with the changed account and storage prefixes and
/// existing trie nodes collecting updates in the process.
///
/// # Returns
///
/// The intermediate progress of state root computation.
fn incremental_root_with_progress(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<StateRootProgress, StateRootError>;
/// Calculate the state root for this [`HashedPostState`].
/// Internally, this method retrieves prefixsets and uses them
/// to calculate incremental state root.
///
/// # Example
///
/// ```
/// use reth_db::test_utils::create_test_rw_db;
/// use reth_db_api::database::Database;
/// use reth_primitives::{Account, U256};
/// use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot};
/// use reth_trie_db::DatabaseStateRoot;
///
/// // Initialize the database
/// let db = create_test_rw_db();
///
/// // Initialize hashed post state
/// let mut hashed_state = HashedPostState::default();
/// hashed_state.accounts.insert(
/// [0x11; 32].into(),
/// Some(Account { nonce: 1, balance: U256::from(10), bytecode_hash: None }),
/// );
///
/// // Calculate the state root
/// let tx = db.tx().expect("failed to create transaction");
/// let state_root = StateRoot::overlay_root(&tx, hashed_state);
/// ```
///
/// # Returns
///
/// The state root for this [`HashedPostState`].
fn overlay_root(tx: &'a TX, post_state: HashedPostState) -> Result<B256, StateRootError>;
/// Calculates the state root for this [`HashedPostState`] and returns it alongside trie
/// updates. See [`Self::overlay_root`] for more info.
fn overlay_root_with_updates(
tx: &'a TX,
post_state: HashedPostState,
) -> Result<(B256, TrieUpdates), StateRootError>;
/// Calculates the state root for provided [`HashedPostState`] using cached intermediate nodes.
fn overlay_root_from_nodes(tx: &'a TX, input: TrieInput) -> Result<B256, StateRootError>;
/// Calculates the state root and trie updates for provided [`HashedPostState`] using
/// cached intermediate nodes.
fn overlay_root_from_nodes_with_updates(
tx: &'a TX,
input: TrieInput,
) -> Result<(B256, TrieUpdates), StateRootError>;
}
/// Extends [`HashedPostState`] with operations specific for working with a database transaction.
pub trait DatabaseHashedPostState<TX>: Sized {
/// Initializes [`HashedPostState`] from reverts. Iterates over state reverts from the specified
/// block up to the current tip and aggregates them into hashed state in reverse.
fn from_reverts(tx: &TX, from: BlockNumber) -> Result<Self, DatabaseError>;
}
impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX>
for StateRoot<DatabaseTrieCursorFactory<'a, TX>, DatabaseHashedCursorFactory<'a, TX>>
{
fn from_tx(tx: &'a TX) -> Self {
Self::new(DatabaseTrieCursorFactory::new(tx), DatabaseHashedCursorFactory::new(tx))
}
fn incremental_root_calculator(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<Self, StateRootError> {
let loaded_prefix_sets = PrefixSetLoader::new(tx).load(range)?;
Ok(Self::from_tx(tx).with_prefix_sets(loaded_prefix_sets))
}
fn incremental_root(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<B256, StateRootError> {
debug!(target: "trie::loader", ?range, "incremental state root");
Self::incremental_root_calculator(tx, range)?.root()
}
fn incremental_root_with_updates(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<(B256, TrieUpdates), StateRootError> {
debug!(target: "trie::loader", ?range, "incremental state root");
Self::incremental_root_calculator(tx, range)?.root_with_updates()
}
fn incremental_root_with_progress(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<StateRootProgress, StateRootError> {
debug!(target: "trie::loader", ?range, "incremental state root with progress");
Self::incremental_root_calculator(tx, range)?.root_with_progress()
}
fn overlay_root(tx: &'a TX, post_state: HashedPostState) -> Result<B256, StateRootError> {
let prefix_sets = post_state.construct_prefix_sets().freeze();
let state_sorted = post_state.into_sorted();
StateRoot::new(
DatabaseTrieCursorFactory::new(tx),
HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted),
)
.with_prefix_sets(prefix_sets)
.root()
}
fn overlay_root_with_updates(
tx: &'a TX,
post_state: HashedPostState,
) -> Result<(B256, TrieUpdates), StateRootError> {
let prefix_sets = post_state.construct_prefix_sets().freeze();
let state_sorted = post_state.into_sorted();
StateRoot::new(
DatabaseTrieCursorFactory::new(tx),
HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted),
)
.with_prefix_sets(prefix_sets)
.root_with_updates()
}
fn overlay_root_from_nodes(tx: &'a TX, input: TrieInput) -> Result<B256, StateRootError> {
let state_sorted = input.state.into_sorted();
let nodes_sorted = input.nodes.into_sorted();
StateRoot::new(
InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted),
HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted),
)
.with_prefix_sets(input.prefix_sets.freeze())
.root()
}
fn overlay_root_from_nodes_with_updates(
tx: &'a TX,
input: TrieInput,
) -> Result<(B256, TrieUpdates), StateRootError> {
let state_sorted = input.state.into_sorted();
let nodes_sorted = input.nodes.into_sorted();
StateRoot::new(
InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted),
HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted),
)
.with_prefix_sets(input.prefix_sets.freeze())
.root_with_updates()
}
}
impl<TX: DbTx> DatabaseHashedPostState<TX> for HashedPostState {
fn from_reverts(tx: &TX, from: BlockNumber) -> Result<Self, DatabaseError> {
// Iterate over account changesets and record value before first occurring account change.
let mut accounts = HashMap::<Address, Option<Account>>::default();
let mut account_changesets_cursor = tx.cursor_read::<tables::AccountChangeSets>()?;
for entry in account_changesets_cursor.walk_range(from..)? {
let (_, AccountBeforeTx { address, info }) = entry?;
if let hash_map::Entry::Vacant(entry) = accounts.entry(address) {
entry.insert(info);
}
}
// Iterate over storage changesets and record value before first occurring storage change.
let mut storages = HashMap::<Address, HashMap<B256, U256>>::default();
let mut storage_changesets_cursor = tx.cursor_read::<tables::StorageChangeSets>()?;
for entry in
storage_changesets_cursor.walk_range(BlockNumberAddress((from, Address::ZERO))..)?
{
let (BlockNumberAddress((_, address)), storage) = entry?;
let account_storage = storages.entry(address).or_default();
if let hash_map::Entry::Vacant(entry) = account_storage.entry(storage.key) {
entry.insert(storage.value);
}
}
let hashed_accounts = HashMap::from_iter(
accounts.into_iter().map(|(address, info)| (keccak256(address), info)),
);
let hashed_storages = HashMap::from_iter(storages.into_iter().map(|(address, storage)| {
(
keccak256(address),
HashedStorage::from_iter(
// 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.
false,
storage.into_iter().map(|(slot, value)| (keccak256(slot), value)),
),
)
}));
Ok(Self { accounts: hashed_accounts, storages: hashed_storages })
}
}
#[cfg(test)]
mod tests {
use super::*;
use reth_db::test_utils::create_test_rw_db;
use reth_db_api::database::Database;
use reth_primitives::{hex, revm_primitives::AccountInfo, Address, U256};
use revm::db::BundleState;
use std::collections::HashMap;
#[test]
fn from_bundle_state_with_rayon() {
let address1 = Address::with_last_byte(1);
let address2 = Address::with_last_byte(2);
let slot1 = U256::from(1015);
let slot2 = U256::from(2015);
let account1 = AccountInfo { nonce: 1, ..Default::default() };
let account2 = AccountInfo { nonce: 2, ..Default::default() };
let bundle_state = BundleState::builder(2..=2)
.state_present_account_info(address1, account1)
.state_present_account_info(address2, account2)
.state_storage(address1, HashMap::from([(slot1, (U256::ZERO, U256::from(10)))]))
.state_storage(address2, HashMap::from([(slot2, (U256::ZERO, U256::from(20)))]))
.build();
assert_eq!(bundle_state.reverts.len(), 1);
let post_state = HashedPostState::from_bundle_state(&bundle_state.state);
assert_eq!(post_state.accounts.len(), 2);
assert_eq!(post_state.storages.len(), 2);
let db = create_test_rw_db();
let tx = db.tx().expect("failed to create transaction");
assert_eq!(
StateRoot::overlay_root(&tx, post_state).unwrap(),
hex!("b464525710cafcf5d4044ac85b72c08b1e76231b8d91f288fe438cc41d8eaafd")
);
}
}