chore(stateless): Remove reth-stateless crate (#22115)

This commit is contained in:
Brian Picciano
2026-02-12 12:20:49 +01:00
committed by GitHub
parent 217a337d8c
commit c915841a45
13 changed files with 44 additions and 1122 deletions

View File

@@ -27,8 +27,7 @@ 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", "witness"] }
reth-stateless = { workspace = true }
reth-revm.workspace = true
reth-tracing.workspace = true
reth-trie.workspace = true
reth-trie-db.workspace = true

View File

@@ -4,28 +4,22 @@ use crate::{
models::{BlockchainTest, ForkSpec},
Case, Error, Suite,
};
use alloy_rlp::{Decodable, Encodable};
use alloy_rlp::Decodable;
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use reth_chainspec::ChainSpec;
use reth_consensus::{Consensus, HeaderValidator};
use reth_db_common::init::{insert_genesis_hashes, insert_genesis_history, insert_genesis_state};
use reth_ethereum_consensus::{validate_block_post_execution, EthBeaconConsensus};
use reth_ethereum_primitives::{Block, TransactionSigned};
use reth_ethereum_primitives::Block;
use reth_evm::{execute::Executor, ConfigureEvm};
use reth_evm_ethereum::EthEvmConfig;
use reth_primitives_traits::{
Block as BlockTrait, ParallelBridgeBuffered, RecoveredBlock, SealedBlock,
};
use reth_primitives_traits::{ParallelBridgeBuffered, RecoveredBlock, SealedBlock};
use reth_provider::{
test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
ExecutionOutcome, HeaderProvider, HistoryWriter, OriginalValuesKnown, StateProofProvider,
StateWriteConfig, StateWriter, StaticFileProviderFactory, StaticFileSegment, StaticFileWriter,
};
use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord, State};
use reth_stateless::{
trie::StatelessSparseTrie, validation::stateless_validation_with_trie, ExecutionWitness,
UncompressedPublicKey,
ExecutionOutcome, HistoryWriter, OriginalValuesKnown, StateWriteConfig, StateWriter,
StaticFileProviderFactory, StaticFileSegment, StaticFileWriter,
};
use reth_revm::database::StateProviderDatabase;
use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot};
use reth_trie_db::DatabaseStateRoot;
use std::{
@@ -103,35 +97,31 @@ impl BlockchainTestCase {
}
/// Execute a single `BlockchainTest`, validating the outcome against the
/// expectations encoded in the JSON file. Returns the list of executed blocks
/// with their execution witnesses.
pub fn run_single_case(
name: &str,
case: &BlockchainTest,
) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
/// expectations encoded in the JSON file.
pub fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
let expectation = Self::expected_failure(case);
match run_case(case) {
// All blocks executed successfully.
Ok(program_inputs) => {
Ok(()) => {
// Check if the test case specifies that it should have failed
if let Some((block, msg)) = expectation {
Err(Error::Assertion(format!(
"Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
)))
} else {
Ok(program_inputs)
Ok(())
}
}
// A block processing failure occurred.
Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }) => {
Err(Error::BlockProcessingFailed { block_number, err }) => {
match expectation {
// It happened on exactly the block we were told to fail on
Some((expected, _)) if block_number == expected => Ok(partial_program_inputs),
Some((expected, _)) if block_number == expected => Ok(()),
// Uncle sidechain edge case, we accept as long as it failed.
// But we don't check the exact block number.
_ if Self::is_uncle_sidechain_case(name) => Ok(partial_program_inputs),
_ if Self::is_uncle_sidechain_case(name) => Ok(()),
// Expected failure, but block number does not match
Some((expected, _)) => Err(Error::Assertion(format!(
@@ -139,7 +129,7 @@ impl BlockchainTestCase {
))),
// No failure expected at all - bubble up original error.
None => Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }),
None => Err(Error::BlockProcessingFailed { block_number, err }),
}
}
@@ -198,13 +188,9 @@ impl Case for BlockchainTestCase {
/// outcome.
///
/// Returns:
/// - `Ok(_)` if all blocks execute successfully, returning recovered blocks and full block
/// execution witness.
/// - `Err(Error)` if any block fails to execute correctly, returning a partial block execution
/// witness if the error is of variant `BlockProcessingFailed`.
fn run_case(
case: &BlockchainTest,
) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
/// - `Ok(())` if all blocks execute successfully.
/// - `Err(Error)` if any block fails to execute correctly.
fn run_case(case: &BlockchainTest) -> Result<(), Error> {
// Create a new test database and initialize a provider for the test case.
let chain_spec = case.network.to_chain_spec();
let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
@@ -218,53 +204,43 @@ fn run_case(
.try_recover()
.unwrap();
provider
.insert_block(&genesis_block)
.map_err(|err| Error::block_failed(0, Default::default(), err))?;
provider.insert_block(&genesis_block).map_err(|err| Error::block_failed(0, err))?;
// Increment block number for receipts static file
provider
.static_file_provider()
.latest_writer(StaticFileSegment::Receipts)
.and_then(|mut writer| writer.increment_block(0))
.map_err(|err| Error::block_failed(0, Default::default(), err))?;
.map_err(|err| Error::block_failed(0, err))?;
let genesis_state = case.pre.clone().into_genesis_state();
insert_genesis_state(&provider, genesis_state.iter())
.map_err(|err| Error::block_failed(0, Default::default(), err))?;
.map_err(|err| Error::block_failed(0, err))?;
insert_genesis_hashes(&provider, genesis_state.iter())
.map_err(|err| Error::block_failed(0, Default::default(), err))?;
.map_err(|err| Error::block_failed(0, err))?;
insert_genesis_history(&provider, genesis_state.iter())
.map_err(|err| Error::block_failed(0, Default::default(), err))?;
.map_err(|err| Error::block_failed(0, err))?;
// Decode blocks
let blocks = decode_blocks(&case.blocks)?;
let executor_provider = EthEvmConfig::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
let block_number = (block_index + 1) as u64;
// Insert the block into the database
provider
.insert_block(block)
.map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
// Commit static files, so we can query the headers for stateless execution below
provider.insert_block(block).map_err(|err| Error::block_failed(block_number, err))?;
provider
.static_file_provider()
.commit()
.map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
// Consensus checks before block execution
pre_execution_checks(chain_spec.clone(), &parent, block).map_err(|err| {
program_inputs.push((block.clone(), execution_witness_with_parent(&parent)));
Error::block_failed(block_number, program_inputs.clone(), err)
})?;
let mut witness_record = ExecutionWitnessRecord::default();
pre_execution_checks(chain_spec.clone(), &parent, block)
.map_err(|err| Error::block_failed(block_number, err))?;
// Execute the block
let state_provider = provider.latest();
@@ -272,41 +248,12 @@ fn run_case(
let executor = executor_provider.batch_executor(state_db);
let output = executor
.execute_with_state_closure_always(&(*block).clone(), |statedb: &State<_>| {
witness_record.record_executed_state(statedb);
})
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
.execute(&(*block).clone())
.map_err(|err| Error::block_failed(block_number, err))?;
// Consensus checks after block execution
validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests, None)
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
// 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));
.map_err(|err| Error::block_failed(block_number, err))?;
// Compute and check the post state root
let hashed_state =
@@ -315,11 +262,10 @@ fn run_case(
provider.tx_ref(),
&hashed_state.clone_into_sorted(),
)
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
if computed_state_root != block.state_root {
return Err(Error::block_failed(
block_number,
program_inputs.clone(),
Error::Assertion("state root mismatch".to_string()),
));
}
@@ -331,14 +277,14 @@ fn run_case(
OriginalValuesKnown::Yes,
StateWriteConfig::default(),
)
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
provider
.write_hashed_state(&hashed_state.into_sorted())
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
provider
.update_history_indices(block.number..=block.number)
.map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
// Since there were no errors, update the parent block
parent = block.clone()
@@ -365,25 +311,7 @@ fn run_case(
}
}
// Now validate using the stateless client if everything else passes
for (recovered_block, execution_witness) in &program_inputs {
let block = recovered_block.clone().into_block();
// Recover the actual public keys from the transaction signatures
let public_keys = recover_signers(block.body().transactions())
.expect("Failed to recover public keys from transaction signatures");
stateless_validation_with_trie::<StatelessSparseTrie, _, _>(
block,
public_keys,
execution_witness.clone(),
chain_spec.clone(),
EthEvmConfig::new(chain_spec.clone()),
)
.expect("stateless validation failed");
}
Ok(program_inputs)
Ok(())
}
fn decode_blocks(
@@ -396,12 +324,10 @@ fn decode_blocks(
let block_number = (block_index + 1) as u64;
let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
.map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
.map_err(|err| Error::block_failed(block_number, err))?;
let recovered_block = decoded
.clone()
.try_recover()
.map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
let recovered_block =
decoded.clone().try_recover().map_err(|err| Error::block_failed(block_number, err))?;
blocks.push(recovered_block);
}
@@ -430,26 +356,6 @@ fn pre_execution_checks(
Ok(())
}
/// Recover public keys from transaction signatures.
fn recover_signers<'a, I>(txs: I) -> Result<Vec<UncompressedPublicKey>, Box<dyn std::error::Error>>
where
I: IntoIterator<Item = &'a TransactionSigned>,
{
txs.into_iter()
.enumerate()
.map(|(i, tx)| {
tx.signature()
.recover_from_prehash(&tx.signature_hash())
.map(|keys| {
UncompressedPublicKey(
keys.to_encoded_point(false).as_bytes().try_into().unwrap(),
)
})
.map_err(|e| format!("failed to recover signature for tx #{i}: {e}").into())
})
.collect::<Result<Vec<UncompressedPublicKey>, _>>()
}
/// 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
@@ -516,9 +422,3 @@ fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
path_str.contains(&rhs)
}
fn execution_witness_with_parent(parent: &RecoveredBlock<Block>) -> ExecutionWitness {
let mut serialized_header = Vec::new();
parent.header().encode(&mut serialized_header);
ExecutionWitness { headers: vec![serialized_header.into()], ..Default::default() }
}

View File

@@ -1,10 +1,7 @@
//! Test results and errors
use reth_db::DatabaseError;
use reth_ethereum_primitives::Block;
use reth_primitives_traits::RecoveredBlock;
use reth_provider::ProviderError;
use reth_stateless::ExecutionWitness;
use std::path::{Path, PathBuf};
use thiserror::Error;
@@ -26,9 +23,6 @@ pub enum Error {
BlockProcessingFailed {
/// The block number for the block that failed
block_number: u64,
/// Contains the inputs necessary for the block stateless validation guest program used in
/// zkVMs to prove the block is invalid.
partial_program_inputs: Vec<(RecoveredBlock<Block>, ExecutionWitness)>,
/// The specific error
#[source]
err: Box<dyn std::error::Error + Send + Sync>,
@@ -72,10 +66,9 @@ impl Error {
/// Create a new [`Error::BlockProcessingFailed`] error.
pub fn block_failed(
block_number: u64,
partial_program_inputs: Vec<(RecoveredBlock<Block>, ExecutionWitness)>,
err: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::BlockProcessingFailed { block_number, partial_program_inputs, err: Box::new(err) }
Self::BlockProcessingFailed { block_number, err: Box::new(err) }
}
}