feat: add stateless crate to expose stateless validation (#15591)

Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com>
This commit is contained in:
kevaundray
2025-05-06 13:38:07 +01:00
committed by GitHub
parent 1e33d4cfe2
commit 3e5c230f4d
13 changed files with 846 additions and 20 deletions

View File

@@ -13,10 +13,7 @@ workspace = true
[features]
ef-tests = []
asm-keccak = [
"alloy-primitives/asm-keccak",
"revm/asm-keccak",
]
asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"]
[dependencies]
reth-chainspec.workspace = true
@@ -30,7 +27,9 @@ reth-provider = { workspace = true, features = ["test-utils"] }
reth-evm.workspace = true
reth-evm-ethereum.workspace = true
reth-ethereum-consensus.workspace = true
reth-revm = { workspace = true, features = ["std"] }
reth-revm = { workspace = true, features = ["std", "witness"] }
reth-stateless = { workspace = true }
reth-tracing.workspace = true
reth-trie.workspace = true
reth-trie-db.workspace = true
revm = { workspace = true, features = ["secp256k1", "blst", "c-kzg"] }
@@ -46,6 +45,7 @@ serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
rayon.workspace = true
tracing.workspace = true
# TODO: When we build for a windows target on an ubuntu runner, crunchy tries to
# get the wrong path, update this when the workflow has been updated
#

View File

@@ -4,7 +4,7 @@ use crate::{
models::{BlockchainTest, ForkSpec},
Case, Error, Suite,
};
use alloy_rlp::Decodable;
use alloy_rlp::{Decodable, Encodable};
use rayon::iter::{ParallelBridge, ParallelIterator};
use reth_chainspec::ChainSpec;
use reth_consensus::{Consensus, HeaderValidator};
@@ -16,9 +16,11 @@ use reth_evm_ethereum::execute::EthExecutorProvider;
use reth_primitives_traits::{RecoveredBlock, SealedBlock};
use reth_provider::{
test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
ExecutionOutcome, HistoryWriter, OriginalValuesKnown, StateWriter, StorageLocation,
ExecutionOutcome, HeaderProvider, HistoryWriter, OriginalValuesKnown, StateProofProvider,
StateWriter, StorageLocation,
};
use reth_revm::database::StateProviderDatabase;
use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord, State};
use reth_stateless::{validation::stateless_validation, ExecutionWitness};
use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot};
use reth_trie_db::DatabaseStateRoot;
use std::{collections::BTreeMap, fs, path::Path, sync::Arc};
@@ -212,6 +214,7 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> {
let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone());
let mut parent = genesis_block;
let mut program_inputs = Vec::new();
for (block_index, block) in blocks.iter().enumerate() {
// Note: same as the comment on `decode_blocks` as to why we cannot use block.number
@@ -226,16 +229,50 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> {
pre_execution_checks(chain_spec.clone(), &parent, block)
.map_err(|_| Error::BlockProcessingFailed { block_number })?;
let mut witness_record = ExecutionWitnessRecord::default();
// Execute the block
let state_db = StateProviderDatabase(provider.latest());
let state_provider = provider.latest();
let state_db = StateProviderDatabase(&state_provider);
let executor = executor_provider.batch_executor(state_db);
let output =
executor.execute(block).map_err(|_| Error::BlockProcessingFailed { block_number })?;
let output = executor
.execute_with_state_closure(&(*block).clone(), |statedb: &State<_>| {
witness_record.record_executed_state(statedb);
})
.map_err(|_| Error::BlockProcessingFailed { block_number })?;
// Consensus checks after block execution
validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests)
.map_err(|_| Error::BlockProcessingFailed { block_number })?;
// Generate the stateless witness
// TODO: Most of this code is copy-pasted from debug_executionWitness
let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
witness_record;
let state = state_provider.witness(Default::default(), hashed_state)?;
let mut exec_witness = ExecutionWitness { state, codes, keys, headers: Default::default() };
let smallest = lowest_block_number.unwrap_or_else(|| {
// Return only the parent header, if there were no calls to the
// BLOCKHASH opcode.
block_number.saturating_sub(1)
});
let range = smallest..block_number;
exec_witness.headers = provider
.headers_range(range)?
.into_iter()
.map(|header| {
let mut serialized_header = Vec::new();
header.encode(&mut serialized_header);
serialized_header.into()
})
.collect();
program_inputs.push((block.clone(), exec_witness));
// Compute and check the post state root
let hashed_state =
HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
@@ -280,6 +317,12 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> {
account.assert_db(address, provider.tx_ref())?;
}
// Now validate using the stateless client if everything else passes
for (block, execution_witness) in program_inputs {
stateless_validation(block, execution_witness, chain_spec.clone())
.expect("stateless validation failed");
}
Ok(())
}

View File

@@ -7,6 +7,7 @@ macro_rules! general_state_test {
($test_name:ident, $dir:ident) => {
#[test]
fn $test_name() {
reth_tracing::init_test_tracing();
BlockchainTests::new(format!("GeneralStateTests/{}", stringify!($dir))).run();
}
};
@@ -81,6 +82,7 @@ macro_rules! blockchain_test {
($test_name:ident, $dir:ident) => {
#[test]
fn $test_name() {
reth_tracing::init_test_tracing();
BlockchainTests::new(format!("{}", stringify!($dir))).run();
}
};