Skip to content

Commit

Permalink
feat(ef-tests): validate state root (paradigmxyz#4995)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkrasiuk authored Oct 12, 2023
1 parent 422f38a commit d6ea90f
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 39 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
39 changes: 22 additions & 17 deletions testing/ef-tests/src/cases/blockchain_test.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Test runners for `BlockchainTests` in <https://github.com/ethereum/tests>
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};

Expand Down Expand Up @@ -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
Expand All @@ -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(())
Expand Down
39 changes: 18 additions & 21 deletions testing/ef-tests/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pub struct BlockchainTest {
/// Block data.
pub blocks: Vec<Block>,
/// 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.
pub pre: State,
/// Hash of the best block.
Expand Down Expand Up @@ -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::<tables::PlainAccountState>(
address,
RethAccount {
balance: account.balance.0,
nonce: account.nonce.0.to::<u64>(),
bytecode_hash: code_hash,
},
)?;
let reth_account = RethAccount {
balance: account.balance.0,
nonce: account.nonce.0.to::<u64>(),
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 {
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>(
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 },
)
})?;
}
Expand All @@ -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.
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
Expand Down
3 changes: 3 additions & 0 deletions testing/ef-tests/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit d6ea90f

Please sign in to comment.