diff --git a/README.md b/README.md index dcb0ea323626..8dd1c987d7a4 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ cargo test --workspace --features geth-tests # Note: Requires cloning https://github.com/ethereum/tests # # cd testing/ef-tests && git clone https://github.com/ethereum/tests ethereum-tests -cargo test --workspace --features ef-tests +cargo test -p ef-tests --features ef-tests ``` We recommend using [`cargo nextest`](https://nexte.st/) to speed up testing. With nextest installed, simply substitute `cargo test` with `cargo nextest run`. diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 6b5c10aba0b7..6b2b08da11ba 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -1,13 +1,13 @@ //! Test runners for `BlockchainTests` in use crate::{ - models::{BlockchainTest, ForkSpec, RootOrState}, + models::{BlockchainTest, ForkSpec}, Case, Error, Suite, }; use alloy_rlp::Decodable; use reth_db::test_utils::create_test_rw_db; use reth_primitives::{BlockBody, SealedBlock}; -use reth_provider::{BlockWriter, ProviderFactory}; +use reth_provider::{BlockWriter, HashingWriter, ProviderFactory}; use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; @@ -88,8 +88,8 @@ impl Case for BlockchainTestCase { let mut last_block = None; for block in case.blocks.iter() { let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; - last_block = Some(decoded.number); - provider.insert_block(decoded, None, None)?; + provider.insert_block(decoded.clone(), None, None)?; + last_block = Some(decoded); } // Call execution stage @@ -98,31 +98,36 @@ impl Case for BlockchainTestCase { Arc::new(case.network.clone().into()), )); + let target = last_block.as_ref().map(|b| b.number); tokio::runtime::Builder::new_current_thread() .build() .expect("Could not build tokio RT") .block_on(async { // ignore error - let _ = stage - .execute(&provider, ExecInput { target: last_block, checkpoint: None }) - .await; + let _ = + stage.execute(&provider, ExecInput { target, checkpoint: None }).await; }); } // Validate post state - match &case.post_state { - Some(RootOrState::Root(root)) => { - // TODO: We should really check the state root here... - println!("Post-state root: #{root:?}") + if let Some(state) = &case.post_state { + for (&address, account) in state.iter() { + account.assert_db(address, provider.tx_ref())?; } - Some(RootOrState::State(state)) => { - for (&address, account) in state.iter() { - account.assert_db(address, provider.tx_ref())?; - } - } - None => println!("No post-state"), + } else if let Some(expected_state_root) = case.post_state_hash { + // `insert_hashes` will insert hashed data, compute the state root and match it to + // expected internally + let last_block = last_block.unwrap_or_default(); + provider.insert_hashes( + 0..=last_block.number, + last_block.hash, + expected_state_root, + )?; + } else { + return Err(Error::MissingPostState) } + // Drop provider without committing to the database. drop(provider); } Ok(()) diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index f656c0fee6e8..35dd9298602f 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -26,7 +26,9 @@ pub struct BlockchainTest { /// Block data. pub blocks: Vec, /// The expected post state. - pub post_state: Option, + pub post_state: Option>, + /// The expected post state merkle root. + pub post_state_hash: Option, /// The test pre-state. pub pre: State, /// Hash of the best block. @@ -153,23 +155,28 @@ impl State { Tx: DbTxMut<'a>, { for (&address, account) in self.0.iter() { + let hashed_address = keccak256(address); let has_code = !account.code.is_empty(); let code_hash = has_code.then(|| keccak256(&account.code)); - tx.put::( - address, - RethAccount { - balance: account.balance.0, - nonce: account.nonce.0.to::(), - bytecode_hash: code_hash, - }, - )?; + let reth_account = RethAccount { + balance: account.balance.0, + nonce: account.nonce.0.to::(), + bytecode_hash: code_hash, + }; + tx.put::(address, reth_account)?; + tx.put::(hashed_address, reth_account)?; if let Some(code_hash) = code_hash { tx.put::(code_hash, Bytecode::new_raw(account.code.clone()))?; } - account.storage.iter().try_for_each(|(k, v)| { + account.storage.iter().filter(|(_, v)| v.0 != U256::ZERO).try_for_each(|(k, v)| { + let storage_key = B256::from_slice(&k.0.to_be_bytes::<32>()); tx.put::( address, - StorageEntry { key: B256::from_slice(&k.0.to_be_bytes::<32>()), value: v.0 }, + StorageEntry { key: storage_key, value: v.0 }, + )?; + tx.put::( + hashed_address, + StorageEntry { key: keccak256(storage_key), value: v.0 }, ) })?; } @@ -186,16 +193,6 @@ impl Deref for State { } } -/// Merkle root hash or storage accounts. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] -#[serde(untagged)] -pub enum RootOrState { - /// If state is too big, only state root is present - Root(B256), - /// State - State(BTreeMap), -} - /// An account. #[derive(Debug, PartialEq, Eq, Deserialize, Clone)] #[serde(deny_unknown_fields)] diff --git a/testing/ef-tests/src/result.rs b/testing/ef-tests/src/result.rs index 2709bcab54da..e0d6ec501262 100644 --- a/testing/ef-tests/src/result.rs +++ b/testing/ef-tests/src/result.rs @@ -17,6 +17,9 @@ pub enum Error { /// The test was skipped #[error("Test was skipped")] Skipped, + /// No post state found in test + #[error("No post state found for validation")] + MissingPostState, /// An IO error occurred #[error("An error occurred interacting with the file system at {path}: {error}")] Io {