feat(ef-tests): validate state root (#4995)

This commit is contained in:
Roman Krasiuk
2023-10-12 16:44:41 +03:00
committed by GitHub
parent 422f38ac06
commit d6ea90fd33
4 changed files with 44 additions and 39 deletions

View File

@ -100,7 +100,7 @@ cargo test --workspace --features geth-tests
# Note: Requires cloning https://github.com/ethereum/tests # Note: Requires cloning https://github.com/ethereum/tests
# #
# cd testing/ef-tests && git clone https://github.com/ethereum/tests 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`. We recommend using [`cargo nextest`](https://nexte.st/) to speed up testing. With nextest installed, simply substitute `cargo test` with `cargo nextest run`.

View File

@ -1,13 +1,13 @@
//! Test runners for `BlockchainTests` in <https://github.com/ethereum/tests> //! Test runners for `BlockchainTests` in <https://github.com/ethereum/tests>
use crate::{ use crate::{
models::{BlockchainTest, ForkSpec, RootOrState}, models::{BlockchainTest, ForkSpec},
Case, Error, Suite, Case, Error, Suite,
}; };
use alloy_rlp::Decodable; use alloy_rlp::Decodable;
use reth_db::test_utils::create_test_rw_db; use reth_db::test_utils::create_test_rw_db;
use reth_primitives::{BlockBody, SealedBlock}; use reth_primitives::{BlockBody, SealedBlock};
use reth_provider::{BlockWriter, ProviderFactory}; use reth_provider::{BlockWriter, HashingWriter, ProviderFactory};
use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; use reth_stages::{stages::ExecutionStage, ExecInput, Stage};
use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; use std::{collections::BTreeMap, fs, path::Path, sync::Arc};
@ -88,8 +88,8 @@ impl Case for BlockchainTestCase {
let mut last_block = None; let mut last_block = None;
for block in case.blocks.iter() { for block in case.blocks.iter() {
let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?;
last_block = Some(decoded.number); provider.insert_block(decoded.clone(), None, None)?;
provider.insert_block(decoded, None, None)?; last_block = Some(decoded);
} }
// Call execution stage // Call execution stage
@ -98,31 +98,36 @@ impl Case for BlockchainTestCase {
Arc::new(case.network.clone().into()), Arc::new(case.network.clone().into()),
)); ));
let target = last_block.as_ref().map(|b| b.number);
tokio::runtime::Builder::new_current_thread() tokio::runtime::Builder::new_current_thread()
.build() .build()
.expect("Could not build tokio RT") .expect("Could not build tokio RT")
.block_on(async { .block_on(async {
// ignore error // ignore error
let _ = stage let _ =
.execute(&provider, ExecInput { target: last_block, checkpoint: None }) stage.execute(&provider, ExecInput { target, checkpoint: None }).await;
.await;
}); });
} }
// Validate post state // Validate post state
match &case.post_state { if let Some(state) = &case.post_state {
Some(RootOrState::Root(root)) => {
// TODO: We should really check the state root here...
println!("Post-state root: #{root:?}")
}
Some(RootOrState::State(state)) => {
for (&address, account) in state.iter() { for (&address, account) in state.iter() {
account.assert_db(address, provider.tx_ref())?; account.assert_db(address, provider.tx_ref())?;
} }
} } else if let Some(expected_state_root) = case.post_state_hash {
None => println!("No post-state"), // `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); drop(provider);
} }
Ok(()) Ok(())

View File

@ -26,7 +26,9 @@ pub struct BlockchainTest {
/// Block data. /// Block data.
pub blocks: Vec<Block>, pub blocks: Vec<Block>,
/// The expected post state. /// The expected post state.
pub post_state: Option<RootOrState>, pub post_state: Option<BTreeMap<Address, Account>>,
/// The expected post state merkle root.
pub post_state_hash: Option<B256>,
/// The test pre-state. /// The test pre-state.
pub pre: State, pub pre: State,
/// Hash of the best block. /// Hash of the best block.
@ -153,23 +155,28 @@ impl State {
Tx: DbTxMut<'a>, Tx: DbTxMut<'a>,
{ {
for (&address, account) in self.0.iter() { for (&address, account) in self.0.iter() {
let hashed_address = keccak256(address);
let has_code = !account.code.is_empty(); let has_code = !account.code.is_empty();
let code_hash = has_code.then(|| keccak256(&account.code)); let code_hash = has_code.then(|| keccak256(&account.code));
tx.put::<tables::PlainAccountState>( let reth_account = RethAccount {
address,
RethAccount {
balance: account.balance.0, balance: account.balance.0,
nonce: account.nonce.0.to::<u64>(), nonce: account.nonce.0.to::<u64>(),
bytecode_hash: code_hash, bytecode_hash: code_hash,
}, };
)?; tx.put::<tables::PlainAccountState>(address, reth_account)?;
tx.put::<tables::HashedAccount>(hashed_address, reth_account)?;
if let Some(code_hash) = code_hash { if let Some(code_hash) = code_hash {
tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.clone()))?; tx.put::<tables::Bytecodes>(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::<tables::PlainStorageState>( tx.put::<tables::PlainStorageState>(
address, address,
StorageEntry { key: B256::from_slice(&k.0.to_be_bytes::<32>()), value: v.0 }, StorageEntry { key: storage_key, value: v.0 },
)?;
tx.put::<tables::HashedStorage>(
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<Address, Account>),
}
/// An account. /// An account.
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] #[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]

View File

@ -17,6 +17,9 @@ pub enum Error {
/// The test was skipped /// The test was skipped
#[error("Test was skipped")] #[error("Test was skipped")]
Skipped, Skipped,
/// No post state found in test
#[error("No post state found for validation")]
MissingPostState,
/// An IO error occurred /// An IO error occurred
#[error("An error occurred interacting with the file system at {path}: {error}")] #[error("An error occurred interacting with the file system at {path}: {error}")]
Io { Io {