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_storage_errors::db::DatabaseError;
use reth_trie::{
hashed_cursor::HashedPostStateCursorFactory, trie_cursor::InMemoryTrieCursorFactory,
updates::TrieUpdates, HashedPostState, HashedStorage, StateRoot, StateRootProgress, TrieInput,
};
use std::{collections::HashMap, ops::RangeInclusive};
use tracing::debug;
pub trait DatabaseStateRoot<'a, TX>: Sized {
fn from_tx(tx: &'a TX) -> Self;
fn incremental_root_calculator(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<Self, StateRootError>;
fn incremental_root(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<B256, StateRootError>;
fn incremental_root_with_updates(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<(B256, TrieUpdates), StateRootError>;
fn incremental_root_with_progress(
tx: &'a TX,
range: RangeInclusive<BlockNumber>,
) -> Result<StateRootProgress, StateRootError>;
fn overlay_root(tx: &'a TX, post_state: HashedPostState) -> Result<B256, StateRootError>;
fn overlay_root_with_updates(
tx: &'a TX,
post_state: HashedPostState,
) -> Result<(B256, TrieUpdates), StateRootError>;
fn overlay_root_from_nodes(tx: &'a TX, input: TrieInput) -> Result<B256, StateRootError>;
fn overlay_root_from_nodes_with_updates(
tx: &'a TX,
input: TrieInput,
) -> Result<(B256, TrieUpdates), StateRootError>;
}
pub trait DatabaseHashedPostState<TX>: Sized {
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> {
let mut accounts = HashMap::new();
let mut account_changesets_cursor = tx.cursor_read::<tables::AccountChangeSets>()?;
for entry in account_changesets_cursor.walk_range(from..)? {
let (_, AccountBeforeTx { address, info }) = entry?;
accounts.entry(address).or_insert(info);
}
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();
account_storage.entry(storage.key).or_insert(storage.value);
}
let hashed_accounts =
accounts.into_iter().map(|(address, info)| (keccak256(address), info)).collect();
let hashed_storages = storages
.into_iter()
.map(|(address, storage)| {
(
keccak256(address),
HashedStorage::from_iter(
false,
storage.into_iter().map(|(slot, value)| (keccak256(slot), value)),
),
)
})
.collect();
Ok(Self { accounts: hashed_accounts, storages: hashed_storages })
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{hex, map::HashMap, Address, U256};
use reth_db::test_utils::create_test_rw_db;
use reth_db_api::database::Database;
use revm::{db::BundleState, primitives::AccountInfo};
#[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_iter([(slot1, (U256::ZERO, U256::from(10)))]))
.state_storage(address2, HashMap::from_iter([(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")
);
}
}