mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
189 lines
6.8 KiB
Rust
189 lines
6.8 KiB
Rust
//! Test runners for `BlockchainTests` in <https://github.com/ethereum/tests>
|
|
|
|
use crate::{
|
|
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, HashingWriter, ProviderFactory};
|
|
use reth_stages::{stages::ExecutionStage, ExecInput, Stage};
|
|
use std::{collections::BTreeMap, fs, path::Path, sync::Arc};
|
|
|
|
/// A handler for the blockchain test suite.
|
|
#[derive(Debug)]
|
|
pub struct BlockchainTests {
|
|
suite: String,
|
|
}
|
|
|
|
impl BlockchainTests {
|
|
/// Create a new handler for a subset of the blockchain test suite.
|
|
pub fn new(suite: String) -> Self {
|
|
Self { suite }
|
|
}
|
|
}
|
|
|
|
impl Suite for BlockchainTests {
|
|
type Case = BlockchainTestCase;
|
|
|
|
fn suite_name(&self) -> String {
|
|
format!("BlockchainTests/{}", self.suite)
|
|
}
|
|
}
|
|
|
|
/// An Ethereum blockchain test.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct BlockchainTestCase {
|
|
tests: BTreeMap<String, BlockchainTest>,
|
|
skip: bool,
|
|
}
|
|
|
|
impl Case for BlockchainTestCase {
|
|
fn load(path: &Path) -> Result<Self, Error> {
|
|
Ok(BlockchainTestCase {
|
|
tests: {
|
|
let s = fs::read_to_string(path)
|
|
.map_err(|error| Error::Io { path: path.into(), error })?;
|
|
serde_json::from_str(&s)
|
|
.map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
|
|
},
|
|
skip: should_skip(path),
|
|
})
|
|
}
|
|
|
|
// TODO: Clean up
|
|
fn run(&self) -> Result<(), Error> {
|
|
if self.skip {
|
|
return Err(Error::Skipped)
|
|
}
|
|
|
|
for case in self.tests.values() {
|
|
if matches!(
|
|
case.network,
|
|
ForkSpec::ByzantiumToConstantinopleAt5 |
|
|
ForkSpec::Constantinople |
|
|
ForkSpec::ConstantinopleFix |
|
|
ForkSpec::MergeEOF |
|
|
ForkSpec::MergeMeterInitCode |
|
|
ForkSpec::MergePush0 |
|
|
ForkSpec::Unknown
|
|
) {
|
|
continue
|
|
}
|
|
|
|
// Create the database
|
|
let db = create_test_rw_db();
|
|
let factory = ProviderFactory::new(db.as_ref(), Arc::new(case.network.clone().into()));
|
|
let provider = factory.provider_rw().unwrap();
|
|
|
|
// Insert test state
|
|
provider.insert_block(
|
|
SealedBlock::new(case.genesis_block_header.clone().into(), BlockBody::default()),
|
|
None,
|
|
None,
|
|
)?;
|
|
case.pre.write_to_db(provider.tx_ref())?;
|
|
|
|
let mut last_block = None;
|
|
for block in case.blocks.iter() {
|
|
let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?;
|
|
provider.insert_block(decoded.clone(), None, None)?;
|
|
last_block = Some(decoded);
|
|
}
|
|
|
|
// Call execution stage
|
|
{
|
|
let mut stage = ExecutionStage::new_with_factory(reth_revm::Factory::new(
|
|
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, checkpoint: None }).await;
|
|
});
|
|
}
|
|
|
|
// Validate post state
|
|
if let Some(state) = &case.post_state {
|
|
for (&address, account) in state.iter() {
|
|
account.assert_db(address, provider.tx_ref())?;
|
|
}
|
|
} 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(())
|
|
}
|
|
}
|
|
|
|
/// Returns whether the test at the given path should be skipped.
|
|
///
|
|
/// Some tests are edge cases that cannot happen on mainnet, while others are skipped for
|
|
/// convenience (e.g. they take a long time to run) or are temporarily disabled.
|
|
///
|
|
/// The reason should be documented in a comment above the file name(s).
|
|
pub fn should_skip(path: &Path) -> bool {
|
|
let path_str = path.to_str().expect("Path is not valid UTF-8");
|
|
let name = path.file_name().unwrap().to_str().unwrap();
|
|
matches!(
|
|
name,
|
|
// funky test with `bigint 0x00` value in json :) not possible to happen on mainnet and require
|
|
// custom json parser. https://github.com/ethereum/tests/issues/971
|
|
| "ValueOverflow.json"
|
|
|
|
// txbyte is of type 02 and we dont parse tx bytes for this test to fail.
|
|
| "typeTwoBerlin.json"
|
|
|
|
// Test checks if nonce overflows. We are handling this correctly but we are not parsing
|
|
// exception in testsuite There are more nonce overflow tests that are in internal
|
|
// call/create, and those tests are passing and are enabled.
|
|
| "CreateTransactionHighNonce.json"
|
|
|
|
// Test check if gas price overflows, we handle this correctly but does not match tests specific
|
|
// exception.
|
|
| "HighGasPrice.json"
|
|
|
|
// Skip test where basefee/accesslist/difficulty is present but it shouldn't be supported in
|
|
// London/Berlin/TheMerge. https://github.com/ethereum/tests/blob/5b7e1ab3ffaf026d99d20b17bb30f533a2c80c8b/GeneralStateTests/stExample/eip1559.json#L130
|
|
// It is expected to not execute these tests.
|
|
| "accessListExample.json"
|
|
| "basefeeExample.json"
|
|
| "eip1559.json"
|
|
| "mergeTest.json"
|
|
|
|
// These tests are passing, but they take a lot of time to execute so we are going to skip them.
|
|
| "loopExp.json"
|
|
| "Call50000_sha256.json"
|
|
| "static_Call50000_sha256.json"
|
|
| "loopMul.json"
|
|
| "CALLBlake2f_MaxRounds.json"
|
|
| "shiftCombinations.json"
|
|
)
|
|
// Ignore outdated EOF tests that haven't been updated for Cancun yet.
|
|
|| path_contains(path_str, &["EIPTests", "stEOF"])
|
|
}
|
|
|
|
/// `str::contains` but for a path. Takes into account the OS path separator (`/` or `\`).
|
|
fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
|
|
let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
|
|
path_str.contains(&rhs)
|
|
}
|