mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-19 03:04:27 -05:00
chore(stateless): Remove reth-stateless crate (#22115)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 side‑chain 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() }
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user