diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index 5b5ae3b49..bcbccac58 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -19,9 +19,9 @@ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::BlockHashOrNumber; use reth_provider::{ - AccountExtReader, ChainSpecProvider, HashingWriter, HeaderProvider, LatestStateProviderRef, - OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, - StaticFileProviderFactory, StorageReader, + writer::StorageWriter, AccountExtReader, ChainSpecProvider, HashingWriter, HeaderProvider, + LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, + StateWriter, StaticFileProviderFactory, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; @@ -168,7 +168,8 @@ impl Command { .try_seal_with_senders() .map_err(|_| BlockValidationError::SenderRecoveryError)?, )?; - execution_outcome.write_to_storage(&provider_rw, None, OriginalValuesKnown::No)?; + let mut storage_writer = StorageWriter::new(Some(&provider_rw), None); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::No)?; let storage_lists = provider_rw.changed_storages_with_range(block.number..=block.number)?; let storages = provider_rw.plain_state_storages(storage_lists)?; provider_rw.insert_storage_for_hashing(storages)?; diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index b6cad5fc3..d44ecfc9d 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -16,8 +16,8 @@ use reth_network_api::NetworkInfo; use reth_network_p2p::full_block::FullBlockClient; use reth_primitives::BlockHashOrNumber; use reth_provider::{ - BlockNumReader, BlockWriter, ChainSpecProvider, HeaderProvider, LatestStateProviderRef, - OriginalValuesKnown, ProviderError, ProviderFactory, StateWriter, + writer::StorageWriter, BlockNumReader, BlockWriter, ChainSpecProvider, HeaderProvider, + LatestStateProviderRef, OriginalValuesKnown, ProviderError, ProviderFactory, StateWriter, }; use reth_revm::database::StateProviderDatabase; use reth_stages::{ @@ -150,7 +150,10 @@ impl Command { ), )); executor.execute_and_verify_one((&sealed_block.clone().unseal(), td).into())?; - executor.finalize().write_to_storage(&provider_rw, None, OriginalValuesKnown::Yes)?; + let execution_outcome = executor.finalize(); + + let mut storage_writer = StorageWriter::new(Some(&provider_rw), None); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::Yes)?; let checkpoint = Some(StageCheckpoint::new( block_number.checked_sub(1).ok_or(eyre::eyre!("GenesisBlockHasNoParent"))?, diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index a52ace30b..55d6a75b2 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -78,16 +78,15 @@ impl PersistenceService { // Must be written after blocks because of the receipt lookup. let execution_outcome = block.execution_outcome().clone(); // TODO: do we provide a static file producer here? - execution_outcome.write_to_storage(&provider_rw, None, OriginalValuesKnown::No)?; + let mut storage_writer = StorageWriter::new(Some(&provider_rw), None); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::No)?; // insert hashes and intermediate merkle nodes { let trie_updates = block.trie_updates().clone(); let hashed_state = block.hashed_state(); - // TODO: use single storage writer in task when sf / db tasks are combined - let storage_writer = StorageWriter::new(Some(&provider_rw), None); storage_writer.write_hashed_state(&hashed_state.clone().into_sorted())?; - trie_updates.write_to_database(provider_rw.tx_ref())?; + storage_writer.write_trie_updates(&trie_updates)?; } // update history indices @@ -186,7 +185,7 @@ impl PersistenceService { let receipts_writer = provider.get_writer(first_block.number, StaticFileSegment::Receipts)?; - let storage_writer = StorageWriter::new(Some(&provider_rw), Some(receipts_writer)); + let mut storage_writer = StorageWriter::new(Some(&provider_rw), Some(receipts_writer)); let receipts_iter = blocks.iter().map(|block| { let receipts = block.execution_outcome().receipts().receipt_vec.clone(); debug_assert!(receipts.len() == 1); diff --git a/crates/optimism/cli/src/commands/import_receipts.rs b/crates/optimism/cli/src/commands/import_receipts.rs index fade01da4..f6b4a792c 100644 --- a/crates/optimism/cli/src/commands/import_receipts.rs +++ b/crates/optimism/cli/src/commands/import_receipts.rs @@ -16,8 +16,8 @@ use reth_node_core::version::SHORT_VERSION; use reth_optimism_primitives::bedrock_import::is_dup_tx; use reth_primitives::Receipts; use reth_provider::{ - OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, - StaticFileProviderFactory, StaticFileWriter, StatsReader, + writer::StorageWriter, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, + StateWriter, StaticFileProviderFactory, StaticFileWriter, StatsReader, }; use reth_stages::StageId; use reth_static_file_types::StaticFileSegment; @@ -140,7 +140,7 @@ where ); // We're reusing receipt writing code internal to - // `ExecutionOutcome::write_to_storage`, so we just use a default empty + // `StorageWriter::append_receipts_from_blocks`, so we just use a default empty // `BundleState`. let execution_outcome = ExecutionOutcome::new(Default::default(), receipts, first_block, Default::default()); @@ -149,11 +149,8 @@ where static_file_provider.get_writer(first_block, StaticFileSegment::Receipts)?; // finally, write the receipts - execution_outcome.write_to_storage( - &provider, - Some(static_file_producer), - OriginalValuesKnown::Yes, - )?; + let mut storage_writer = StorageWriter::new(Some(&provider), Some(static_file_producer)); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::Yes)?; } provider.commit()?; diff --git a/crates/stages/stages/benches/setup/mod.rs b/crates/stages/stages/benches/setup/mod.rs index aa082c707..e5ec504ec 100644 --- a/crates/stages/stages/benches/setup/mod.rs +++ b/crates/stages/stages/benches/setup/mod.rs @@ -6,6 +6,7 @@ use reth_db_api::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{Account, Address, SealedBlock, B256, U256}; +use reth_provider::TrieWriter; use reth_stages::{ stages::{AccountHashingStage, StorageHashingStage}, test_utils::{StorageKind, TestStageDB}, @@ -139,12 +140,10 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> TestStageDB { let offset = transitions.len() as u64; + let provider_rw = db.factory.provider_rw().unwrap(); db.insert_changesets(transitions, None).unwrap(); - db.commit(|tx| { - updates.write_to_database(tx)?; - Ok(()) - }) - .unwrap(); + provider_rw.write_trie_updates(&updates).unwrap(); + provider_rw.commit().unwrap(); let (transitions, final_state) = random_changeset_range( &mut rng, diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index f526a030a..43eaf45d5 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -10,6 +10,7 @@ use reth_primitives::{BlockNumber, Header, StaticFileSegment}; use reth_primitives_traits::format_gas_throughput; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, + writer::StorageWriter, BlockReader, DatabaseProviderRW, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, StateWriter, StatsReader, TransactionVariant, }; @@ -358,8 +359,11 @@ where } let time = Instant::now(); + // write output - state.write_to_storage(provider, static_file_producer, OriginalValuesKnown::Yes)?; + let mut writer = StorageWriter::new(Some(provider), static_file_producer); + writer.write_to_storage(state, OriginalValuesKnown::Yes)?; + let db_write_duration = time.elapsed(); debug!( target: "sync::stages::execution", diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 0d9130f39..f85ef565f 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -7,8 +7,8 @@ use reth_db_api::{ }; use reth_primitives::{BlockNumber, GotExpected, SealedHeader, B256}; use reth_provider::{ - DatabaseProviderRW, HeaderProvider, ProviderError, StageCheckpointReader, - StageCheckpointWriter, StatsReader, + writer::StorageWriter, DatabaseProviderRW, HeaderProvider, ProviderError, + StageCheckpointReader, StageCheckpointWriter, StatsReader, }; use reth_stages_api::{ BlockErrorKind, EntitiesCheckpoint, ExecInput, ExecOutput, MerkleCheckpoint, Stage, @@ -218,7 +218,8 @@ impl Stage for MerkleStage { })?; match progress { StateRootProgress::Progress(state, hashed_entries_walked, updates) => { - updates.write_to_database(tx)?; + let writer = StorageWriter::new(Some(provider), None); + writer.write_trie_updates(&updates)?; let checkpoint = MerkleCheckpoint::new( to_block, @@ -238,7 +239,8 @@ impl Stage for MerkleStage { }) } StateRootProgress::Complete(root, hashed_entries_walked, updates) => { - updates.write_to_database(tx)?; + let writer = StorageWriter::new(Some(provider), None); + writer.write_trie_updates(&updates)?; entities_checkpoint.processed += hashed_entries_walked as u64; @@ -253,7 +255,8 @@ impl Stage for MerkleStage { error!(target: "sync::stages::merkle", %e, ?current_block_number, ?to_block, "Incremental state root failed! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); StageError::Fatal(Box::new(e)) })?; - updates.write_to_database(provider.tx_ref())?; + let writer = StorageWriter::new(Some(provider), None); + writer.write_trie_updates(&updates)?; let total_hashed_entries = (provider.count_entries::()? + provider.count_entries::()?) @@ -326,7 +329,8 @@ impl Stage for MerkleStage { validate_state_root(block_root, target.seal_slow(), input.unwind_to)?; // Validation passed, apply unwind changes to the database. - updates.write_to_database(provider.tx_ref())?; + let writer = StorageWriter::new(Some(provider), None); + writer.write_trie_updates(&updates)?; // TODO(alexey): update entities checkpoint } else { diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 47c93a371..63a1760ea 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -11,12 +11,13 @@ use reth_primitives::{ Account, Address, Bytecode, Receipts, StaticFileSegment, StorageEntry, B256, U256, }; use reth_provider::{ - bundle_state::{BundleStateInit, RevertsInit}, errors::provider::ProviderResult, providers::{StaticFileProvider, StaticFileWriter}, - BlockHashReader, BlockNumReader, ChainSpecProvider, DatabaseProviderRW, ExecutionOutcome, - HashingWriter, HistoryWriter, OriginalValuesKnown, ProviderError, ProviderFactory, - StageCheckpointWriter, StateWriter, StaticFileProviderFactory, + writer::StorageWriter, + BlockHashReader, BlockNumReader, BundleStateInit, ChainSpecProvider, DatabaseProviderRW, + ExecutionOutcome, HashingWriter, HistoryWriter, OriginalValuesKnown, ProviderError, + ProviderFactory, RevertsInit, StageCheckpointWriter, StateWriter, StaticFileProviderFactory, + TrieWriter, }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_trie::{IntermediateStateRootState, StateRoot as StateRootComputer, StateRootProgress}; @@ -202,7 +203,8 @@ pub fn insert_state<'a, 'b, DB: Database>( Vec::new(), ); - execution_outcome.write_to_storage(provider, None, OriginalValuesKnown::Yes)?; + let mut storage_writer = StorageWriter::new(Some(provider), None); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::Yes)?; trace!(target: "reth::cli", "Inserted state"); @@ -462,7 +464,7 @@ fn compute_state_root(provider: &DatabaseProviderRW) -> eyre:: .root_with_progress()? { StateRootProgress::Progress(state, _, updates) => { - let updated_len = updates.write_to_database(tx)?; + let updated_len = provider.write_trie_updates(&updates)?; total_flushed_updates += updated_len; trace!(target: "reth::cli", @@ -482,7 +484,7 @@ fn compute_state_root(provider: &DatabaseProviderRW) -> eyre:: } } StateRootProgress::Complete(root, _, updates) => { - let updated_len = updates.write_to_database(tx)?; + let updated_len = provider.write_trie_updates(&updates)?; total_flushed_updates += updated_len; trace!(target: "reth::cli", diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index c7e172161..48058084e 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -75,9 +75,4 @@ rand.workspace = true [features] optimism = ["reth-primitives/optimism", "reth-execution-types/optimism"] serde = ["reth-execution-types/serde"] -test-utils = [ - "alloy-rlp", - "reth-db/test-utils", - "reth-nippy-jar/test-utils", - "reth-chain-state/test-utils" -] +test-utils = ["alloy-rlp", "reth-db/test-utils", "reth-nippy-jar/test-utils", "reth-trie/test-utils", "reth-chain-state/test-utils", "reth-db/test-utils"] diff --git a/crates/storage/provider/src/bundle_state/execution_outcome.rs b/crates/storage/provider/src/bundle_state/execution_outcome.rs deleted file mode 100644 index b56a8a53e..000000000 --- a/crates/storage/provider/src/bundle_state/execution_outcome.rs +++ /dev/null @@ -1,1039 +0,0 @@ -use crate::{ - providers::StaticFileProviderRWRefMut, writer::StorageWriter, DatabaseProviderRW, StateChanges, - StateReverts, StateWriter, -}; -use reth_db::Database; -pub use reth_execution_types::*; -use reth_storage_errors::provider::ProviderResult; -pub use revm::db::states::OriginalValuesKnown; - -impl StateWriter for ExecutionOutcome { - fn write_to_storage( - self, - provider_rw: &DatabaseProviderRW, - static_file_producer: Option>, - is_value_known: OriginalValuesKnown, - ) -> ProviderResult<()> - where - DB: Database, - { - let (plain_state, reverts) = self.bundle.into_plain_state_and_reverts(is_value_known); - - StateReverts(reverts).write_to_db(provider_rw, self.first_block)?; - - StorageWriter::new(Some(provider_rw), static_file_producer) - .append_receipts_from_blocks(self.first_block, self.receipts.into_iter())?; - - StateChanges(plain_state).write_to_db(provider_rw)?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_utils::create_test_provider_factory, AccountReader}; - use reth_db::{tables, test_utils::create_test_rw_db}; - use reth_db_api::{ - cursor::{DbCursorRO, DbDupCursorRO}, - database::Database, - models::{AccountBeforeTx, BlockNumberAddress}, - transaction::{DbTx, DbTxMut}, - }; - use reth_primitives::{ - keccak256, Account, Address, Receipt, Receipts, StorageEntry, B256, U256, - }; - use reth_trie::{test_utils::state_root, StateRoot}; - use reth_trie_db::DatabaseStateRoot; - use revm::{ - db::{ - states::{ - bundle_state::BundleRetention, changes::PlainStorageRevert, PlainStorageChangeset, - }, - BundleState, EmptyDB, - }, - primitives::{ - Account as RevmAccount, AccountInfo as RevmAccountInfo, AccountStatus, EvmStorageSlot, - }, - DatabaseCommit, State, - }; - use std::collections::{BTreeMap, HashMap}; - - #[test] - fn write_to_db_account_info() { - let factory = create_test_provider_factory(); - let provider = factory.provider_rw().unwrap(); - - let address_a = Address::ZERO; - let address_b = Address::repeat_byte(0xff); - - let account_a = RevmAccountInfo { balance: U256::from(1), nonce: 1, ..Default::default() }; - let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() }; - let account_b_changed = - RevmAccountInfo { balance: U256::from(3), nonce: 3, ..Default::default() }; - - let mut state = State::builder().with_bundle_update().build(); - state.insert_not_existing(address_a); - state.insert_account(address_b, account_b.clone()); - - // 0x00.. is created - state.commit(HashMap::from([( - address_a, - RevmAccount { - info: account_a.clone(), - status: AccountStatus::Touched | AccountStatus::Created, - storage: HashMap::default(), - }, - )])); - - // 0xff.. is changed (balance + 1, nonce + 1) - state.commit(HashMap::from([( - address_b, - RevmAccount { - info: account_b_changed.clone(), - status: AccountStatus::Touched, - storage: HashMap::default(), - }, - )])); - - state.merge_transitions(BundleRetention::Reverts); - let mut revm_bundle_state = state.take_bundle(); - - // Write plain state and reverts separately. - let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts(); - let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes); - assert!(plain_state.storage.is_empty()); - assert!(plain_state.contracts.is_empty()); - StateChanges(plain_state) - .write_to_db(&provider) - .expect("Could not write plain state to DB"); - - assert_eq!(reverts.storage, [[]]); - StateReverts(reverts).write_to_db(&provider, 1).expect("Could not write reverts to DB"); - - let reth_account_a = account_a.into(); - let reth_account_b = account_b.into(); - let reth_account_b_changed = account_b_changed.clone().into(); - - // Check plain state - assert_eq!( - provider.basic_account(address_a).expect("Could not read account state"), - Some(reth_account_a), - "Account A state is wrong" - ); - assert_eq!( - provider.basic_account(address_b).expect("Could not read account state"), - Some(reth_account_b_changed), - "Account B state is wrong" - ); - - // Check change set - let mut changeset_cursor = provider - .tx_ref() - .cursor_dup_read::() - .expect("Could not open changeset cursor"); - assert_eq!( - changeset_cursor.seek_exact(1).expect("Could not read account change set"), - Some((1, AccountBeforeTx { address: address_a, info: None })), - "Account A changeset is wrong" - ); - assert_eq!( - changeset_cursor.next_dup().expect("Changeset table is malformed"), - Some((1, AccountBeforeTx { address: address_b, info: Some(reth_account_b) })), - "Account B changeset is wrong" - ); - - let mut state = State::builder().with_bundle_update().build(); - state.insert_account(address_b, account_b_changed.clone()); - - // 0xff.. is destroyed - state.commit(HashMap::from([( - address_b, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: account_b_changed, - storage: HashMap::default(), - }, - )])); - - state.merge_transitions(BundleRetention::Reverts); - let mut revm_bundle_state = state.take_bundle(); - - // Write plain state and reverts separately. - let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts(); - let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes); - // Account B selfdestructed so flag for it should be present. - assert_eq!( - plain_state.storage, - [PlainStorageChangeset { address: address_b, wipe_storage: true, storage: vec![] }] - ); - assert!(plain_state.contracts.is_empty()); - StateChanges(plain_state) - .write_to_db(&provider) - .expect("Could not write plain state to DB"); - - assert_eq!( - reverts.storage, - [[PlainStorageRevert { address: address_b, wiped: true, storage_revert: vec![] }]] - ); - StateReverts(reverts).write_to_db(&provider, 2).expect("Could not write reverts to DB"); - - // Check new plain state for account B - assert_eq!( - provider.basic_account(address_b).expect("Could not read account state"), - None, - "Account B should be deleted" - ); - - // Check change set - assert_eq!( - changeset_cursor.seek_exact(2).expect("Could not read account change set"), - Some((2, AccountBeforeTx { address: address_b, info: Some(reth_account_b_changed) })), - "Account B changeset is wrong after deletion" - ); - } - - #[test] - fn write_to_db_storage() { - let factory = create_test_provider_factory(); - let provider = factory.provider_rw().unwrap(); - - let address_a = Address::ZERO; - let address_b = Address::repeat_byte(0xff); - - let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() }; - - let mut state = State::builder().with_bundle_update().build(); - state.insert_not_existing(address_a); - state.insert_account_with_storage( - address_b, - account_b.clone(), - HashMap::from([(U256::from(1), U256::from(1))]), - ); - - state.commit(HashMap::from([ - ( - address_a, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: RevmAccountInfo::default(), - // 0x00 => 0 => 1 - // 0x01 => 0 => 2 - storage: HashMap::from([ - ( - U256::from(0), - EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, - ), - ( - U256::from(1), - EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, - ), - ]), - }, - ), - ( - address_b, - RevmAccount { - status: AccountStatus::Touched, - info: account_b, - // 0x01 => 1 => 2 - storage: HashMap::from([( - U256::from(1), - EvmStorageSlot { - present_value: U256::from(2), - original_value: U256::from(1), - ..Default::default() - }, - )]), - }, - ), - ])); - - state.merge_transitions(BundleRetention::Reverts); - - ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write bundle state to DB"); - - // Check plain storage state - let mut storage_cursor = provider - .tx_ref() - .cursor_dup_read::() - .expect("Could not open plain storage state cursor"); - - assert_eq!( - storage_cursor.seek_exact(address_a).unwrap(), - Some((address_a, StorageEntry { key: B256::ZERO, value: U256::from(1) })), - "Slot 0 for account A should be 1" - ); - assert_eq!( - storage_cursor.next_dup().unwrap(), - Some(( - address_a, - StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } - )), - "Slot 1 for account A should be 2" - ); - assert_eq!( - storage_cursor.next_dup().unwrap(), - None, - "Account A should only have 2 storage slots" - ); - - assert_eq!( - storage_cursor.seek_exact(address_b).unwrap(), - Some(( - address_b, - StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } - )), - "Slot 1 for account B should be 2" - ); - assert_eq!( - storage_cursor.next_dup().unwrap(), - None, - "Account B should only have 1 storage slot" - ); - - // Check change set - let mut changeset_cursor = provider - .tx_ref() - .cursor_dup_read::() - .expect("Could not open storage changeset cursor"); - assert_eq!( - changeset_cursor.seek_exact(BlockNumberAddress((1, address_a))).unwrap(), - Some(( - BlockNumberAddress((1, address_a)), - StorageEntry { key: B256::ZERO, value: U256::from(0) } - )), - "Slot 0 for account A should have changed from 0" - ); - assert_eq!( - changeset_cursor.next_dup().unwrap(), - Some(( - BlockNumberAddress((1, address_a)), - StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(0) } - )), - "Slot 1 for account A should have changed from 0" - ); - assert_eq!( - changeset_cursor.next_dup().unwrap(), - None, - "Account A should only be in the changeset 2 times" - ); - - assert_eq!( - changeset_cursor.seek_exact(BlockNumberAddress((1, address_b))).unwrap(), - Some(( - BlockNumberAddress((1, address_b)), - StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(1) } - )), - "Slot 1 for account B should have changed from 1" - ); - assert_eq!( - changeset_cursor.next_dup().unwrap(), - None, - "Account B should only be in the changeset 1 time" - ); - - // Delete account A - let mut state = State::builder().with_bundle_update().build(); - state.insert_account(address_a, RevmAccountInfo::default()); - - state.commit(HashMap::from([( - address_a, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: RevmAccountInfo::default(), - storage: HashMap::default(), - }, - )])); - - state.merge_transitions(BundleRetention::Reverts); - ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 2, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write bundle state to DB"); - - assert_eq!( - storage_cursor.seek_exact(address_a).unwrap(), - None, - "Account A should have no storage slots after deletion" - ); - - assert_eq!( - changeset_cursor.seek_exact(BlockNumberAddress((2, address_a))).unwrap(), - Some(( - BlockNumberAddress((2, address_a)), - StorageEntry { key: B256::ZERO, value: U256::from(1) } - )), - "Slot 0 for account A should have changed from 1 on deletion" - ); - assert_eq!( - changeset_cursor.next_dup().unwrap(), - Some(( - BlockNumberAddress((2, address_a)), - StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } - )), - "Slot 1 for account A should have changed from 2 on deletion" - ); - assert_eq!( - changeset_cursor.next_dup().unwrap(), - None, - "Account A should only be in the changeset 2 times on deletion" - ); - } - - #[test] - fn write_to_db_multiple_selfdestructs() { - let factory = create_test_provider_factory(); - let provider = factory.provider_rw().unwrap(); - - let address1 = Address::random(); - let account_info = RevmAccountInfo { nonce: 1, ..Default::default() }; - - // Block #0: initial state. - let mut init_state = State::builder().with_bundle_update().build(); - init_state.insert_not_existing(address1); - init_state.commit(HashMap::from([( - address1, - RevmAccount { - info: account_info.clone(), - status: AccountStatus::Touched | AccountStatus::Created, - // 0x00 => 0 => 1 - // 0x01 => 0 => 2 - storage: HashMap::from([ - ( - U256::ZERO, - EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, - ), - ( - U256::from(1), - EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, - ), - ]), - }, - )])); - init_state.merge_transitions(BundleRetention::Reverts); - ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write init bundle state to DB"); - - let mut state = State::builder().with_bundle_update().build(); - state.insert_account_with_storage( - address1, - account_info.clone(), - HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]), - ); - - // Block #1: change storage. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched, - info: account_info.clone(), - // 0x00 => 1 => 2 - storage: HashMap::from([( - U256::ZERO, - EvmStorageSlot { - original_value: U256::from(1), - present_value: U256::from(2), - ..Default::default() - }, - )]), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #2: destroy account. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #3: re-create account and change storage. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #4: change storage. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched, - info: account_info.clone(), - // 0x00 => 0 => 2 - // 0x02 => 0 => 4 - // 0x06 => 0 => 6 - storage: HashMap::from([ - ( - U256::ZERO, - EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, - ), - ( - U256::from(2), - EvmStorageSlot { present_value: U256::from(4), ..Default::default() }, - ), - ( - U256::from(6), - EvmStorageSlot { present_value: U256::from(6), ..Default::default() }, - ), - ]), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #5: Destroy account again. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #6: Create, change, destroy and re-create in the same block. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched, - info: account_info.clone(), - // 0x00 => 0 => 2 - storage: HashMap::from([( - U256::ZERO, - EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, - )]), - }, - )])); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account_info.clone(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - // Block #7: Change storage. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched, - info: account_info, - // 0x00 => 0 => 9 - storage: HashMap::from([( - U256::ZERO, - EvmStorageSlot { present_value: U256::from(9), ..Default::default() }, - )]), - }, - )])); - state.merge_transitions(BundleRetention::Reverts); - - let bundle = state.take_bundle(); - - ExecutionOutcome::new(bundle, Receipts::default(), 1, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write bundle state to DB"); - - let mut storage_changeset_cursor = provider - .tx_ref() - .cursor_dup_read::() - .expect("Could not open plain storage state cursor"); - let mut storage_changes = storage_changeset_cursor.walk_range(..).unwrap(); - - // Iterate through all storage changes - - // Block - // : - // ... - - // Block #0 - // 0x00: 0 - // 0x01: 0 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((0, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((0, address1)), - StorageEntry { key: B256::with_last_byte(1), value: U256::ZERO } - ))) - ); - - // Block #1 - // 0x00: 1 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((1, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) } - ))) - ); - - // Block #2 (destroyed) - // 0x00: 2 - // 0x01: 2 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((2, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((2, address1)), - StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) } - ))) - ); - - // Block #3 - // no storage changes - - // Block #4 - // 0x00: 0 - // 0x02: 0 - // 0x06: 0 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((4, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((4, address1)), - StorageEntry { key: B256::with_last_byte(2), value: U256::ZERO } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((4, address1)), - StorageEntry { key: B256::with_last_byte(6), value: U256::ZERO } - ))) - ); - - // Block #5 (destroyed) - // 0x00: 2 - // 0x02: 4 - // 0x06: 6 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((5, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((5, address1)), - StorageEntry { key: B256::with_last_byte(2), value: U256::from(4) } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((5, address1)), - StorageEntry { key: B256::with_last_byte(6), value: U256::from(6) } - ))) - ); - - // Block #6 - // no storage changes (only inter block changes) - - // Block #7 - // 0x00: 0 - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((7, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } - ))) - ); - assert_eq!(storage_changes.next(), None); - } - - #[test] - fn storage_change_after_selfdestruct_within_block() { - let factory = create_test_provider_factory(); - let provider = factory.provider_rw().unwrap(); - - let address1 = Address::random(); - let account1 = RevmAccountInfo { nonce: 1, ..Default::default() }; - - // Block #0: initial state. - let mut init_state = State::builder().with_bundle_update().build(); - init_state.insert_not_existing(address1); - init_state.commit(HashMap::from([( - address1, - RevmAccount { - info: account1.clone(), - status: AccountStatus::Touched | AccountStatus::Created, - // 0x00 => 0 => 1 - // 0x01 => 0 => 2 - storage: HashMap::from([ - ( - U256::ZERO, - EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, - ), - ( - U256::from(1), - EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, - ), - ]), - }, - )])); - init_state.merge_transitions(BundleRetention::Reverts); - ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write init bundle state to DB"); - - let mut state = State::builder().with_bundle_update().build(); - state.insert_account_with_storage( - address1, - account1.clone(), - HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]), - ); - - // Block #1: Destroy, re-create, change storage. - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: account1.clone(), - storage: HashMap::default(), - }, - )])); - - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account1.clone(), - storage: HashMap::default(), - }, - )])); - - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched, - info: account1, - // 0x01 => 0 => 5 - storage: HashMap::from([( - U256::from(1), - EvmStorageSlot { present_value: U256::from(5), ..Default::default() }, - )]), - }, - )])); - - // Commit block #1 changes to the database. - state.merge_transitions(BundleRetention::Reverts); - ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new()) - .write_to_storage(&provider, None, OriginalValuesKnown::Yes) - .expect("Could not write bundle state to DB"); - - let mut storage_changeset_cursor = provider - .tx_ref() - .cursor_dup_read::() - .expect("Could not open plain storage state cursor"); - let range = BlockNumberAddress::range(1..=1); - let mut storage_changes = storage_changeset_cursor.walk_range(range).unwrap(); - - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((1, address1)), - StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) } - ))) - ); - assert_eq!( - storage_changes.next(), - Some(Ok(( - BlockNumberAddress((1, address1)), - StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) } - ))) - ); - assert_eq!(storage_changes.next(), None); - } - - #[test] - fn revert_to_indices() { - let base = ExecutionOutcome { - bundle: BundleState::default(), - receipts: vec![vec![Some(Receipt::default()); 2]; 7].into(), - first_block: 10, - requests: Vec::new(), - }; - - let mut this = base.clone(); - assert!(this.revert_to(10)); - assert_eq!(this.receipts.len(), 1); - - let mut this = base.clone(); - assert!(!this.revert_to(9)); - assert_eq!(this.receipts.len(), 7); - - let mut this = base.clone(); - assert!(this.revert_to(15)); - assert_eq!(this.receipts.len(), 6); - - let mut this = base.clone(); - assert!(this.revert_to(16)); - assert_eq!(this.receipts.len(), 7); - - let mut this = base; - assert!(!this.revert_to(17)); - assert_eq!(this.receipts.len(), 7); - } - - #[test] - fn bundle_state_state_root() { - type PreState = BTreeMap)>; - let mut prestate: PreState = (0..10) - .map(|key| { - let account = Account { nonce: 1, balance: U256::from(key), bytecode_hash: None }; - let storage = - (1..11).map(|key| (B256::with_last_byte(key), U256::from(key))).collect(); - (Address::with_last_byte(key), (account, storage)) - }) - .collect(); - - let db = create_test_rw_db(); - - // insert initial state to the database - db.update(|tx| { - for (address, (account, storage)) in &prestate { - let hashed_address = keccak256(address); - tx.put::(hashed_address, *account).unwrap(); - for (slot, value) in storage { - tx.put::( - hashed_address, - StorageEntry { key: keccak256(slot), value: *value }, - ) - .unwrap(); - } - } - - let (_, updates) = StateRoot::from_tx(tx).root_with_updates().unwrap(); - updates.write_to_database(tx).unwrap(); - }) - .unwrap(); - - let tx = db.tx().unwrap(); - let mut state = State::builder().with_bundle_update().build(); - - let assert_state_root = |state: &State, expected: &PreState, msg| { - assert_eq!( - StateRoot::overlay_root( - &tx, - ExecutionOutcome::new( - state.bundle_state.clone(), - Receipts::default(), - 0, - Vec::new() - ) - .hash_state_slow() - ) - .unwrap(), - state_root(expected.clone().into_iter().map(|(address, (account, storage))| ( - address, - (account, storage.into_iter()) - ))), - "{msg}" - ); - }; - - // database only state root is correct - assert_state_root(&state, &prestate, "empty"); - - // destroy account 1 - let address1 = Address::with_last_byte(1); - let account1_old = prestate.remove(&address1).unwrap(); - state.insert_account(address1, account1_old.0.into()); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::SelfDestructed, - info: RevmAccountInfo::default(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "destroyed account"); - - // change slot 2 in account 2 - let address2 = Address::with_last_byte(2); - let slot2 = U256::from(2); - let slot2_key = B256::from(slot2); - let account2 = prestate.get_mut(&address2).unwrap(); - let account2_slot2_old_value = *account2.1.get(&slot2_key).unwrap(); - state.insert_account_with_storage( - address2, - account2.0.into(), - HashMap::from([(slot2, account2_slot2_old_value)]), - ); - - let account2_slot2_new_value = U256::from(100); - account2.1.insert(slot2_key, account2_slot2_new_value); - state.commit(HashMap::from([( - address2, - RevmAccount { - status: AccountStatus::Touched, - info: account2.0.into(), - storage: HashMap::from_iter([( - slot2, - EvmStorageSlot::new_changed(account2_slot2_old_value, account2_slot2_new_value), - )]), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "changed storage"); - - // change balance of account 3 - let address3 = Address::with_last_byte(3); - let account3 = prestate.get_mut(&address3).unwrap(); - state.insert_account(address3, account3.0.into()); - - account3.0.balance = U256::from(24); - state.commit(HashMap::from([( - address3, - RevmAccount { - status: AccountStatus::Touched, - info: account3.0.into(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "changed balance"); - - // change nonce of account 4 - let address4 = Address::with_last_byte(4); - let account4 = prestate.get_mut(&address4).unwrap(); - state.insert_account(address4, account4.0.into()); - - account4.0.nonce = 128; - state.commit(HashMap::from([( - address4, - RevmAccount { - status: AccountStatus::Touched, - info: account4.0.into(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "changed nonce"); - - // recreate account 1 - let account1_new = - Account { nonce: 56, balance: U256::from(123), bytecode_hash: Some(B256::random()) }; - prestate.insert(address1, (account1_new, BTreeMap::default())); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account1_new.into(), - storage: HashMap::default(), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "recreated"); - - // update storage for account 1 - let slot20 = U256::from(20); - let slot20_key = B256::from(slot20); - let account1_slot20_value = U256::from(12345); - prestate.get_mut(&address1).unwrap().1.insert(slot20_key, account1_slot20_value); - state.commit(HashMap::from([( - address1, - RevmAccount { - status: AccountStatus::Touched | AccountStatus::Created, - info: account1_new.into(), - storage: HashMap::from_iter([( - slot20, - EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value), - )]), - }, - )])); - state.merge_transitions(BundleRetention::PlainState); - assert_state_root(&state, &prestate, "recreated changed storage"); - } - - #[test] - fn prepend_state() { - let address1 = Address::random(); - let address2 = Address::random(); - - let account1 = RevmAccountInfo { nonce: 1, ..Default::default() }; - let account1_changed = RevmAccountInfo { nonce: 1, ..Default::default() }; - let account2 = RevmAccountInfo { nonce: 1, ..Default::default() }; - - let present_state = BundleState::builder(2..=2) - .state_present_account_info(address1, account1_changed.clone()) - .build(); - assert_eq!(present_state.reverts.len(), 1); - let previous_state = BundleState::builder(1..=1) - .state_present_account_info(address1, account1) - .state_present_account_info(address2, account2.clone()) - .build(); - assert_eq!(previous_state.reverts.len(), 1); - - let mut test = ExecutionOutcome { - bundle: present_state, - receipts: vec![vec![Some(Receipt::default()); 2]; 1].into(), - first_block: 2, - requests: Vec::new(), - }; - - test.prepend_state(previous_state); - - assert_eq!(test.receipts.len(), 1); - let end_state = test.state(); - assert_eq!(end_state.state.len(), 2); - // reverts num should stay the same. - assert_eq!(end_state.reverts.len(), 1); - // account1 is not overwritten. - assert_eq!(end_state.state.get(&address1).unwrap().info, Some(account1_changed)); - // account2 got inserted - assert_eq!(end_state.state.get(&address2).unwrap().info, Some(account2)); - } -} diff --git a/crates/storage/provider/src/bundle_state/mod.rs b/crates/storage/provider/src/bundle_state/mod.rs index 3dad9389f..842f6cf6e 100644 --- a/crates/storage/provider/src/bundle_state/mod.rs +++ b/crates/storage/provider/src/bundle_state/mod.rs @@ -1,10 +1,8 @@ //! Bundle state module. //! This module contains all the logic related to bundle state. -mod execution_outcome; mod state_changes; mod state_reverts; -pub use execution_outcome::{AccountRevertInit, BundleStateInit, OriginalValuesKnown, RevertsInit}; pub use state_changes::StateChanges; pub use state_reverts::{StateReverts, StorageRevertsIter}; diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 41b8c5438..88df2fff0 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -34,7 +34,10 @@ pub use reth_storage_errors::provider::{ProviderError, ProviderResult}; pub use reth_execution_types::*; pub mod bundle_state; -pub use bundle_state::{OriginalValuesKnown, StateChanges, StateReverts}; +pub use bundle_state::{StateChanges, StateReverts}; + +/// Re-export `OriginalValuesKnown` +pub use revm::db::states::OriginalValuesKnown; /// Writer standalone type. pub mod writer; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 8386f4090..19e9d4091 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,5 +1,4 @@ use crate::{ - bundle_state::{BundleStateInit, RevertsInit}, providers::{database::metrics, static_file::StaticFileWriter, StaticFileProvider}, to_range, traits::{ @@ -7,12 +6,13 @@ use crate::{ }, writer::StorageWriter, AccountReader, BlockExecutionReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, - BlockReader, BlockWriter, EvmEnvProvider, FinalizedBlockReader, FinalizedBlockWriter, - HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, - HistoryWriter, LatestStateProvider, OriginalValuesKnown, ProviderError, PruneCheckpointReader, - PruneCheckpointWriter, RequestsProvider, StageCheckpointReader, StateProviderBox, StateWriter, - StatsReader, StorageReader, TransactionVariant, TransactionsProvider, TransactionsProviderExt, - WithdrawalsProvider, + BlockReader, BlockWriter, BundleStateInit, EvmEnvProvider, FinalizedBlockReader, + FinalizedBlockWriter, HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, + HistoricalStateProvider, HistoryWriter, LatestStateProvider, OriginalValuesKnown, + ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RequestsProvider, RevertsInit, + StageCheckpointReader, StateProviderBox, StateWriter, StatsReader, StorageReader, + StorageTrieWriter, TransactionVariant, TransactionsProvider, TransactionsProviderExt, + TrieWriter, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_chainspec::{ChainInfo, ChainSpec, EthereumHardforks}; @@ -44,8 +44,9 @@ use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_errors::provider::{ProviderResult, RootMismatch}; use reth_trie::{ prefix_set::{PrefixSet, PrefixSetMut, TriePrefixSets}, - updates::TrieUpdates, - HashedPostStateSorted, Nibbles, StateRoot, + trie_cursor::DatabaseStorageTrieCursor, + updates::{StorageTrieUpdates, TrieUpdates}, + HashedPostStateSorted, Nibbles, StateRoot, StoredNibbles, }; use reth_trie_db::DatabaseStateRoot; use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; @@ -2627,6 +2628,93 @@ impl StorageReader for DatabaseProvider { } } +impl TrieWriter for DatabaseProvider { + /// Writes trie updates. Returns the number of entries modified. + fn write_trie_updates(&self, trie_updates: &TrieUpdates) -> ProviderResult { + if trie_updates.is_empty() { + return Ok(0) + } + + // Track the number of inserted entries. + let mut num_entries = 0; + + // Merge updated and removed nodes. Updated nodes must take precedence. + let mut account_updates = trie_updates + .removed_nodes_ref() + .iter() + .filter_map(|n| { + (!trie_updates.account_nodes_ref().contains_key(n)).then_some((n, None)) + }) + .collect::>(); + account_updates.extend( + trie_updates.account_nodes_ref().iter().map(|(nibbles, node)| (nibbles, Some(node))), + ); + // Sort trie node updates. + account_updates.sort_unstable_by(|a, b| a.0.cmp(b.0)); + + let tx = self.tx_ref(); + let mut account_trie_cursor = tx.cursor_write::()?; + for (key, updated_node) in account_updates { + let nibbles = StoredNibbles(key.clone()); + match updated_node { + Some(node) => { + if !nibbles.0.is_empty() { + num_entries += 1; + account_trie_cursor.upsert(nibbles, node.clone())?; + } + } + None => { + num_entries += 1; + if account_trie_cursor.seek_exact(nibbles)?.is_some() { + account_trie_cursor.delete_current()?; + } + } + } + } + + num_entries += self.write_storage_trie_updates(trie_updates.storage_tries_ref())?; + + Ok(num_entries) + } +} + +impl StorageTrieWriter for DatabaseProvider { + /// Writes storage trie updates from the given storage trie map. First sorts the storage trie + /// updates by the hashed address, writing in sorted order. + fn write_storage_trie_updates( + &self, + storage_tries: &HashMap, + ) -> ProviderResult { + let mut num_entries = 0; + let mut storage_tries = Vec::from_iter(storage_tries); + storage_tries.sort_unstable_by(|a, b| a.0.cmp(b.0)); + let mut cursor = self.tx_ref().cursor_dup_write::()?; + for (hashed_address, storage_trie_updates) in storage_tries { + let mut db_storage_trie_cursor = + DatabaseStorageTrieCursor::new(cursor, *hashed_address); + num_entries += + db_storage_trie_cursor.write_storage_trie_updates(storage_trie_updates)?; + cursor = db_storage_trie_cursor.cursor; + } + + Ok(num_entries) + } + + fn write_individual_storage_trie_updates( + &self, + hashed_address: B256, + updates: &StorageTrieUpdates, + ) -> ProviderResult { + if updates.is_empty() { + return Ok(0) + } + + let cursor = self.tx_ref().cursor_dup_write::()?; + let mut trie_db_cursor = DatabaseStorageTrieCursor::new(cursor, hashed_address); + Ok(trie_db_cursor.write_storage_trie_updates(updates)?) + } +} + impl HashingWriter for DatabaseProvider { fn unwind_account_hashing( &self, @@ -2822,7 +2910,7 @@ impl HashingWriter for DatabaseProvider { block_hash: end_block_hash, }))) } - trie_updates.write_to_database(&self.tx)?; + self.write_trie_updates(&trie_updates)?; } durations_recorder.record_relative(metrics::Action::InsertMerkleTree); @@ -3031,7 +3119,7 @@ impl BlockExecutionWriter for DatabaseProviderRW { block_hash: parent_hash, }))) } - trie_updates.write_to_database(&self.tx)?; + self.write_trie_updates(&trie_updates)?; // get blocks let blocks = self.take_block_range(range.clone())?; @@ -3119,7 +3207,7 @@ impl BlockExecutionWriter for DatabaseProviderRW { block_hash: parent_hash, }))) } - trie_updates.write_to_database(&self.tx)?; + self.write_trie_updates(&trie_updates)?; // get blocks let blocks = self.take_block_range(range.clone())?; @@ -3333,14 +3421,16 @@ impl BlockWriter for DatabaseProviderRW { // Write state and changesets to the database. // Must be written after blocks because of the receipt lookup. - execution_outcome.write_to_storage(self, None, OriginalValuesKnown::No)?; + // TODO: should _these_ be moved to storagewriter? seems like storagewriter should be + // _above_ db provider + let mut storage_writer = StorageWriter::new(Some(self), None); + storage_writer.write_to_storage(execution_outcome, OriginalValuesKnown::No)?; durations_recorder.record_relative(metrics::Action::InsertState); // insert hashes and intermediate merkle nodes { - let storage_writer = StorageWriter::new(Some(self), None); storage_writer.write_hashed_state(&hashed_state)?; - trie_updates.write_to_database(&self.tx)?; + self.write_trie_updates(&trie_updates)?; } durations_recorder.record_relative(metrics::Action::InsertHashes); diff --git a/crates/storage/provider/src/test_utils/mod.rs b/crates/storage/provider/src/test_utils/mod.rs index 2e43212cd..edbbe4582 100644 --- a/crates/storage/provider/src/test_utils/mod.rs +++ b/crates/storage/provider/src/test_utils/mod.rs @@ -1,9 +1,13 @@ -use crate::{providers::StaticFileProvider, ProviderFactory}; +use crate::{providers::StaticFileProvider, HashingWriter, ProviderFactory, TrieWriter}; use reth_chainspec::{ChainSpec, MAINNET}; use reth_db::{ test_utils::{create_test_rw_db, create_test_static_files_dir, TempDatabase}, - DatabaseEnv, + Database, DatabaseEnv, }; +use reth_errors::ProviderResult; +use reth_primitives::{Account, StorageEntry, B256}; +use reth_trie::StateRoot; +use reth_trie_db::DatabaseStateRoot; use std::sync::Arc; pub mod blocks; @@ -31,3 +35,39 @@ pub fn create_test_provider_factory_with_chain_spec( StaticFileProvider::read_write(static_dir.into_path()).expect("static file provider"), ) } + +/// Inserts the genesis alloc from the provided chain spec into the trie. +pub fn insert_genesis( + provider_factory: &ProviderFactory, + chain_spec: Arc, +) -> ProviderResult { + let provider = provider_factory.provider_rw()?; + + // Hash accounts and insert them into hashing table. + let genesis = chain_spec.genesis(); + let alloc_accounts = genesis + .alloc + .iter() + .map(|(addr, account)| (*addr, Some(Account::from_genesis_account(account)))); + provider.insert_account_for_hashing(alloc_accounts).unwrap(); + + let alloc_storage = genesis.alloc.clone().into_iter().filter_map(|(addr, account)| { + // Only return `Some` if there is storage. + account.storage.map(|storage| { + ( + addr, + storage.into_iter().map(|(key, value)| StorageEntry { key, value: value.into() }), + ) + }) + }); + provider.insert_storage_for_hashing(alloc_storage)?; + + let (root, updates) = StateRoot::from_tx(provider.tx_ref()) + .root_with_updates() + .map_err(Into::::into)?; + provider.write_trie_updates(&updates).unwrap(); + + provider.commit()?; + + Ok(root) +} diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index bd7507875..b761bfe82 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -24,6 +24,9 @@ pub use spec::ChainSpecProvider; mod hashing; pub use hashing::HashingWriter; +mod trie; +pub use trie::{StorageTrieWriter, TrieWriter}; + mod history; pub use history::HistoryWriter; diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index b445892a0..9910d42c5 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -1,19 +1,14 @@ -use crate::{providers::StaticFileProviderRWRefMut, DatabaseProviderRW}; -use reth_db::Database; +use reth_execution_types::ExecutionOutcome; use reth_storage_errors::provider::ProviderResult; use revm::db::OriginalValuesKnown; -/// A helper trait for [`ExecutionOutcome`](reth_execution_types::ExecutionOutcome) to -/// write state and receipts to storage. +/// A helper trait for [`ExecutionOutcome`] to write state and receipts to storage. pub trait StateWriter { /// Write the data and receipts to the database or static files if `static_file_producer` is /// `Some`. It should be `None` if there is any kind of pruning/filtering over the receipts. - fn write_to_storage( - self, - provider_rw: &DatabaseProviderRW, - static_file_producer: Option>, + fn write_to_storage( + &mut self, + execution_outcome: ExecutionOutcome, is_value_known: OriginalValuesKnown, - ) -> ProviderResult<()> - where - DB: Database; + ) -> ProviderResult<()>; } diff --git a/crates/storage/provider/src/traits/trie.rs b/crates/storage/provider/src/traits/trie.rs new file mode 100644 index 000000000..960af93c8 --- /dev/null +++ b/crates/storage/provider/src/traits/trie.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use auto_impl::auto_impl; +use reth_primitives::B256; +use reth_storage_errors::provider::ProviderResult; +use reth_trie::updates::{StorageTrieUpdates, TrieUpdates}; + +/// Trie Writer +#[auto_impl(&, Arc, Box)] +pub trait TrieWriter: Send + Sync { + /// Writes trie updates to the database. + /// + /// Returns the number of entries modified. + fn write_trie_updates(&self, trie_updates: &TrieUpdates) -> ProviderResult; +} + +/// Storage Trie Writer +#[auto_impl(&, Arc, Box)] +pub trait StorageTrieWriter: Send + Sync { + /// Writes storage trie updates from the given storage trie map. + /// + /// First sorts the storage trie updates by the hashed address key, writing in sorted order. + /// + /// Returns the number of entries modified. + fn write_storage_trie_updates( + &self, + storage_tries: &HashMap, + ) -> ProviderResult; + + /// Writes storage trie updates for the given hashed address. + fn write_individual_storage_trie_updates( + &self, + hashed_address: B256, + updates: &StorageTrieUpdates, + ) -> ProviderResult; +} diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index 7b79e47c7..8b368cf21 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -1,6 +1,7 @@ -use std::borrow::Borrow; - -use crate::{providers::StaticFileProviderRWRefMut, DatabaseProviderRW}; +use crate::{ + providers::StaticFileProviderRWRefMut, DatabaseProviderRW, StateChanges, StateReverts, + StateWriter, TrieWriter, +}; use itertools::Itertools; use reth_db::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, @@ -9,13 +10,16 @@ use reth_db::{ Database, }; use reth_errors::{ProviderError, ProviderResult}; +use reth_execution_types::ExecutionOutcome; use reth_primitives::{ BlockNumber, Header, StaticFileSegment, StorageEntry, TransactionSignedNoHash, B256, U256, }; use reth_storage_api::ReceiptWriter; use reth_storage_errors::writer::StorageWriterError; -use reth_trie::HashedPostStateSorted; +use reth_trie::{updates::TrieUpdates, HashedPostStateSorted}; +use revm::db::OriginalValuesKnown; use static_file::StaticFileWriter; +use std::borrow::Borrow; mod database; mod static_file; @@ -246,7 +250,7 @@ impl<'a, 'b, DB: Database> StorageWriter<'a, 'b, DB> { /// - `blocks`: An iterator over blocks, each block having a vector of optional receipts. If /// `receipt` is `None`, it has been pruned. pub fn append_receipts_from_blocks( - mut self, + &mut self, initial_block_number: BlockNumber, blocks: impl Iterator>>, ) -> ProviderResult<()> { @@ -310,15 +314,67 @@ impl<'a, 'b, DB: Database> StorageWriter<'a, 'b, DB> { Ok(()) } + + /// Writes trie updates. Returns the number of entries modified. + pub fn write_trie_updates(&self, trie_updates: &TrieUpdates) -> ProviderResult { + self.ensure_database_writer()?; + self.database_writer().write_trie_updates(trie_updates) + } +} + +impl<'a, 'b, DB: Database> StateWriter for StorageWriter<'a, 'b, DB> { + /// Write the data and receipts to the database or static files if `static_file_producer` is + /// `Some`. It should be `None` if there is any kind of pruning/filtering over the receipts. + fn write_to_storage( + &mut self, + execution_outcome: ExecutionOutcome, + is_value_known: OriginalValuesKnown, + ) -> ProviderResult<()> { + self.ensure_database_writer()?; + let (plain_state, reverts) = + execution_outcome.bundle.into_plain_state_and_reverts(is_value_known); + + StateReverts(reverts).write_to_db(self.database_writer(), execution_outcome.first_block)?; + + self.append_receipts_from_blocks( + execution_outcome.first_block, + execution_outcome.receipts.into_iter(), + )?; + + StateChanges(plain_state).write_to_db(self.database_writer())?; + + Ok(()) + } } #[cfg(test)] mod tests { use super::*; - use crate::test_utils::create_test_provider_factory; - use reth_db_api::transaction::DbTx; - use reth_primitives::{keccak256, Account, Address, B256, U256}; - use reth_trie::{HashedPostState, HashedStorage}; + use crate::{test_utils::create_test_provider_factory, AccountReader, TrieWriter}; + use reth_db::tables; + use reth_db_api::{ + cursor::{DbCursorRO, DbDupCursorRO}, + models::{AccountBeforeTx, BlockNumberAddress}, + transaction::{DbTx, DbTxMut}, + }; + use reth_primitives::{ + keccak256, Account, Address, Receipt, Receipts, StorageEntry, B256, U256, + }; + use reth_trie::{test_utils::state_root, HashedPostState, HashedStorage, StateRoot}; + use reth_trie_db::DatabaseStateRoot; + use revm::{ + db::{ + states::{ + bundle_state::BundleRetention, changes::PlainStorageRevert, PlainStorageChangeset, + }, + BundleState, EmptyDB, + }, + primitives::{ + Account as RevmAccount, AccountInfo as RevmAccountInfo, AccountStatus, EvmStorageSlot, + }, + DatabaseCommit, State, + }; + use std::collections::{BTreeMap, HashMap}; #[test] fn wiped_entries_are_removed() { @@ -371,4 +427,997 @@ mod tests { Ok(None) ); } + + #[test] + fn write_to_db_account_info() { + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + + let address_a = Address::ZERO; + let address_b = Address::repeat_byte(0xff); + + let account_a = RevmAccountInfo { balance: U256::from(1), nonce: 1, ..Default::default() }; + let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() }; + let account_b_changed = + RevmAccountInfo { balance: U256::from(3), nonce: 3, ..Default::default() }; + + let mut state = State::builder().with_bundle_update().build(); + state.insert_not_existing(address_a); + state.insert_account(address_b, account_b.clone()); + + // 0x00.. is created + state.commit(HashMap::from([( + address_a, + RevmAccount { + info: account_a.clone(), + status: AccountStatus::Touched | AccountStatus::Created, + storage: HashMap::default(), + }, + )])); + + // 0xff.. is changed (balance + 1, nonce + 1) + state.commit(HashMap::from([( + address_b, + RevmAccount { + info: account_b_changed.clone(), + status: AccountStatus::Touched, + storage: HashMap::default(), + }, + )])); + + state.merge_transitions(BundleRetention::Reverts); + let mut revm_bundle_state = state.take_bundle(); + + // Write plain state and reverts separately. + let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts(); + let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes); + assert!(plain_state.storage.is_empty()); + assert!(plain_state.contracts.is_empty()); + StateChanges(plain_state) + .write_to_db(&provider) + .expect("Could not write plain state to DB"); + + assert_eq!(reverts.storage, [[]]); + StateReverts(reverts).write_to_db(&provider, 1).expect("Could not write reverts to DB"); + + let reth_account_a = account_a.into(); + let reth_account_b = account_b.into(); + let reth_account_b_changed = account_b_changed.clone().into(); + + // Check plain state + assert_eq!( + provider.basic_account(address_a).expect("Could not read account state"), + Some(reth_account_a), + "Account A state is wrong" + ); + assert_eq!( + provider.basic_account(address_b).expect("Could not read account state"), + Some(reth_account_b_changed), + "Account B state is wrong" + ); + + // Check change set + let mut changeset_cursor = provider + .tx_ref() + .cursor_dup_read::() + .expect("Could not open changeset cursor"); + assert_eq!( + changeset_cursor.seek_exact(1).expect("Could not read account change set"), + Some((1, AccountBeforeTx { address: address_a, info: None })), + "Account A changeset is wrong" + ); + assert_eq!( + changeset_cursor.next_dup().expect("Changeset table is malformed"), + Some((1, AccountBeforeTx { address: address_b, info: Some(reth_account_b) })), + "Account B changeset is wrong" + ); + + let mut state = State::builder().with_bundle_update().build(); + state.insert_account(address_b, account_b_changed.clone()); + + // 0xff.. is destroyed + state.commit(HashMap::from([( + address_b, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: account_b_changed, + storage: HashMap::default(), + }, + )])); + + state.merge_transitions(BundleRetention::Reverts); + let mut revm_bundle_state = state.take_bundle(); + + // Write plain state and reverts separately. + let reverts = revm_bundle_state.take_all_reverts().into_plain_state_reverts(); + let plain_state = revm_bundle_state.into_plain_state(OriginalValuesKnown::Yes); + // Account B selfdestructed so flag for it should be present. + assert_eq!( + plain_state.storage, + [PlainStorageChangeset { address: address_b, wipe_storage: true, storage: vec![] }] + ); + assert!(plain_state.contracts.is_empty()); + StateChanges(plain_state) + .write_to_db(&provider) + .expect("Could not write plain state to DB"); + + assert_eq!( + reverts.storage, + [[PlainStorageRevert { address: address_b, wiped: true, storage_revert: vec![] }]] + ); + StateReverts(reverts).write_to_db(&provider, 2).expect("Could not write reverts to DB"); + + // Check new plain state for account B + assert_eq!( + provider.basic_account(address_b).expect("Could not read account state"), + None, + "Account B should be deleted" + ); + + // Check change set + assert_eq!( + changeset_cursor.seek_exact(2).expect("Could not read account change set"), + Some((2, AccountBeforeTx { address: address_b, info: Some(reth_account_b_changed) })), + "Account B changeset is wrong after deletion" + ); + } + + #[test] + fn write_to_db_storage() { + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + + let address_a = Address::ZERO; + let address_b = Address::repeat_byte(0xff); + + let account_b = RevmAccountInfo { balance: U256::from(2), nonce: 2, ..Default::default() }; + + let mut state = State::builder().with_bundle_update().build(); + state.insert_not_existing(address_a); + state.insert_account_with_storage( + address_b, + account_b.clone(), + HashMap::from([(U256::from(1), U256::from(1))]), + ); + + state.commit(HashMap::from([ + ( + address_a, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: RevmAccountInfo::default(), + // 0x00 => 0 => 1 + // 0x01 => 0 => 2 + storage: HashMap::from([ + ( + U256::from(0), + EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, + ), + ( + U256::from(1), + EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, + ), + ]), + }, + ), + ( + address_b, + RevmAccount { + status: AccountStatus::Touched, + info: account_b, + // 0x01 => 1 => 2 + storage: HashMap::from([( + U256::from(1), + EvmStorageSlot { + present_value: U256::from(2), + original_value: U256::from(1), + ..Default::default() + }, + )]), + }, + ), + ])); + + state.merge_transitions(BundleRetention::Reverts); + + let outcome = + ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + // Check plain storage state + let mut storage_cursor = provider + .tx_ref() + .cursor_dup_read::() + .expect("Could not open plain storage state cursor"); + + assert_eq!( + storage_cursor.seek_exact(address_a).unwrap(), + Some((address_a, StorageEntry { key: B256::ZERO, value: U256::from(1) })), + "Slot 0 for account A should be 1" + ); + assert_eq!( + storage_cursor.next_dup().unwrap(), + Some(( + address_a, + StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } + )), + "Slot 1 for account A should be 2" + ); + assert_eq!( + storage_cursor.next_dup().unwrap(), + None, + "Account A should only have 2 storage slots" + ); + + assert_eq!( + storage_cursor.seek_exact(address_b).unwrap(), + Some(( + address_b, + StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } + )), + "Slot 1 for account B should be 2" + ); + assert_eq!( + storage_cursor.next_dup().unwrap(), + None, + "Account B should only have 1 storage slot" + ); + + // Check change set + let mut changeset_cursor = provider + .tx_ref() + .cursor_dup_read::() + .expect("Could not open storage changeset cursor"); + assert_eq!( + changeset_cursor.seek_exact(BlockNumberAddress((1, address_a))).unwrap(), + Some(( + BlockNumberAddress((1, address_a)), + StorageEntry { key: B256::ZERO, value: U256::from(0) } + )), + "Slot 0 for account A should have changed from 0" + ); + assert_eq!( + changeset_cursor.next_dup().unwrap(), + Some(( + BlockNumberAddress((1, address_a)), + StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(0) } + )), + "Slot 1 for account A should have changed from 0" + ); + assert_eq!( + changeset_cursor.next_dup().unwrap(), + None, + "Account A should only be in the changeset 2 times" + ); + + assert_eq!( + changeset_cursor.seek_exact(BlockNumberAddress((1, address_b))).unwrap(), + Some(( + BlockNumberAddress((1, address_b)), + StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(1) } + )), + "Slot 1 for account B should have changed from 1" + ); + assert_eq!( + changeset_cursor.next_dup().unwrap(), + None, + "Account B should only be in the changeset 1 time" + ); + + // Delete account A + let mut state = State::builder().with_bundle_update().build(); + state.insert_account(address_a, RevmAccountInfo::default()); + + state.commit(HashMap::from([( + address_a, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: RevmAccountInfo::default(), + storage: HashMap::default(), + }, + )])); + + state.merge_transitions(BundleRetention::Reverts); + let outcome = + ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 2, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + assert_eq!( + storage_cursor.seek_exact(address_a).unwrap(), + None, + "Account A should have no storage slots after deletion" + ); + + assert_eq!( + changeset_cursor.seek_exact(BlockNumberAddress((2, address_a))).unwrap(), + Some(( + BlockNumberAddress((2, address_a)), + StorageEntry { key: B256::ZERO, value: U256::from(1) } + )), + "Slot 0 for account A should have changed from 1 on deletion" + ); + assert_eq!( + changeset_cursor.next_dup().unwrap(), + Some(( + BlockNumberAddress((2, address_a)), + StorageEntry { key: B256::from(U256::from(1).to_be_bytes()), value: U256::from(2) } + )), + "Slot 1 for account A should have changed from 2 on deletion" + ); + assert_eq!( + changeset_cursor.next_dup().unwrap(), + None, + "Account A should only be in the changeset 2 times on deletion" + ); + } + + #[test] + fn write_to_db_multiple_selfdestructs() { + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + + let address1 = Address::random(); + let account_info = RevmAccountInfo { nonce: 1, ..Default::default() }; + + // Block #0: initial state. + let mut init_state = State::builder().with_bundle_update().build(); + init_state.insert_not_existing(address1); + init_state.commit(HashMap::from([( + address1, + RevmAccount { + info: account_info.clone(), + status: AccountStatus::Touched | AccountStatus::Created, + // 0x00 => 0 => 1 + // 0x01 => 0 => 2 + storage: HashMap::from([ + ( + U256::ZERO, + EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, + ), + ( + U256::from(1), + EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, + ), + ]), + }, + )])); + init_state.merge_transitions(BundleRetention::Reverts); + + let outcome = + ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + let mut state = State::builder().with_bundle_update().build(); + state.insert_account_with_storage( + address1, + account_info.clone(), + HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]), + ); + + // Block #1: change storage. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched, + info: account_info.clone(), + // 0x00 => 1 => 2 + storage: HashMap::from([( + U256::ZERO, + EvmStorageSlot { + original_value: U256::from(1), + present_value: U256::from(2), + ..Default::default() + }, + )]), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #2: destroy account. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #3: re-create account and change storage. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #4: change storage. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched, + info: account_info.clone(), + // 0x00 => 0 => 2 + // 0x02 => 0 => 4 + // 0x06 => 0 => 6 + storage: HashMap::from([ + ( + U256::ZERO, + EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, + ), + ( + U256::from(2), + EvmStorageSlot { present_value: U256::from(4), ..Default::default() }, + ), + ( + U256::from(6), + EvmStorageSlot { present_value: U256::from(6), ..Default::default() }, + ), + ]), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #5: Destroy account again. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #6: Create, change, destroy and re-create in the same block. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched, + info: account_info.clone(), + // 0x00 => 0 => 2 + storage: HashMap::from([( + U256::ZERO, + EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, + )]), + }, + )])); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account_info.clone(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + // Block #7: Change storage. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched, + info: account_info, + // 0x00 => 0 => 9 + storage: HashMap::from([( + U256::ZERO, + EvmStorageSlot { present_value: U256::from(9), ..Default::default() }, + )]), + }, + )])); + state.merge_transitions(BundleRetention::Reverts); + + let bundle = state.take_bundle(); + + let outcome = ExecutionOutcome::new(bundle, Receipts::default(), 1, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + let mut storage_changeset_cursor = provider + .tx_ref() + .cursor_dup_read::() + .expect("Could not open plain storage state cursor"); + let mut storage_changes = storage_changeset_cursor.walk_range(..).unwrap(); + + // Iterate through all storage changes + + // Block + // : + // ... + + // Block #0 + // 0x00: 0 + // 0x01: 0 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((0, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((0, address1)), + StorageEntry { key: B256::with_last_byte(1), value: U256::ZERO } + ))) + ); + + // Block #1 + // 0x00: 1 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((1, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) } + ))) + ); + + // Block #2 (destroyed) + // 0x00: 2 + // 0x01: 2 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((2, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((2, address1)), + StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) } + ))) + ); + + // Block #3 + // no storage changes + + // Block #4 + // 0x00: 0 + // 0x02: 0 + // 0x06: 0 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((4, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((4, address1)), + StorageEntry { key: B256::with_last_byte(2), value: U256::ZERO } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((4, address1)), + StorageEntry { key: B256::with_last_byte(6), value: U256::ZERO } + ))) + ); + + // Block #5 (destroyed) + // 0x00: 2 + // 0x02: 4 + // 0x06: 6 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((5, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::from(2) } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((5, address1)), + StorageEntry { key: B256::with_last_byte(2), value: U256::from(4) } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((5, address1)), + StorageEntry { key: B256::with_last_byte(6), value: U256::from(6) } + ))) + ); + + // Block #6 + // no storage changes (only inter block changes) + + // Block #7 + // 0x00: 0 + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((7, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::ZERO } + ))) + ); + assert_eq!(storage_changes.next(), None); + } + + #[test] + fn storage_change_after_selfdestruct_within_block() { + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + + let address1 = Address::random(); + let account1 = RevmAccountInfo { nonce: 1, ..Default::default() }; + + // Block #0: initial state. + let mut init_state = State::builder().with_bundle_update().build(); + init_state.insert_not_existing(address1); + init_state.commit(HashMap::from([( + address1, + RevmAccount { + info: account1.clone(), + status: AccountStatus::Touched | AccountStatus::Created, + // 0x00 => 0 => 1 + // 0x01 => 0 => 2 + storage: HashMap::from([ + ( + U256::ZERO, + EvmStorageSlot { present_value: U256::from(1), ..Default::default() }, + ), + ( + U256::from(1), + EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, + ), + ]), + }, + )])); + init_state.merge_transitions(BundleRetention::Reverts); + let outcome = + ExecutionOutcome::new(init_state.take_bundle(), Receipts::default(), 0, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + let mut state = State::builder().with_bundle_update().build(); + state.insert_account_with_storage( + address1, + account1.clone(), + HashMap::from([(U256::ZERO, U256::from(1)), (U256::from(1), U256::from(2))]), + ); + + // Block #1: Destroy, re-create, change storage. + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: account1.clone(), + storage: HashMap::default(), + }, + )])); + + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account1.clone(), + storage: HashMap::default(), + }, + )])); + + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched, + info: account1, + // 0x01 => 0 => 5 + storage: HashMap::from([( + U256::from(1), + EvmStorageSlot { present_value: U256::from(5), ..Default::default() }, + )]), + }, + )])); + + // Commit block #1 changes to the database. + state.merge_transitions(BundleRetention::Reverts); + let outcome = + ExecutionOutcome::new(state.take_bundle(), Receipts::default(), 1, Vec::new()); + let mut writer = StorageWriter::new(Some(&provider), None); + writer + .write_to_storage(outcome, OriginalValuesKnown::Yes) + .expect("Could not write bundle state to DB"); + + let mut storage_changeset_cursor = provider + .tx_ref() + .cursor_dup_read::() + .expect("Could not open plain storage state cursor"); + let range = BlockNumberAddress::range(1..=1); + let mut storage_changes = storage_changeset_cursor.walk_range(range).unwrap(); + + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((1, address1)), + StorageEntry { key: B256::with_last_byte(0), value: U256::from(1) } + ))) + ); + assert_eq!( + storage_changes.next(), + Some(Ok(( + BlockNumberAddress((1, address1)), + StorageEntry { key: B256::with_last_byte(1), value: U256::from(2) } + ))) + ); + assert_eq!(storage_changes.next(), None); + } + + #[test] + fn revert_to_indices() { + let base = ExecutionOutcome { + bundle: BundleState::default(), + receipts: vec![vec![Some(Receipt::default()); 2]; 7].into(), + first_block: 10, + requests: Vec::new(), + }; + + let mut this = base.clone(); + assert!(this.revert_to(10)); + assert_eq!(this.receipts.len(), 1); + + let mut this = base.clone(); + assert!(!this.revert_to(9)); + assert_eq!(this.receipts.len(), 7); + + let mut this = base.clone(); + assert!(this.revert_to(15)); + assert_eq!(this.receipts.len(), 6); + + let mut this = base.clone(); + assert!(this.revert_to(16)); + assert_eq!(this.receipts.len(), 7); + + let mut this = base; + assert!(!this.revert_to(17)); + assert_eq!(this.receipts.len(), 7); + } + + #[test] + fn bundle_state_state_root() { + type PreState = BTreeMap)>; + let mut prestate: PreState = (0..10) + .map(|key| { + let account = Account { nonce: 1, balance: U256::from(key), bytecode_hash: None }; + let storage = + (1..11).map(|key| (B256::with_last_byte(key), U256::from(key))).collect(); + (Address::with_last_byte(key), (account, storage)) + }) + .collect(); + + let provider_factory = create_test_provider_factory(); + let provider_rw = provider_factory.provider_rw().unwrap(); + + // insert initial state to the database + let tx = provider_rw.tx_ref(); + for (address, (account, storage)) in &prestate { + let hashed_address = keccak256(address); + tx.put::(hashed_address, *account).unwrap(); + for (slot, value) in storage { + tx.put::( + hashed_address, + StorageEntry { key: keccak256(slot), value: *value }, + ) + .unwrap(); + } + } + + let (_, updates) = StateRoot::from_tx(tx).root_with_updates().unwrap(); + provider_rw.write_trie_updates(&updates).unwrap(); + + let mut state = State::builder().with_bundle_update().build(); + + let assert_state_root = |state: &State, expected: &PreState, msg| { + assert_eq!( + StateRoot::overlay_root( + tx, + ExecutionOutcome::new( + state.bundle_state.clone(), + Receipts::default(), + 0, + Vec::new() + ) + .hash_state_slow() + ) + .unwrap(), + state_root(expected.clone().into_iter().map(|(address, (account, storage))| ( + address, + (account, storage.into_iter()) + ))), + "{msg}" + ); + }; + + // database only state root is correct + assert_state_root(&state, &prestate, "empty"); + + // destroy account 1 + let address1 = Address::with_last_byte(1); + let account1_old = prestate.remove(&address1).unwrap(); + state.insert_account(address1, account1_old.0.into()); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::SelfDestructed, + info: RevmAccountInfo::default(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "destroyed account"); + + // change slot 2 in account 2 + let address2 = Address::with_last_byte(2); + let slot2 = U256::from(2); + let slot2_key = B256::from(slot2); + let account2 = prestate.get_mut(&address2).unwrap(); + let account2_slot2_old_value = *account2.1.get(&slot2_key).unwrap(); + state.insert_account_with_storage( + address2, + account2.0.into(), + HashMap::from([(slot2, account2_slot2_old_value)]), + ); + + let account2_slot2_new_value = U256::from(100); + account2.1.insert(slot2_key, account2_slot2_new_value); + state.commit(HashMap::from([( + address2, + RevmAccount { + status: AccountStatus::Touched, + info: account2.0.into(), + storage: HashMap::from_iter([( + slot2, + EvmStorageSlot::new_changed(account2_slot2_old_value, account2_slot2_new_value), + )]), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "changed storage"); + + // change balance of account 3 + let address3 = Address::with_last_byte(3); + let account3 = prestate.get_mut(&address3).unwrap(); + state.insert_account(address3, account3.0.into()); + + account3.0.balance = U256::from(24); + state.commit(HashMap::from([( + address3, + RevmAccount { + status: AccountStatus::Touched, + info: account3.0.into(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "changed balance"); + + // change nonce of account 4 + let address4 = Address::with_last_byte(4); + let account4 = prestate.get_mut(&address4).unwrap(); + state.insert_account(address4, account4.0.into()); + + account4.0.nonce = 128; + state.commit(HashMap::from([( + address4, + RevmAccount { + status: AccountStatus::Touched, + info: account4.0.into(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "changed nonce"); + + // recreate account 1 + let account1_new = + Account { nonce: 56, balance: U256::from(123), bytecode_hash: Some(B256::random()) }; + prestate.insert(address1, (account1_new, BTreeMap::default())); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account1_new.into(), + storage: HashMap::default(), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "recreated"); + + // update storage for account 1 + let slot20 = U256::from(20); + let slot20_key = B256::from(slot20); + let account1_slot20_value = U256::from(12345); + prestate.get_mut(&address1).unwrap().1.insert(slot20_key, account1_slot20_value); + state.commit(HashMap::from([( + address1, + RevmAccount { + status: AccountStatus::Touched | AccountStatus::Created, + info: account1_new.into(), + storage: HashMap::from_iter([( + slot20, + EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value), + )]), + }, + )])); + state.merge_transitions(BundleRetention::PlainState); + assert_state_root(&state, &prestate, "recreated changed storage"); + } + + #[test] + fn prepend_state() { + let address1 = Address::random(); + let address2 = Address::random(); + + let account1 = RevmAccountInfo { nonce: 1, ..Default::default() }; + let account1_changed = RevmAccountInfo { nonce: 1, ..Default::default() }; + let account2 = RevmAccountInfo { nonce: 1, ..Default::default() }; + + let present_state = BundleState::builder(2..=2) + .state_present_account_info(address1, account1_changed.clone()) + .build(); + assert_eq!(present_state.reverts.len(), 1); + let previous_state = BundleState::builder(1..=1) + .state_present_account_info(address1, account1) + .state_present_account_info(address2, account2.clone()) + .build(); + assert_eq!(previous_state.reverts.len(), 1); + + let mut test = ExecutionOutcome { + bundle: present_state, + receipts: vec![vec![Some(Receipt::default()); 2]; 1].into(), + first_block: 2, + requests: Vec::new(), + }; + + test.prepend_state(previous_state); + + assert_eq!(test.receipts.len(), 1); + let end_state = test.state(); + assert_eq!(end_state.state.len(), 2); + // reverts num should stay the same. + assert_eq!(end_state.reverts.len(), 1); + // account1 is not overwritten. + assert_eq!(end_state.state.get(&address1).unwrap().info, Some(account1_changed)); + // account2 got inserted + assert_eq!(end_state.state.get(&address2).unwrap().info, Some(account2)); + } } diff --git a/crates/trie/db/Cargo.toml b/crates/trie/db/Cargo.toml index 5ee320f90..3c479072b 100644 --- a/crates/trie/db/Cargo.toml +++ b/crates/trie/db/Cargo.toml @@ -73,6 +73,6 @@ similar-asserts.workspace = true criterion.workspace = true [features] -metrics = ["reth-metrics", "dep:metrics"] +metrics = ["reth-metrics", "reth-trie/metrics", "dep:metrics"] serde = ["dep:serde"] test-utils = ["triehash", "reth-trie-common/test-utils"] diff --git a/crates/trie/db/tests/proof.rs b/crates/trie/db/tests/proof.rs index 27365ee82..5cb918277 100644 --- a/crates/trie/db/tests/proof.rs +++ b/crates/trie/db/tests/proof.rs @@ -4,7 +4,9 @@ use reth_db_api::database::Database; use reth_primitives::{ constants::EMPTY_ROOT_HASH, keccak256, Account, Address, Bytes, StorageEntry, B256, U256, }; -use reth_provider::{test_utils::create_test_provider_factory, HashingWriter, ProviderFactory}; +use reth_provider::{ + test_utils::create_test_provider_factory, HashingWriter, ProviderFactory, TrieWriter, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{proof::Proof, Nibbles, StateRoot}; use reth_trie_common::{AccountProof, StorageProof}; @@ -40,7 +42,7 @@ fn insert_genesis( provider_factory: &ProviderFactory, chain_spec: Arc, ) -> ProviderResult { - let mut provider = provider_factory.provider_rw()?; + let provider = provider_factory.provider_rw()?; // Hash accounts and insert them into hashing table. let genesis = chain_spec.genesis(); @@ -64,7 +66,7 @@ fn insert_genesis( let (root, updates) = StateRoot::from_tx(provider.tx_ref()) .root_with_updates() .map_err(Into::::into)?; - updates.write_to_database(provider.tx_mut())?; + provider.write_trie_updates(&updates).unwrap(); provider.commit()?; diff --git a/crates/trie/db/tests/trie.rs b/crates/trie/db/tests/trie.rs index 656905581..8a9dbee9b 100644 --- a/crates/trie/db/tests/trie.rs +++ b/crates/trie/db/tests/trie.rs @@ -6,7 +6,9 @@ use reth_db_api::{ transaction::DbTxMut, }; use reth_primitives::{hex_literal::hex, Account, StorageEntry, U256}; -use reth_provider::{test_utils::create_test_provider_factory, DatabaseProviderRW}; +use reth_provider::{ + test_utils::create_test_provider_factory, DatabaseProviderRW, StorageTrieWriter, TrieWriter, +}; use reth_trie::{ prefix_set::PrefixSetMut, test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, @@ -82,7 +84,7 @@ fn incremental_vs_full_root(inputs: &[&str], modified: &str) { let modified_root = loader.root().unwrap(); // Update the intermediate roots table so that we can run the incremental verification - trie_updates.write_to_database(tx.tx_ref(), hashed_address).unwrap(); + tx.write_individual_storage_trie_updates(hashed_address, &trie_updates).unwrap(); // 3. Calculate the incremental root let mut storage_changes = PrefixSetMut::default(); @@ -637,7 +639,7 @@ fn account_trie_around_extension_node_with_dbtrie() { let (got, updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(expected, got); - updates.write_to_database(tx.tx_ref()).unwrap(); + tx.write_trie_updates(&updates).unwrap(); // read the account updates from the db let mut accounts_trie = tx.tx_ref().cursor_read::().unwrap(); @@ -684,7 +686,7 @@ proptest! { state.iter().map(|(&key, &balance)| (key, (Account { balance, ..Default::default() }, std::iter::empty()))) ); assert_eq!(expected_root, state_root); - trie_updates.write_to_database(tx.tx_ref()).unwrap(); + tx.write_trie_updates(&trie_updates).unwrap(); } } } diff --git a/crates/trie/parallel/benches/root.rs b/crates/trie/parallel/benches/root.rs index 3a4632d3c..bbd2ff228 100644 --- a/crates/trie/parallel/benches/root.rs +++ b/crates/trie/parallel/benches/root.rs @@ -6,6 +6,7 @@ use rayon::ThreadPoolBuilder; use reth_primitives::{Account, B256, U256}; use reth_provider::{ providers::ConsistentDbView, test_utils::create_test_provider_factory, writer::StorageWriter, + TrieWriter, }; use reth_tasks::pool::BlockingTaskPool; use reth_trie::{ @@ -32,7 +33,7 @@ pub fn calculate_state_root(c: &mut Criterion) { storage_writer.write_hashed_state(&db_state.into_sorted()).unwrap(); let (_, updates) = StateRoot::from_tx(provider_rw.tx_ref()).root_with_updates().unwrap(); - updates.write_to_database(provider_rw.tx_ref()).unwrap(); + provider_rw.write_trie_updates(&updates).unwrap(); provider_rw.commit().unwrap(); } diff --git a/crates/trie/trie/src/trie_cursor/database_cursors.rs b/crates/trie/trie/src/trie_cursor/database_cursors.rs index 4c9e5e6a7..7149c53c0 100644 --- a/crates/trie/trie/src/trie_cursor/database_cursors.rs +++ b/crates/trie/trie/src/trie_cursor/database_cursors.rs @@ -1,11 +1,17 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::{BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey}; -use reth_db::{tables, DatabaseError}; +use crate::{ + updates::StorageTrieUpdates, BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey, +}; +use reth_db::{ + cursor::{DbCursorRW, DbDupCursorRW}, + tables, DatabaseError, +}; use reth_db_api::{ cursor::{DbCursorRO, DbDupCursorRO}, transaction::DbTx, }; use reth_primitives::B256; +use reth_trie_common::StorageTrieEntry; /// Implementation of the trie cursor factory for a database transaction. impl<'a, TX: DbTx> TrieCursorFactory for &'a TX { @@ -86,6 +92,62 @@ impl DatabaseStorageTrieCursor { } } +impl DatabaseStorageTrieCursor +where + C: DbCursorRO + + DbCursorRW + + DbDupCursorRO + + DbDupCursorRW, +{ + /// Writes storage updates + pub fn write_storage_trie_updates( + &mut self, + updates: &StorageTrieUpdates, + ) -> Result { + // The storage trie for this account has to be deleted. + if updates.is_deleted && self.cursor.seek_exact(self.hashed_address)?.is_some() { + self.cursor.delete_current_duplicates()?; + } + + // Merge updated and removed nodes. Updated nodes must take precedence. + let mut storage_updates = updates + .removed_nodes + .iter() + .filter_map(|n| (!updates.storage_nodes.contains_key(n)).then_some((n, None))) + .collect::>(); + storage_updates + .extend(updates.storage_nodes.iter().map(|(nibbles, node)| (nibbles, Some(node)))); + + // Sort trie node updates. + storage_updates.sort_unstable_by(|a, b| a.0.cmp(b.0)); + + let mut num_entries = 0; + for (nibbles, maybe_updated) in storage_updates.into_iter().filter(|(n, _)| !n.is_empty()) { + num_entries += 1; + let nibbles = StoredNibblesSubKey(nibbles.clone()); + // Delete the old entry if it exists. + if self + .cursor + .seek_by_key_subkey(self.hashed_address, nibbles.clone())? + .filter(|e| e.nibbles == nibbles) + .is_some() + { + self.cursor.delete_current()?; + } + + // There is an updated version of this node, insert new entry. + if let Some(node) = maybe_updated { + self.cursor.upsert( + self.hashed_address, + StorageTrieEntry { nibbles, node: node.clone() }, + )?; + } + } + + Ok(num_entries) + } +} + impl TrieCursor for DatabaseStorageTrieCursor where C: DbCursorRO + DbDupCursorRO + Send + Sync, diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index ad0f19a0d..2d35dbf48 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -1,12 +1,4 @@ -use crate::{ - walker::TrieWalker, BranchNodeCompact, HashBuilder, Nibbles, StorageTrieEntry, StoredNibbles, - StoredNibblesSubKey, -}; -use reth_db::tables; -use reth_db_api::{ - cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, - transaction::{DbTx, DbTxMut}, -}; +use crate::{walker::TrieWalker, BranchNodeCompact, HashBuilder, Nibbles}; use reth_primitives::B256; use std::collections::{HashMap, HashSet}; @@ -84,64 +76,6 @@ impl TrieUpdates { .collect(); TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries } } - - /// Flush updates all aggregated updates to the database. - /// - /// # Returns - /// - /// The number of storage trie entries updated in the database. - pub fn write_to_database(self, tx: &TX) -> Result - where - TX: DbTx + DbTxMut, - { - if self.is_empty() { - return Ok(0) - } - - // Track the number of inserted entries. - let mut num_entries = 0; - - // Merge updated and removed nodes. Updated nodes must take precedence. - let mut account_updates = self - .removed_nodes - .into_iter() - .filter_map(|n| (!self.account_nodes.contains_key(&n)).then_some((n, None))) - .collect::>(); - account_updates - .extend(self.account_nodes.into_iter().map(|(nibbles, node)| (nibbles, Some(node)))); - // Sort trie node updates. - account_updates.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - - let mut account_trie_cursor = tx.cursor_write::()?; - for (key, updated_node) in account_updates { - let nibbles = StoredNibbles(key); - match updated_node { - Some(node) => { - if !nibbles.0.is_empty() { - num_entries += 1; - account_trie_cursor.upsert(nibbles, node)?; - } - } - None => { - num_entries += 1; - if account_trie_cursor.seek_exact(nibbles)?.is_some() { - account_trie_cursor.delete_current()?; - } - } - } - } - - let mut storage_tries = Vec::from_iter(self.storage_tries); - storage_tries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let mut storage_trie_cursor = tx.cursor_dup_write::()?; - for (hashed_address, storage_trie_updates) in storage_tries { - let updated_storage_entries = - storage_trie_updates.write_with_cursor(&mut storage_trie_cursor, hashed_address)?; - num_entries += updated_storage_entries; - } - - Ok(num_entries) - } } /// Trie updates for storage trie of a single account. @@ -225,77 +159,6 @@ impl StorageTrieUpdates { storage_nodes, } } - - /// Initializes a storage trie cursor and writes updates to database. - pub fn write_to_database( - self, - tx: &TX, - hashed_address: B256, - ) -> Result - where - TX: DbTx + DbTxMut, - { - if self.is_empty() { - return Ok(0) - } - - let mut cursor = tx.cursor_dup_write::()?; - self.write_with_cursor(&mut cursor, hashed_address) - } - - /// Writes updates to database. - /// - /// # Returns - /// - /// The number of storage trie entries updated in the database. - fn write_with_cursor( - self, - cursor: &mut C, - hashed_address: B256, - ) -> Result - where - C: DbCursorRO - + DbCursorRW - + DbDupCursorRO - + DbDupCursorRW, - { - // The storage trie for this account has to be deleted. - if self.is_deleted && cursor.seek_exact(hashed_address)?.is_some() { - cursor.delete_current_duplicates()?; - } - - // Merge updated and removed nodes. Updated nodes must take precedence. - let mut storage_updates = self - .removed_nodes - .into_iter() - .filter_map(|n| (!self.storage_nodes.contains_key(&n)).then_some((n, None))) - .collect::>(); - storage_updates - .extend(self.storage_nodes.into_iter().map(|(nibbles, node)| (nibbles, Some(node)))); - // Sort trie node updates. - storage_updates.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - - let mut num_entries = 0; - for (nibbles, maybe_updated) in storage_updates.into_iter().filter(|(n, _)| !n.is_empty()) { - num_entries += 1; - let nibbles = StoredNibblesSubKey(nibbles); - // Delete the old entry if it exists. - if cursor - .seek_by_key_subkey(hashed_address, nibbles.clone())? - .filter(|e| e.nibbles == nibbles) - .is_some() - { - cursor.delete_current()?; - } - - // There is an updated version of this node, insert new entry. - if let Some(node) = maybe_updated { - cursor.upsert(hashed_address, StorageTrieEntry { nibbles, node })?; - } - } - - Ok(num_entries) - } } /// Sorted trie updates used for lookups and insertions.