diff --git a/Cargo.lock b/Cargo.lock index a026e58e92..0ce14ecdb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1166,7 +1166,7 @@ dependencies = [ "log", "rand 0.8.5", "rlp", - "secp256k1", + "secp256k1 0.24.2", "serde", "sha3", "zeroize", @@ -3367,7 +3367,7 @@ dependencies = [ "reth-interfaces", "reth-libmdbx", "reth-primitives", - "secp256k1", + "secp256k1 0.24.2", "serde", "tempfile", "test-fuzz", @@ -3392,7 +3392,7 @@ dependencies = [ "reth-rlp", "reth-rlp-derive", "reth-tracing", - "secp256k1", + "secp256k1 0.24.2", "thiserror", "tokio", "tokio-stream", @@ -3438,7 +3438,7 @@ dependencies = [ "rand 0.8.5", "reth-primitives", "reth-rlp", - "secp256k1", + "secp256k1 0.24.2", "sha2 0.10.6", "sha3", "thiserror", @@ -3465,7 +3465,7 @@ dependencies = [ "reth-primitives", "reth-rlp", "reth-tracing", - "secp256k1", + "secp256k1 0.24.2", "serde", "smol_str", "snap", @@ -3518,7 +3518,7 @@ dependencies = [ "reth-eth-wire", "reth-primitives", "reth-rpc-types", - "secp256k1", + "secp256k1 0.24.2", "serde", "thiserror", "tokio", @@ -3608,7 +3608,7 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-transaction-pool", - "secp256k1", + "secp256k1 0.24.2", "serial_test", "tempfile", "thiserror", @@ -3635,7 +3635,7 @@ dependencies = [ "plain_hasher", "reth-codecs", "reth-rlp", - "secp256k1", + "secp256k1 0.24.2", "serde", "serde_json", "sucds", @@ -3665,7 +3665,7 @@ dependencies = [ "reth-interfaces", "reth-primitives", "reth-rpc-types", - "secp256k1", + "secp256k1 0.24.2", "serde", "test-fuzz", "thiserror", @@ -3689,7 +3689,7 @@ dependencies = [ "reth-rlp", "reth-rlp-derive", "rlp", - "secp256k1", + "secp256k1 0.24.2", "smol_str", ] @@ -3810,33 +3810,35 @@ dependencies = [ [[package]] name = "revm" version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73d84c8f9836efb0f5f5f8de4700a953c4e1f3119e5cfcb0aad8e5be73daf991" +source = "git+https://github.com/bluealloy/revm?branch=main#488ef8ab62f433b1b434d2d81bc744a2db8f735f" dependencies = [ "arrayref", "auto_impl", "bytes", + "derive_more", + "fixed-hash", "hashbrown 0.13.1", + "hex", + "hex-literal", "num_enum", - "primitive-types", "revm_precompiles", "rlp", + "ruint", "sha3", ] [[package]] name = "revm_precompiles" version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0353d456ef3e989dc9190f42c6020f09bc2025930c37895826029304413204b5" +source = "git+https://github.com/bluealloy/revm?branch=main#488ef8ab62f433b1b434d2d81bc744a2db8f735f" dependencies = [ "bytes", "hashbrown 0.13.1", "num", "once_cell", - "primitive-types", "ripemd", - "secp256k1", + "ruint", + "secp256k1 0.25.0", "sha2 0.10.6", "sha3", "substrate-bn", @@ -3898,6 +3900,26 @@ dependencies = [ "syn", ] +[[package]] +name = "ruint" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad3a104dc8c3867f653b0fec89c65e00b0ceb752718ad282177a7e0f33257ac" +dependencies = [ + "derive_more", + "primitive-types", + "rlp", + "ruint-macro", + "rustc_version", + "thiserror", +] + +[[package]] +name = "ruint-macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62cc5760263ea229d367e7dff3c0cbf09e4797a125bd87059a6c095804f3b2d1" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4058,7 +4080,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" dependencies = [ "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.6.1", +] + +[[package]] +name = "secp256k1" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" +dependencies = [ + "secp256k1-sys 0.7.0", ] [[package]] @@ -4070,6 +4101,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.7.0" diff --git a/bin/reth/src/test_eth_chain/models.rs b/bin/reth/src/test_eth_chain/models.rs index fd4406fc77..6287700f00 100644 --- a/bin/reth/src/test_eth_chain/models.rs +++ b/bin/reth/src/test_eth_chain/models.rs @@ -21,7 +21,7 @@ pub struct BlockchainTestData { /// Blocks. pub blocks: Vec, /// Post state. - pub post_state: Option, + pub post_state: Option, /// Pre state. pub pre: State, /// Hash of best block. @@ -112,6 +112,18 @@ pub struct Block { pub transactions: Option>, /// Uncle/ommer headers pub uncle_headers: Option>, + /// Transaction Sequence + pub transaction_sequence: Option>, +} + +/// Transaction Sequence in block +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct TransactionSequence { + exception: String, + raw_bytes: Bytes, + valid: String, } /// Ethereum blockchain test data State. @@ -191,6 +203,9 @@ pub enum ForkSpec { /// After Merge Init Code test #[serde(alias = "Merge+3860")] MergeMeterInitCode, + /// After Merge plus new PUSH0 opcode + #[serde(alias = "Merge+3855")] + MergePush0, } impl From for reth_executor::SpecUpgrades { @@ -214,6 +229,7 @@ impl From for reth_executor::SpecUpgrades { ForkSpec::Merge => Self::new_paris_activated(), ForkSpec::MergeEOF => Self::new_paris_activated(), ForkSpec::MergeMeterInitCode => Self::new_paris_activated(), + ForkSpec::MergePush0 => Self::new_paris_activated(), ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => { panic!("Overriden with PETERSBURG") } diff --git a/bin/reth/src/test_eth_chain/runner.rs b/bin/reth/src/test_eth_chain/runner.rs index 22f88736b2..ff71da0f2d 100644 --- a/bin/reth/src/test_eth_chain/runner.rs +++ b/bin/reth/src/test_eth_chain/runner.rs @@ -1,22 +1,27 @@ use super::models::Test; -use crate::test_eth_chain::models::ForkSpec; +use crate::test_eth_chain::models::{ForkSpec, RootOrState}; +use eyre::eyre; use reth_db::{ + cursor::DbCursorRO, database::Database, mdbx::{test_utils::create_test_rw_db, WriteMap}, tables, transaction::{DbTx, DbTxMut}, + Error as DbError, }; use reth_executor::SpecUpgrades; use reth_primitives::{ - keccak256, Account as RethAccount, BigEndianHash, SealedBlock, SealedHeader, StorageEntry, H256, + keccak256, Account as RethAccount, Address, JsonU256, SealedBlock, SealedHeader, StorageEntry, + H256, U256, }; use reth_rlp::Decodable; use reth_stages::{stages::execution::ExecutionStage, ExecInput, Stage, Transaction}; use std::{ + collections::HashMap, ffi::OsStr, path::{Path, PathBuf}, }; -use tracing::debug; +use tracing::{debug, info}; /// Tests are test edge cases that are not possible to happen on mainnet, so we are skipping them. pub fn should_skip(path: &Path) -> bool { @@ -81,7 +86,8 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> { ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople | ForkSpec::MergeEOF | - ForkSpec::MergeMeterInitCode + ForkSpec::MergeMeterInitCode | + ForkSpec::MergePush0, ) { continue } @@ -126,10 +132,10 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> { tx.put::(code_hash, account.code.to_vec())?; } account.storage.iter().try_for_each(|(k, v)| { - tx.put::( - address, - StorageEntry { key: H256::from_uint(&k.0), value: v.0 }, - ) + tracing::trace!("Update storage: {address} key:{:?} val:{:?}", k.0, v.0); + let mut key = H256::zero(); + k.0.to_big_endian(&mut key.0); + tx.put::(address, StorageEntry { key, value: v.0 }) })?; Ok(()) @@ -138,17 +144,120 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<()> { // Commit the pre suite state tx.commit()?; + let storage = db.view(|tx| -> Result<_, DbError> { + let mut cursor = tx.cursor_dup::()?; + let walker = cursor.first()?.map(|first| cursor.walk(first.0)).transpose()?; + Ok(walker.map(|mut walker| { + let mut map: HashMap> = HashMap::new(); + while let Some(Ok((address, slot))) = walker.next() { + let key = U256::from_big_endian(&slot.key.0); + map.entry(address).or_default().insert(key, slot.value); + } + map + })) + })??; + tracing::trace!("Pre state :{:?}", storage); + // Initialize the execution stage - // Hardcode the chain_id to Ethereums 1. + // Hardcode the chain_id to Ethereum 1. let mut stage = ExecutionStage::new(reth_executor::Config { chain_id: 1.into(), spec_upgrades }); // Call execution stage let input = ExecInput::default(); - stage.execute(&mut Transaction::new(db.as_ref())?, input).await?; + { + let mut transaction = Transaction::new(db.as_ref())?; + + // ignore error + let _ = stage.execute(&mut transaction, input).await; + transaction.commit()?; + } // Validate post state - //for post in + match suite.post_state { + Some(RootOrState::Root(root)) => { + info!("Post state is root: #{root:?}") + } + Some(RootOrState::State(state)) => db.view(|tx| -> eyre::Result<()> { + let mut cursor = tx.cursor_dup::()?; + let walker = cursor.first()?.map(|first| cursor.walk(first.0)).transpose()?; + let storage = walker.map(|mut walker| { + let mut map: HashMap> = HashMap::new(); + while let Some(Ok((address, slot))) = walker.next() { + let key = U256::from_big_endian(&slot.key.0); + map.entry(address).or_default().insert(key, slot.value); + } + map + }); + tracing::trace!("Our storage:{:?}", storage); + for (address, test_account) in state.iter() { + // check account + let our_account = tx + .get::(*address)? + .ok_or(eyre!("Account is missing:{address} expected:{:?}", test_account))?; + if test_account.balance.0 != our_account.balance { + return Err(eyre!( + "Account {address} balance diff, expected {} got{}", + test_account.balance.0, + our_account.balance + )) + } + if test_account.nonce.0.as_u64() != our_account.nonce { + return Err(eyre!( + "Account {address} nonce diff, expected {} got {}", + test_account.nonce.0, + our_account.nonce + )) + } + if let Some(our_bytecode) = our_account.bytecode_hash { + let test_bytecode = keccak256(test_account.code.as_ref()); + if our_bytecode != test_bytecode { + return Err(eyre!( + "Account {address} bytecode diff, expected: {} got: {:?}", + test_account.code, + our_account.bytecode_hash + )) + } + } else if !test_account.code.is_empty() { + return Err(eyre!( + "Account {address} bytecode diff, expected {} got empty bytecode", + test_account.code, + )) + } + + // get walker if present + if let Some(storage) = storage.as_ref() { + // iterate over storages + for (JsonU256(key), JsonU256(value)) in test_account.storage.iter() { + let our_value = storage + .get(address) + .ok_or(eyre!( + "Missing storage from test {storage:?} got {:?}", + test_account.storage + ))? + .get(key) + .ok_or(eyre!( + "Slot is missing from table {storage:?} got:{:?}", + test_account.storage + ))?; + if value != our_value { + return Err(eyre!( + "Storage diff we got {address}: {storage:?} but expect: {:?}", + test_account.storage + )) + } + } + } else if !test_account.storage.is_empty() { + return Err(eyre!( + "Walker is not present, but storage is not empty.{:?}", + test_account.storage + )) + } + } + Ok(()) + })??, + None => info!("Post state is none"), + } } Ok(()) } diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index ae88410b71..32e80e135e 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -14,7 +14,7 @@ reth-rlp = { path = "../common/rlp" } reth-db = { path = "../storage/db" } reth-provider = { path = "../storage/provider" } -revm = "2.3" +revm = { git = "https://github.com/bluealloy/revm", branch = "main"} # remove from reth and reexport from revm hashbrown = "0.13" diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index a0a91c5067..1ff1f312eb 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -8,12 +8,12 @@ use reth_db::{models::AccountBeforeTx, tables, transaction::DbTxMut, Error as Db use reth_interfaces::executor::Error; use reth_primitives::{ bloom::logs_bloom, Account, Address, Bloom, Header, Log, Receipt, TransactionSignedEcRecovered, - H256, U256, + H160, H256, U256, }; use reth_provider::StateProvider; use revm::{ db::AccountState, Account as RevmAccount, AccountInfo, AnalysisKind, Bytecode, Database, - Return, EVM, + Return, B160, EVM, U256 as evmU256, }; use std::collections::BTreeMap; @@ -108,13 +108,13 @@ pub struct AccountChangeSet { #[derive(Debug)] pub struct ExecutionResult { /// Transaction changeest contraining [Receipt], changed [Accounts][Account] and Storages. - pub changeset: Vec, + pub changesets: Vec, /// Block reward if present. It represent changeset for block reward slot in /// [tables::AccountChangeSet] . pub block_reward: Option>, } -/// Commit chgange to database and return change diff that is used to update state and create +/// Commit change to database and return change diff that is used to update state and create /// history index /// /// ChangeDiff consists of: @@ -124,7 +124,7 @@ pub struct ExecutionResult { /// BTreeMap is used to have sorted values pub fn commit_changes( db: &mut SubState, - changes: hashbrown::HashMap, + changes: hashbrown::HashMap, ) -> (BTreeMap, BTreeMap) { let mut change = BTreeMap::new(); let mut new_bytecodes = BTreeMap::new(); @@ -132,7 +132,7 @@ pub fn commit_changes( for (address, account) in changes { if account.is_destroyed { // get old account that we are destroying. - let db_account = match db.accounts.entry(address) { + let db_account = match db.accounts.entry(B160(address.0)) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(_entry) => { panic!("Left panic to critically jumpout if happens, as every account shound be hot loaded."); @@ -141,7 +141,7 @@ pub fn commit_changes( // Insert into `change` a old account and None for new account // and mark storage to be mapped change.insert( - address, + H160(address.0), AccountChangeSet { account: AccountInfoChangeSet::Destroyed { old: to_reth_acc(&db_account.info) }, storage: BTreeMap::new(), @@ -163,7 +163,7 @@ pub fn commit_changes( match db.contracts.entry(account.info.code_hash) { Entry::Vacant(entry) => { entry.insert(code.clone()); - new_bytecodes.insert(account.info.code_hash, code.clone()); + new_bytecodes.insert(H256(account.info.code_hash.0), code.clone()); } Entry::Occupied(mut entry) => { entry.insert(code.clone()); @@ -175,7 +175,7 @@ pub fn commit_changes( // get old account that is going to be overwritten or none if it does not exist // and get new account that was just inserted. new account mut ref is used for // inserting storage - let (account_info_changeset, new_account) = match db.accounts.entry(address) { + let (account_info_changeset, new_account) = match db.accounts.entry(B160(address.0)) { Entry::Vacant(entry) => { let entry = entry.insert(Default::default()); entry.info = account.info.clone(); @@ -217,13 +217,19 @@ pub fn commit_changes( // insert storage into new db account. new_account.storage.extend(account.storage.into_iter().map(|(key, value)| { - storage.insert(key, (value.original_value(), value.present_value())); + storage.insert( + U256(*key.as_limbs()), + ( + U256(*value.original_value().as_limbs()), + U256(*value.present_value().as_limbs()), + ), + ); (key, value.present_value()) })); // Insert into change. change.insert( - address, + H160(address.0), AccountChangeSet { account: account_info_changeset, storage, wipe_storage }, ); } @@ -240,7 +246,7 @@ pub struct TransactionChangeSet { /// Transaction receipt pub receipt: Receipt, /// State change that this transaction made on state. - pub state_diff: BTreeMap, + pub changeset: BTreeMap, /// new bytecode created as result of transaction execution. pub new_bytecodes: BTreeMap, } @@ -254,8 +260,16 @@ pub fn execute_and_verify_receipt( ) -> Result { let transaction_change_set = execute(header, transactions, config, db)?; - let receipts_iter = transaction_change_set.changeset.iter().map(|changeset| &changeset.receipt); - verify_receipt(header.receipts_root, header.logs_bloom, receipts_iter)?; + let receipts_iter = + transaction_change_set.changesets.iter().map(|changeset| &changeset.receipt); + + if header.number >= config.spec_upgrades.byzantium { + verify_receipt(header.receipts_root, header.logs_bloom, receipts_iter)?; + } + // TODO Before Byzantium receipts contained state root that would mean that expensive operation + // as hashing that is needed for state root got calculated in every transaction + // This was replaced with is_success flag. + // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 Ok(transaction_change_set) } @@ -296,15 +310,15 @@ pub fn execute( let mut evm = EVM::new(); evm.database(db); - evm.env.cfg.chain_id = config.chain_id; + evm.env.cfg.chain_id = evmU256::from_limbs(config.chain_id.0); evm.env.cfg.spec_id = config.spec_upgrades.revm_spec(header.number); - evm.env.cfg.perf_all_precompiles_have_balance = true; + evm.env.cfg.perf_all_precompiles_have_balance = false; evm.env.cfg.perf_analyse_created_bytecodes = AnalysisKind::Raw; revm_wrap::fill_block_env(&mut evm.env.block, header); let mut cumulative_gas_used = 0; // output of verification - let mut changeset = Vec::with_capacity(transactions.len()); + let mut changesets = Vec::with_capacity(transactions.len()); for transaction in transactions.iter() { // The sum of the transaction’s gas limit, Tg, and the gas utilised in this block prior, @@ -321,10 +335,14 @@ pub fn execute( revm_wrap::fill_tx_env(&mut evm.env.tx, transaction); // Execute transaction. - let (revm::ExecutionResult { exit_reason, gas_used, logs, gas_refunded, .. }, state) = - evm.transact(); + let out = evm.transact(); - tracing::trace!(target:"evm","Executing transaction {:?}, gas:{gas_used} refund:{gas_refunded}",transaction.hash()); + // Useful for debugging + // let out = evm.inspect(revm::inspectors::CustomPrintTracer::default()); + // tracing::trace!(target:"evm","Executing transaction {:?}, \n:{out:?}: {:?} + // \nENV:{:?}",transaction.hash(),transaction,evm.env); + + let (revm::ExecutionResult { exit_reason, gas_used, logs, .. }, state) = out; // Fatal internal error. if exit_reason == revm::Return::FatalExternalError { @@ -332,10 +350,13 @@ pub fn execute( } // Success flag was added in `EIP-658: Embedding transaction status code in receipts`. + // TODO for verification (exit_reason): some error should return EVM error as the block with + // that transaction can have consensus error that would make block invalid. let is_success = match exit_reason { revm::return_ok!() => true, revm::return_revert!() => false, - e => return Err(Error::EVMError { error_code: e as u32 }), + _ => false, + //e => return Err(Error::EVMError { error_code: e as u32 }), }; // Add spend gas. @@ -344,14 +365,18 @@ pub fn execute( // Transform logs to reth format. let logs: Vec = logs .into_iter() - .map(|l| Log { address: l.address, topics: l.topics, data: l.data }) + .map(|l| Log { + address: H160(l.address.0), + topics: l.topics.into_iter().map(|h| H256(h.0)).collect(), + data: l.data, + }) .collect(); // commit state - let (state_diff, new_bytecodes) = commit_changes(evm.db().unwrap(), state); + let (changeset, new_bytecodes) = commit_changes(evm.db().unwrap(), state); // Push transaction changeset and calculte header bloom filter for receipt. - changeset.push(TransactionChangeSet { + changesets.push(TransactionChangeSet { receipt: Receipt { tx_type: transaction.tx_type(), success: is_success, @@ -359,7 +384,7 @@ pub fn execute( bloom: logs_bloom(logs.iter()), logs, }, - state_diff, + changeset, new_bytecodes, }) } @@ -373,7 +398,7 @@ pub fn execute( let beneficiary = evm .db .expect("It is set at the start of the function") - .basic(header.beneficiary) + .basic(B160(header.beneficiary.0)) .map_err(|_| Error::ProviderError)?; // NOTE: Related to Ethereum reward change, for other network this is probably going to be moved @@ -403,7 +428,7 @@ pub fn execute( } }); - Ok(ExecutionResult { changeset, block_reward }) + Ok(ExecutionResult { changesets, block_reward }) } #[cfg(test)] @@ -517,10 +542,10 @@ mod tests { // execute chain and verify receipts let out = execute_and_verify_receipt(&block.header, &transactions, &config, db).unwrap(); - assert_eq!(out.changeset.len(), 1, "Should executed one transaction"); + assert_eq!(out.changesets.len(), 1, "Should executed one transaction"); - let changeset = out.changeset[0].clone(); - assert_eq!(changeset.new_bytecodes.len(), 0, "Should have zero new bytecodes"); + let changesets = out.changesets[0].clone(); + assert_eq!(changesets.new_bytecodes.len(), 0, "Should have zero new bytecodes"); let account1 = H160(hex!("1000000000000000000000000000000000000000")); let _account1_info = Account { balance: 0x00.into(), nonce: 0x00, bytecode_hash: None }; @@ -536,17 +561,17 @@ mod tests { Account { balance: 0x3635c9adc5de996b46u128.into(), nonce: 0x01, bytecode_hash: None }; assert_eq!( - changeset.state_diff.get(&account1).unwrap().account, + changesets.changeset.get(&account1).unwrap().account, AccountInfoChangeSet::NoChange, "No change to account" ); assert_eq!( - changeset.state_diff.get(&account2).unwrap().account, + changesets.changeset.get(&account2).unwrap().account, AccountInfoChangeSet::Created { new: account2_info }, "New acccount" ); assert_eq!( - changeset.state_diff.get(&account3).unwrap().account, + changesets.changeset.get(&account3).unwrap().account, AccountInfoChangeSet::Changed { old: account3_old_info, new: account3_info }, "Change to account state" ); @@ -563,10 +588,10 @@ mod tests { )])) ); - assert_eq!(changeset.new_bytecodes.len(), 0, "No new bytecodes"); + assert_eq!(changesets.new_bytecodes.len(), 0, "No new bytecodes"); // check torage - let storage = &changeset.state_diff.get(&account1).unwrap().storage; + let storage = &changesets.changeset.get(&account1).unwrap().storage; assert_eq!(storage.len(), 1, "Only one storage change"); assert_eq!( storage.get(&1.into()), diff --git a/crates/executor/src/revm_wrap.rs b/crates/executor/src/revm_wrap.rs index 3dc6575638..f2febd135a 100644 --- a/crates/executor/src/revm_wrap.rs +++ b/crates/executor/src/revm_wrap.rs @@ -6,7 +6,7 @@ use reth_primitives::{ use reth_provider::StateProvider; use revm::{ db::{CacheDB, DatabaseRef}, - BlockEnv, TransactTo, TxEnv, + BlockEnv, TransactTo, TxEnv, B160, B256, U256 as evmU256, }; /// SubState of database. Uses revm internal cache with binding to reth DbExecutor trait. @@ -40,45 +40,45 @@ impl State { impl DatabaseRef for State { type Error = Error; - fn basic(&self, address: H160) -> Result, Self::Error> { - Ok(self.0.basic_account(address)?.map(|account| revm::AccountInfo { - balance: account.balance, + fn basic(&self, address: B160) -> Result, Self::Error> { + Ok(self.0.basic_account(H160(address.0))?.map(|account| revm::AccountInfo { + balance: evmU256::from_limbs(account.balance.0), nonce: account.nonce, - code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code_hash: B256(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0), code: None, })) } - fn code_by_hash(&self, code_hash: H256) -> Result { - let bytecode = self.0.bytecode_by_hash(code_hash)?.unwrap_or_default(); + fn code_by_hash(&self, code_hash: B256) -> Result { + let bytecode = self.0.bytecode_by_hash(H256(code_hash.0))?.unwrap_or_default(); Ok(revm::Bytecode::new_raw(bytecode.0)) } - fn storage(&self, address: H160, index: U256) -> Result { - let mut h_index = H256::zero(); - index.to_big_endian(h_index.as_bytes_mut()); - - Ok(self.0.storage(address, h_index)?.unwrap_or_default()) + fn storage(&self, address: B160, index: evmU256) -> Result { + let index = H256(index.to_be_bytes()); + let ret = + evmU256::from_limbs(self.0.storage(H160(address.0), index)?.unwrap_or_default().0); + Ok(ret) } - fn block_hash(&self, number: U256) -> Result { - Ok(self.0.block_hash(number)?.unwrap_or_default()) + fn block_hash(&self, number: evmU256) -> Result { + Ok(B256(self.0.block_hash(U256(*number.as_limbs()))?.unwrap_or_default().0)) } } /// Fill block environment from Block. pub fn fill_block_env(block_env: &mut BlockEnv, header: &Header) { - block_env.number = header.number.into(); - block_env.coinbase = header.beneficiary; - block_env.timestamp = header.timestamp.into(); - block_env.difficulty = header.difficulty; - block_env.basefee = header.base_fee_per_gas.unwrap_or_default().into(); - block_env.gas_limit = header.gas_limit.into(); + block_env.number = evmU256::from(header.number); + block_env.coinbase = B160(header.beneficiary.0); + block_env.timestamp = evmU256::from(header.timestamp); + block_env.difficulty = evmU256::from_limbs(header.difficulty.0); + block_env.basefee = evmU256::from(header.base_fee_per_gas.unwrap_or_default()); + block_env.gas_limit = evmU256::from(header.gas_limit); } /// Fill transaction environment from Transaction. pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) { - tx_env.caller = transaction.signer(); + tx_env.caller = B160(transaction.signer().0); match transaction.as_ref().as_ref() { Transaction::Legacy(TxLegacy { nonce, @@ -90,13 +90,13 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere input, }) => { tx_env.gas_limit = *gas_limit; - tx_env.gas_price = (*gas_price).into(); + tx_env.gas_price = evmU256::from(*gas_price); tx_env.gas_priority_fee = None; tx_env.transact_to = match to { - TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Call(to) => TransactTo::Call(B160(to.0)), TransactionKind::Create => TransactTo::create(), }; - tx_env.value = (*value).into(); + tx_env.value = evmU256::from(*value); tx_env.data = input.0.clone(); tx_env.chain_id = *chain_id; tx_env.nonce = Some(*nonce); @@ -112,13 +112,13 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere access_list, }) => { tx_env.gas_limit = *gas_limit; - tx_env.gas_price = (*gas_price).into(); + tx_env.gas_price = evmU256::from(*gas_price); tx_env.gas_priority_fee = None; tx_env.transact_to = match to { - TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Call(to) => TransactTo::Call(B160(to.0)), TransactionKind::Create => TransactTo::create(), }; - tx_env.value = (*value).into(); + tx_env.value = evmU256::from(*value); tx_env.data = input.0.clone(); tx_env.chain_id = Some(*chain_id); tx_env.nonce = Some(*nonce); @@ -127,8 +127,11 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere .iter() .map(|l| { ( - l.address, - l.storage_keys.iter().map(|k| U256::from_big_endian(k.as_ref())).collect(), + B160(l.address.0), + l.storage_keys + .iter() + .map(|k| evmU256::from_be_bytes(k.to_fixed_bytes())) + .collect(), ) }) .collect(); @@ -145,13 +148,13 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere access_list, }) => { tx_env.gas_limit = *gas_limit; - tx_env.gas_price = (*max_fee_per_gas).into(); - tx_env.gas_priority_fee = Some((*max_priority_fee_per_gas).into()); + tx_env.gas_price = evmU256::from(*max_fee_per_gas); + tx_env.gas_priority_fee = Some(evmU256::from(*max_priority_fee_per_gas)); tx_env.transact_to = match to { - TransactionKind::Call(to) => TransactTo::Call(*to), + TransactionKind::Call(to) => TransactTo::Call(B160(to.0)), TransactionKind::Create => TransactTo::create(), }; - tx_env.value = (*value).into(); + tx_env.value = evmU256::from(*value); tx_env.data = input.0.clone(); tx_env.chain_id = Some(*chain_id); tx_env.nonce = Some(*nonce); @@ -160,8 +163,11 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere .iter() .map(|l| { ( - l.address, - l.storage_keys.iter().map(|k| U256::from_big_endian(k.as_ref())).collect(), + B160(l.address.0), + l.storage_keys + .iter() + .map(|k| evmU256::from_be_bytes(k.to_fixed_bytes())) + .collect(), ) }) .collect(); @@ -172,25 +178,22 @@ pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovere /// Check equality between [`reth_primitives::Log`] and [`revm::Log`] pub fn is_log_equal(revm_log: &revm::Log, reth_log: &reth_primitives::Log) -> bool { revm_log.topics.len() == reth_log.topics.len() && - revm_log.address == reth_log.address && + revm_log.address.0 == reth_log.address.0 && revm_log.data == reth_log.data && !revm_log .topics .iter() .zip(reth_log.topics.iter()) - .any(|(revm_topic, reth_topic)| revm_topic != reth_topic) + .any(|(revm_topic, reth_topic)| revm_topic.0 != reth_topic.0) } /// Create reth primitive [Account] from [revm::AccountInfo]. /// Check if revm bytecode hash is [KECCAK_EMPTY] and put None to reth [Account] pub fn to_reth_acc(revm_acc: &revm::AccountInfo) -> Account { + let code_hash = H256(revm_acc.code_hash.0); Account { - balance: revm_acc.balance, + balance: U256(*revm_acc.balance.as_limbs()), nonce: revm_acc.nonce, - bytecode_hash: if revm_acc.code_hash == KECCAK_EMPTY { - None - } else { - Some(revm_acc.code_hash) - }, + bytecode_hash: if code_hash == KECCAK_EMPTY { None } else { Some(code_hash) }, } } diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index a8559750d2..53fdc04bb1 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -2,7 +2,6 @@ use crate::{keccak256, Header, Log, Receipt, TransactionSigned, H256}; use hash_db::Hasher; use hex_literal::hex; use plain_hasher::PlainHasher; -use reth_rlp::Encodable; use triehash::ordered_trie_root; /// Keccak-256 hash of the RLP of an empty list, KEC("\xc0"). @@ -46,12 +45,12 @@ pub fn calculate_transaction_root<'a>( pub fn calculate_receipt_root<'a>(receipts: impl Iterator) -> H256 { ordered_trie_root::(receipts.into_iter().map(|receipt| { let mut receipt_rlp = Vec::new(); - receipt.encode(&mut receipt_rlp); + receipt.encode_inner(&mut receipt_rlp, false); receipt_rlp })) } -/// Calculates the log root for a header. +/// Calculates the log root for headers. pub fn calculate_log_root<'a>(logs: impl Iterator + Clone) -> H256 { //https://github.com/ethereum/go-ethereum/blob/356bbe343a30789e77bb38f25983c8f2f2bfbb47/cmd/evm/internal/t8ntool/execution.go#L255 let mut logs_rlp = Vec::new(); @@ -69,7 +68,13 @@ pub fn calculate_ommers_root<'a>(ommers: impl Iterator + Clon #[cfg(test)] mod tests { - use crate::{hex_literal::hex, proofs::calculate_transaction_root, Block}; + + use crate::{ + hex_literal::hex, + proofs::{calculate_receipt_root, calculate_transaction_root}, + Block, Bloom, Log, Receipt, TxType, H160, H256, + }; + use bytes::Bytes; use reth_rlp::Decodable; #[test] @@ -81,4 +86,23 @@ mod tests { let tx_root = calculate_transaction_root(block.body.iter()); assert_eq!(block.transactions_root, tx_root, "Should be same"); } + + #[test] + fn check_receipt_root() { + let logs = vec![Log { address: H160::zero(), topics: vec![], data: Bytes::default() }]; + let bloom = Bloom(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); + let receipt = Receipt { + tx_type: TxType::EIP2930, + success: true, + cumulative_gas_used: 102068, + bloom, + logs, + }; + let receipt = vec![receipt]; + let root = calculate_receipt_root(receipt.iter()); + assert_eq!( + root, + H256(hex!("fe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")) + ); + } } diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 487263bf38..a103ede182 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -36,7 +36,7 @@ impl Receipt { } /// Encodes the receipt data. - fn encode_receipt(&self, out: &mut dyn BufMut) { + fn encode_fields(&self, out: &mut dyn BufMut) { self.receipt_rlp_header().encode(out); self.success.encode(out); self.cumulative_gas_used.encode(out); @@ -44,6 +44,34 @@ impl Receipt { self.logs.encode(out); } + /// Encode receipt with or without the header data. + pub fn encode_inner(&self, out: &mut dyn BufMut, with_header: bool) { + if matches!(self.tx_type, TxType::Legacy) { + self.encode_fields(out); + return + } + + let mut payload = BytesMut::new(); + self.encode_fields(&mut payload); + + if with_header { + let payload_length = payload.len() + 1; + let header = reth_rlp::Header { list: false, payload_length }; + header.encode(out); + } + + match self.tx_type { + TxType::EIP2930 => { + out.put_u8(0x01); + } + TxType::EIP1559 => { + out.put_u8(0x02); + } + _ => unreachable!("legacy handled; qed."), + } + out.put_slice(payload.as_ref()); + } + /// Returns the length of the receipt data. fn receipt_length(&self) -> usize { let rlp_head = self.receipt_rlp_header(); @@ -90,29 +118,7 @@ impl Encodable for Receipt { payload_len } fn encode(&self, out: &mut dyn BufMut) { - if matches!(self.tx_type, TxType::Legacy) { - self.encode_receipt(out); - return - } - - let mut payload = BytesMut::new(); - self.encode_receipt(&mut payload); - let payload_length = payload.len() + 1; - - let header = reth_rlp::Header { list: false, payload_length }; - header.encode(out); - - match self.tx_type { - TxType::EIP2930 => { - out.put_u8(0x01); - } - TxType::EIP1559 => { - out.put_u8(0x02); - } - _ => unreachable!("legacy handled; qed."), - } - - out.put_slice(payload.as_ref()); + self.encode_inner(out, true) } } @@ -153,15 +159,15 @@ impl Decodable for Receipt { #[cfg(test)] mod tests { use super::*; - use crate::{Address, H256}; - use ethers_core::{types::Bytes, utils::hex}; + use crate::{hex_literal::hex, Address, H256}; + use ethers_core::types::Bytes; use reth_rlp::{Decodable, Encodable}; use std::str::FromStr; #[test] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn encode_legacy_receipt() { - let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + let expected = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"); let mut data = vec![]; let receipt = Receipt { @@ -195,7 +201,7 @@ mod tests { #[test] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn decode_legacy_receipt() { - let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + let data = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"); // EIP658Receipt let expected = Receipt { diff --git a/crates/stages/src/db.rs b/crates/stages/src/db.rs index 6ca8c3ca98..2e3044c255 100644 --- a/crates/stages/src/db.rs +++ b/crates/stages/src/db.rs @@ -86,8 +86,7 @@ where /// Panics if an inner transaction does not exist. This should never be the case unless /// [Transaction::close] was called without following up with a call to [Transaction::open]. pub fn commit(&mut self) -> Result { - let success = - self.tx.take().expect("Tried committing a non-existent transaction").commit()?; + let success = if let Some(tx) = self.tx.take() { tx.commit()? } else { false }; self.tx = Some(self.db.tx_mut()?); Ok(success) } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 87eb2e1524..95192048dc 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -133,6 +133,8 @@ impl Stage for ExecutionStage { // Fetch transactions, execute them and generate results let mut block_change_patches = Vec::with_capacity(canonical_batch.len()); for (header, body) in block_batch.iter() { + let num = header.number; + tracing::trace!(target: "sync::stages::execution", ?num, "Execute block."); // iterate over all transactions let mut tx_walker = tx_cursor.walk(body.start_tx_id)?; let mut transactions = Vec::with_capacity(body.tx_count as usize); @@ -175,7 +177,7 @@ impl Stage for ExecutionStage { let state_provider = SubState::new(State::new(StateProviderImplRefLatest::new(&**tx))); trace!(target: "sync::stages::execution", number = header.number, txs = recovered_transactions.len(), "Executing block"); - let change_set = std::thread::scope(|scope| { + let changeset = std::thread::scope(|scope| { let handle = std::thread::Builder::new() .stack_size(50 * 1024 * 1024) .spawn_scoped(scope, || { @@ -193,7 +195,7 @@ impl Stage for ExecutionStage { handle.join().expect("Expects for thread to not panic") }) .map_err(|error| StageError::ExecutionError { block: header.number, error })?; - block_change_patches.push(change_set); + block_change_patches.push(changeset); } // Get last tx count so that we can know amount of transaction in the block. @@ -203,9 +205,9 @@ impl Stage for ExecutionStage { // apply changes to plain database. for results in block_change_patches.into_iter() { // insert state change set - for result in results.changeset.into_iter() { + for result in results.changesets.into_iter() { // TODO insert to transitionId to tx_index - for (address, account_change_set) in result.state_diff.into_iter() { + for (address, account_change_set) in result.changeset.into_iter() { let AccountChangeSet { account, wipe_storage, storage } = account_change_set; // apply account change to db. Updates AccountChangeSet and PlainAccountState // tables. @@ -230,13 +232,17 @@ impl Stage for ExecutionStage { storage_id.clone(), StorageEntry { key: hkey, value: old_value }, )?; + tracing::debug!( + target = "sync::stages::execution", + "{address} setting storage:{key} ({old_value} -> {new_value})" + ); - if new_value.is_zero() { - tx.delete::( - address, - Some(StorageEntry { key: hkey, value: old_value }), - )?; - } else { + // Always delete old value as duplicate table put will not override it + tx.delete::( + address, + Some(StorageEntry { key: hkey, value: old_value }), + )?; + if !new_value.is_zero() { tx.put::( address, StorageEntry { key: hkey, value: new_value }, @@ -259,7 +265,6 @@ impl Stage for ExecutionStage { } // If there is block reward we will add account changeset to db - // TODO add apply_block_reward_changeset to db tx fn which maybe takes an option. if let Some(block_reward_changeset) = results.block_reward { // we are sure that block reward index is present. for (address, changeset) in block_reward_changeset.into_iter() { diff --git a/crates/storage/db/src/abstraction/cursor.rs b/crates/storage/db/src/abstraction/cursor.rs index 06f319bbb7..fda0c2c9cf 100644 --- a/crates/storage/db/src/abstraction/cursor.rs +++ b/crates/storage/db/src/abstraction/cursor.rs @@ -50,6 +50,9 @@ pub trait DbDupCursorRO<'tx, T: DupSort> { /// Returns the next `value` of a duplicate `key`. fn next_dup_val(&mut self) -> ValueOnlyResult; + /// Seek by key and subkey + fn seek_by_key_subkey(&mut self, key: T::Key, value: T::SubKey) -> ValueOnlyResult; + /// Returns an iterator starting at a key greater or equal than `start_key` of a DupSort /// table. fn walk_dup<'cursor>( diff --git a/crates/storage/db/src/abstraction/mock.rs b/crates/storage/db/src/abstraction/mock.rs index e07a71eee6..084f9015d9 100644 --- a/crates/storage/db/src/abstraction/mock.rs +++ b/crates/storage/db/src/abstraction/mock.rs @@ -151,6 +151,14 @@ impl<'tx, T: DupSort> DbDupCursorRO<'tx, T> for CursorMock { todo!() } + fn seek_by_key_subkey( + &mut self, + _key: ::Key, + _subkey: ::SubKey, + ) -> ValueOnlyResult { + todo!() + } + fn walk_dup<'cursor>( &'cursor mut self, _key: ::Key, diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index 74a4a74798..586cba7f86 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -103,6 +103,18 @@ impl<'tx, K: TransactionKind, T: DupSort> DbDupCursorRO<'tx, T> for Cursor<'tx, self.inner.next_dup().map_err(|e| Error::Read(e.into()))?.map(decode_value::).transpose() } + fn seek_by_key_subkey( + &mut self, + key: ::Key, + subkey: ::SubKey, + ) -> ValueOnlyResult { + self.inner + .get_both_range(key.encode().as_ref(), subkey.encode().as_ref()) + .map_err(|e| Error::Read(e.into()))? + .map(decode_one::) + .transpose() + } + /// Returns an iterator starting at a key greater or equal than `start_key` of a DUPSORT table. fn walk_dup<'cursor>( &'cursor mut self, diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 7e9a47b77c..5fbc98b2c3 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -367,6 +367,95 @@ mod tests { } } + #[test] + fn db_iterate_over_all_dup_values() { + let env = test_utils::create_test_db::(EnvKind::RW); + let key1 = Address::from_str("0x1111111111111111111111111111111111111111") + .expect(ERROR_ETH_ADDRESS); + let key2 = Address::from_str("0x2222222222222222222222222222222222222222") + .expect(ERROR_ETH_ADDRESS); + + // PUT key1 (0,0) + let value00 = StorageEntry::default(); + env.update(|tx| tx.put::(key1, value00.clone()).expect(ERROR_PUT)) + .unwrap(); + + // PUT key1 (1,1) + let value11 = StorageEntry { key: H256::from_low_u64_be(1), value: U256::from(1) }; + env.update(|tx| tx.put::(key1, value11.clone()).expect(ERROR_PUT)) + .unwrap(); + + // PUT key2 (2,2) + let value22 = StorageEntry { key: H256::from_low_u64_be(2), value: U256::from(2) }; + env.update(|tx| tx.put::(key2, value22.clone()).expect(ERROR_PUT)) + .unwrap(); + + // Iterate with walk_dup + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor_dup::().unwrap(); + let first = cursor.first().unwrap().unwrap(); + let mut walker = cursor.walk_dup(first.0, first.1.key).unwrap(); + + // Notice that value11 and value22 have been ordered in the DB. + assert_eq!(Some(Ok((key1, value00.clone()))), walker.next()); + assert_eq!(Some(Ok((key1, value11.clone()))), walker.next()); + // NOTE: Dup cursor does NOT iterates on all values but only on duplicated values of the + // same key. assert_eq!(Ok(Some(value22.clone())), walker.next()); + assert_eq!(None, walker.next()); + } + + // Iterate by using `walk` + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor_dup::().unwrap(); + let first = cursor.first().unwrap().unwrap(); + let mut walker = cursor.walk(first.0).unwrap(); + assert_eq!(Some(Ok((key1, value00))), walker.next()); + assert_eq!(Some(Ok((key1, value11))), walker.next()); + assert_eq!(Some(Ok((key2, value22))), walker.next()); + } + } + + #[test] + fn dup_value_with_same_subkey() { + let env = test_utils::create_test_db::(EnvKind::RW); + let key1 = Address::from_str("0x1111111111111111111111111111111111111111") + .expect(ERROR_ETH_ADDRESS); + + // PUT key1 (0,1) + let value01 = StorageEntry { key: H256::from_low_u64_be(0), value: U256::from(1) }; + env.update(|tx| tx.put::(key1, value01.clone()).expect(ERROR_PUT)) + .unwrap(); + + // PUT key1 (0,0) + let value00 = StorageEntry::default(); + env.update(|tx| tx.put::(key1, value00.clone()).expect(ERROR_PUT)) + .unwrap(); + + // Iterate with walk + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor_dup::().unwrap(); + let first = cursor.first().unwrap().unwrap(); + let mut walker = cursor.walk(first.0).unwrap(); + + // NOTE: Both values are present + assert_eq!(Some(Ok((key1, value00.clone()))), walker.next()); + assert_eq!(Some(Ok((key1, value01.clone()))), walker.next()); + assert_eq!(None, walker.next()); + } + + // seek_by_key_subkey + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor_dup::().unwrap(); + + // NOTE: There are two values with same SubKey but only first one is shown + assert_eq!(Ok(Some(value00.clone())), cursor.seek_by_key_subkey(key1, value00.key)); + } + } + #[test] fn db_sharded_key() { let db: Arc> = test_utils::create_test_db(EnvKind::RW); diff --git a/crates/storage/provider/src/db_provider/storage.rs b/crates/storage/provider/src/db_provider/storage.rs index ab15671f6b..d4744a785e 100644 --- a/crates/storage/provider/src/db_provider/storage.rs +++ b/crates/storage/provider/src/db_provider/storage.rs @@ -225,16 +225,10 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for StateProviderImplRefLatest<'a, 'b, /// Get storage. fn storage(&self, account: Address, storage_key: StorageKey) -> Result> { let mut cursor = self.db.cursor_dup::()?; - if let Some((_, entry)) = cursor.seek_exact(account)? { + if let Some(entry) = cursor.seek_by_key_subkey(account, storage_key)? { if entry.key == storage_key { return Ok(Some(entry.value)) } - - if let Some((_, entry)) = cursor.seek(storage_key)? { - if entry.key == storage_key { - return Ok(Some(entry.value)) - } - } } Ok(None) }