mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
222 lines
8.7 KiB
Rust
222 lines
8.7 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 rayon::iter::{ParallelBridge, ParallelIterator};
|
|
use reth_primitives::{BlockBody, SealedBlock, StaticFileSegment};
|
|
use reth_provider::{
|
|
providers::StaticFileWriter, test_utils::create_test_provider_factory_with_chain_spec,
|
|
DatabaseProviderFactory, HashingWriter, StaticFileProviderFactory,
|
|
};
|
|
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 const 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(Self {
|
|
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),
|
|
})
|
|
}
|
|
|
|
/// Runs the test cases for the Ethereum Forks test suite.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the test is flagged for skipping or encounters issues during execution.
|
|
fn run(&self) -> Result<(), Error> {
|
|
// If the test is marked for skipping, return a Skipped error immediately.
|
|
if self.skip {
|
|
return Err(Error::Skipped)
|
|
}
|
|
|
|
// Iterate through test cases, filtering by the network type to exclude specific forks.
|
|
self.tests
|
|
.values()
|
|
.filter(|case| {
|
|
!matches!(
|
|
case.network,
|
|
ForkSpec::ByzantiumToConstantinopleAt5 |
|
|
ForkSpec::Constantinople |
|
|
ForkSpec::ConstantinopleFix |
|
|
ForkSpec::MergeEOF |
|
|
ForkSpec::MergeMeterInitCode |
|
|
ForkSpec::MergePush0 |
|
|
ForkSpec::Unknown
|
|
)
|
|
})
|
|
.par_bridge()
|
|
.try_for_each(|case| {
|
|
// Create a new test database and initialize a provider for the test case.
|
|
let provider = create_test_provider_factory_with_chain_spec(Arc::new(
|
|
case.network.clone().into(),
|
|
))
|
|
.database_provider_rw()
|
|
.unwrap();
|
|
|
|
// Insert initial test state into the provider.
|
|
provider.insert_historical_block(
|
|
SealedBlock::new(
|
|
case.genesis_block_header.clone().into(),
|
|
BlockBody::default(),
|
|
)
|
|
.try_seal_with_senders()
|
|
.unwrap(),
|
|
)?;
|
|
case.pre.write_to_db(provider.tx_ref())?;
|
|
|
|
// Initialize receipts static file with genesis
|
|
{
|
|
let static_file_provider = provider.static_file_provider();
|
|
let mut receipts_writer =
|
|
static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
|
|
receipts_writer.increment_block(0).unwrap();
|
|
receipts_writer.commit_without_sync_all().unwrap();
|
|
}
|
|
|
|
// Decode and insert blocks, creating a chain of blocks for the test case.
|
|
let last_block = case.blocks.iter().try_fold(None, |_, block| {
|
|
let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?;
|
|
provider.insert_historical_block(
|
|
decoded.clone().try_seal_with_senders().unwrap(),
|
|
)?;
|
|
Ok::<Option<SealedBlock>, Error>(Some(decoded))
|
|
})?;
|
|
provider
|
|
.static_file_provider()
|
|
.latest_writer(StaticFileSegment::Headers)
|
|
.unwrap()
|
|
.commit_without_sync_all()
|
|
.unwrap();
|
|
|
|
// Execute the execution stage using the EVM processor factory for the test case
|
|
// network.
|
|
let _ = ExecutionStage::new_with_executor(
|
|
reth_evm_ethereum::execute::EthExecutorProvider::ethereum(Arc::new(
|
|
case.network.clone().into(),
|
|
)),
|
|
)
|
|
.execute(
|
|
&provider,
|
|
ExecInput { target: last_block.as_ref().map(|b| b.number), checkpoint: None },
|
|
);
|
|
|
|
// Validate the post-state for the test case.
|
|
match (&case.post_state, &case.post_state_hash) {
|
|
(Some(state), None) => {
|
|
// Validate accounts in the state against the provider's database.
|
|
for (&address, account) in state {
|
|
account.assert_db(address, provider.tx_ref())?;
|
|
}
|
|
}
|
|
(None, Some(expected_state_root)) => {
|
|
// Insert state hashes into the provider based on the expected state root.
|
|
let last_block = last_block.unwrap_or_default();
|
|
provider.insert_hashes(
|
|
0..=last_block.number,
|
|
last_block.hash(),
|
|
*expected_state_root,
|
|
)?;
|
|
}
|
|
_ => return Err(Error::MissingPostState),
|
|
}
|
|
|
|
// Drop the provider without committing to the database.
|
|
drop(provider);
|
|
Ok(())
|
|
})?;
|
|
|
|
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"
|
|
| "ValueOverflowParis.json"
|
|
|
|
// txbyte is of type 02 and we don't 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"
|
|
| "HighGasPriceParis.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)
|
|
}
|