reth_stateless/
validation.rs

1use crate::{
2    recover_block::{recover_block_with_public_keys, UncompressedPublicKey},
3    trie::{StatelessSparseTrie, StatelessTrie},
4    witness_db::WitnessDatabase,
5    ExecutionWitness,
6};
7use alloc::{
8    collections::BTreeMap,
9    fmt::Debug,
10    string::{String, ToString},
11    sync::Arc,
12    vec::Vec,
13};
14use alloy_consensus::{BlockHeader, Header};
15use alloy_primitives::{keccak256, B256};
16use reth_chainspec::{EthChainSpec, EthereumHardforks};
17use reth_consensus::{Consensus, HeaderValidator};
18use reth_errors::ConsensusError;
19use reth_ethereum_consensus::{validate_block_post_execution, EthBeaconConsensus};
20use reth_ethereum_primitives::{Block, EthPrimitives, EthereumReceipt};
21use reth_evm::{
22    execute::{BlockExecutionOutput, Executor},
23    ConfigureEvm,
24};
25use reth_primitives_traits::{RecoveredBlock, SealedHeader};
26use reth_trie_common::{HashedPostState, KeccakKeyHasher};
27
28/// BLOCKHASH ancestor lookup window limit per EVM (number of most recent blocks accessible).
29const BLOCKHASH_ANCESTOR_LIMIT: usize = 256;
30
31/// Errors that can occur during stateless validation.
32#[derive(Debug, thiserror::Error)]
33pub enum StatelessValidationError {
34    /// Error when the number of ancestor headers exceeds the limit.
35    #[error("ancestor header count ({count}) exceeds limit ({limit})")]
36    AncestorHeaderLimitExceeded {
37        /// The number of headers provided.
38        count: usize,
39        /// The limit.
40        limit: usize,
41    },
42
43    /// Error when the ancestor headers do not form a contiguous chain.
44    #[error("invalid ancestor chain")]
45    InvalidAncestorChain,
46
47    /// Error when revealing the witness data failed.
48    #[error("failed to reveal witness data for pre-state root {pre_state_root}")]
49    WitnessRevealFailed {
50        /// The pre-state root used for verification.
51        pre_state_root: B256,
52    },
53
54    /// Error during stateless block execution.
55    #[error("stateless block execution failed")]
56    StatelessExecutionFailed(String),
57
58    /// Error during consensus validation of the block.
59    #[error("consensus validation failed: {0}")]
60    ConsensusValidationFailed(#[from] ConsensusError),
61
62    /// Error during stateless state root calculation.
63    #[error("stateless state root calculation failed")]
64    StatelessStateRootCalculationFailed,
65
66    /// Error calculating the pre-state root from the witness data.
67    #[error("stateless pre-state root calculation failed")]
68    StatelessPreStateRootCalculationFailed,
69
70    /// Error when required ancestor headers are missing (e.g., parent header for pre-state root).
71    #[error("missing required ancestor headers")]
72    MissingAncestorHeader,
73
74    /// Error when deserializing ancestor headers
75    #[error("could not deserialize ancestor headers")]
76    HeaderDeserializationFailed,
77
78    /// Error when the computed state root does not match the one in the block header.
79    #[error("mismatched post-state root: {got}\n {expected}")]
80    PostStateRootMismatch {
81        /// The computed post-state root
82        got: B256,
83        /// The expected post-state root; in the block header
84        expected: B256,
85    },
86
87    /// Error when the computed pre-state root does not match the expected one.
88    #[error("mismatched pre-state root: {got} \n {expected}")]
89    PreStateRootMismatch {
90        /// The computed pre-state root
91        got: B256,
92        /// The expected pre-state root from the previous block
93        expected: B256,
94    },
95
96    /// Error during signer recovery.
97    #[error("signer recovery failed")]
98    SignerRecovery,
99
100    /// Error when signature has non-normalized s value in homestead block.
101    #[error("signature s value not normalized for homestead block")]
102    HomesteadSignatureNotNormalized,
103
104    /// Custom error.
105    #[error("{0}")]
106    Custom(&'static str),
107}
108
109/// Performs stateless validation of a block using the provided witness data.
110///
111/// This function attempts to fully validate a given `current_block` statelessly, ie without access
112/// to a persistent database.
113/// It relies entirely on the `witness` data and `ancestor_headers`
114/// provided alongside the block.
115///
116/// The witness data is validated in the following way:
117///
118/// 1. **Ancestor Header Verification:** Checks if the `ancestor_headers` are present, form a
119///    contiguous chain back from `current_block`'s parent, and do not exceed the `BLOCKHASH` opcode
120///    limit using `compute_ancestor_hashes`. We must have at least one ancestor header, even if the
121///    `BLOCKHASH` opcode is not used because we need the state root of the previous block to verify
122///    the pre state reads.
123///
124/// 2. **Pre-State Verification:** Retrieves the expected `pre_state_root` from the parent header
125///    from `ancestor_headers`. Verifies the provided [`ExecutionWitness`] against the
126///    `pre_state_root`.
127///
128/// 3. **Chain Verification:** The code currently does not verify the [`EthChainSpec`] and expects a
129///    higher level function to assert that this is correct by, for example, asserting that it is
130///    equal to the Ethereum Mainnet `ChainSpec` or asserting against the genesis hash that this
131///    `ChainSpec` defines.
132///
133/// High Level Overview of functionality:
134///
135/// - Verify all state accesses against a trusted pre-state root
136/// - Put all state accesses into an in-memory database
137/// - Use the in-memory database to execute the block
138/// - Validate the output of block execution (e.g. receipts, logs, requests)
139/// - Compute the post-state root using the state-diff from block execution
140/// - Check that the post-state root is the state root in the block.
141///
142/// If all steps succeed the function returns `Some` containing the hash of the validated
143/// `current_block`.
144pub fn stateless_validation<ChainSpec, E>(
145    current_block: Block,
146    public_keys: Vec<UncompressedPublicKey>,
147    witness: ExecutionWitness,
148    chain_spec: Arc<ChainSpec>,
149    evm_config: E,
150) -> Result<(B256, BlockExecutionOutput<EthereumReceipt>), StatelessValidationError>
151where
152    ChainSpec: Send + Sync + EthChainSpec<Header = Header> + EthereumHardforks + Debug,
153    E: ConfigureEvm<Primitives = EthPrimitives> + Clone + 'static,
154{
155    stateless_validation_with_trie::<StatelessSparseTrie, ChainSpec, E>(
156        current_block,
157        public_keys,
158        witness,
159        chain_spec,
160        evm_config,
161    )
162}
163
164/// Performs stateless validation of a block using a custom `StatelessTrie` implementation.
165///
166/// This is a generic version of `stateless_validation` that allows users to provide their own
167/// implementation of the `StatelessTrie` for custom trie backends or optimizations.
168///
169/// See `stateless_validation` for detailed documentation of the validation process.
170pub fn stateless_validation_with_trie<T, ChainSpec, E>(
171    current_block: Block,
172    public_keys: Vec<UncompressedPublicKey>,
173    witness: ExecutionWitness,
174    chain_spec: Arc<ChainSpec>,
175    evm_config: E,
176) -> Result<(B256, BlockExecutionOutput<EthereumReceipt>), StatelessValidationError>
177where
178    T: StatelessTrie,
179    ChainSpec: Send + Sync + EthChainSpec<Header = Header> + EthereumHardforks + Debug,
180    E: ConfigureEvm<Primitives = EthPrimitives> + Clone + 'static,
181{
182    let current_block = recover_block_with_public_keys(current_block, public_keys, &*chain_spec)?;
183
184    let mut ancestor_headers: Vec<_> = witness
185        .headers
186        .iter()
187        .map(|bytes| {
188            let hash = keccak256(bytes);
189            alloy_rlp::decode_exact::<Header>(bytes)
190                .map(|h| SealedHeader::new(h, hash))
191                .map_err(|_| StatelessValidationError::HeaderDeserializationFailed)
192        })
193        .collect::<Result<_, _>>()?;
194    // Sort the headers by their block number to ensure that they are in
195    // ascending order.
196    ancestor_headers.sort_by_key(|header| header.number());
197
198    // Enforce BLOCKHASH ancestor headers limit (256 most recent blocks)
199    let count = ancestor_headers.len();
200    if count > BLOCKHASH_ANCESTOR_LIMIT {
201        return Err(StatelessValidationError::AncestorHeaderLimitExceeded {
202            count,
203            limit: BLOCKHASH_ANCESTOR_LIMIT,
204        });
205    }
206
207    // Check that the ancestor headers form a contiguous chain and are not just random headers.
208    let ancestor_hashes = compute_ancestor_hashes(&current_block, &ancestor_headers)?;
209
210    // There should be at least one ancestor header.
211    // The edge case here would be the genesis block, but we do not create proofs for the genesis
212    // block.
213    let parent = match ancestor_headers.last() {
214        Some(prev_header) => prev_header,
215        None => return Err(StatelessValidationError::MissingAncestorHeader),
216    };
217
218    // Validate block against pre-execution consensus rules
219    validate_block_consensus(chain_spec.clone(), &current_block, parent)?;
220
221    // First verify that the pre-state reads are correct
222    let (mut trie, bytecode) = T::new(&witness, parent.state_root)?;
223
224    // Create an in-memory database that will use the reads to validate the block
225    let db = WitnessDatabase::new(&trie, bytecode, ancestor_hashes);
226
227    // Execute the block
228    let executor = evm_config.executor(db);
229    let output = executor
230        .execute(&current_block)
231        .map_err(|e| StatelessValidationError::StatelessExecutionFailed(e.to_string()))?;
232
233    // Post validation checks
234    validate_block_post_execution(&current_block, &chain_spec, &output.receipts, &output.requests)
235        .map_err(StatelessValidationError::ConsensusValidationFailed)?;
236
237    // Compute and check the post state root
238    let hashed_state = HashedPostState::from_bundle_state::<KeccakKeyHasher>(&output.state.state);
239    let state_root = trie.calculate_state_root(hashed_state)?;
240    if state_root != current_block.state_root {
241        return Err(StatelessValidationError::PostStateRootMismatch {
242            got: state_root,
243            expected: current_block.state_root,
244        });
245    }
246
247    // Return block hash
248    Ok((current_block.hash_slow(), output))
249}
250
251/// Performs consensus validation checks on a block without execution or state validation.
252///
253/// This function validates a block against Ethereum consensus rules by:
254///
255/// 1. **Header Validation:** Validates the sealed header against protocol specifications,
256///    including:
257///    - Gas limit checks
258///    - Base fee validation for EIP-1559
259///    - Withdrawals root validation for Shanghai fork
260///    - Blob-related fields validation for Cancun fork
261///
262/// 2. **Pre-Execution Validation:** Validates block structure, transaction format, signature
263///    validity, and other pre-execution requirements.
264///
265/// This function acts as a preliminary validation before executing and validating the state
266/// transition function.
267fn validate_block_consensus<ChainSpec>(
268    chain_spec: Arc<ChainSpec>,
269    block: &RecoveredBlock<Block>,
270    parent: &SealedHeader<Header>,
271) -> Result<(), StatelessValidationError>
272where
273    ChainSpec: Send + Sync + EthChainSpec<Header = Header> + EthereumHardforks + Debug,
274{
275    let consensus = EthBeaconConsensus::new(chain_spec);
276
277    consensus.validate_header(block.sealed_header())?;
278    consensus.validate_header_against_parent(block.sealed_header(), parent)?;
279
280    consensus.validate_block_pre_execution(block)?;
281
282    Ok(())
283}
284
285/// Verifies the contiguity, number of ancestor headers and extracts their hashes.
286///
287/// This function is used to prepare the data required for the `BLOCKHASH`
288/// opcode in a stateless execution context.
289///
290/// It verifies that the provided `ancestor_headers` form a valid, unbroken chain leading back from
291///    the parent of the `current_block`.
292///
293/// Note: This function becomes obsolete if EIP-2935 is implemented.
294/// Note: The headers are assumed to be in ascending order.
295///
296/// If both checks pass, it returns a [`BTreeMap`] mapping the block number of each
297/// ancestor header to its corresponding block hash.
298fn compute_ancestor_hashes(
299    current_block: &RecoveredBlock<Block>,
300    ancestor_headers: &[SealedHeader],
301) -> Result<BTreeMap<u64, B256>, StatelessValidationError> {
302    let mut ancestor_hashes = BTreeMap::new();
303
304    let mut child_header = current_block.sealed_header();
305
306    // Next verify that headers supplied are contiguous
307    for parent_header in ancestor_headers.iter().rev() {
308        let parent_hash = child_header.parent_hash();
309        ancestor_hashes.insert(parent_header.number, parent_hash);
310
311        if parent_hash != parent_header.hash() {
312            return Err(StatelessValidationError::InvalidAncestorChain); // Blocks must be contiguous
313        }
314
315        if parent_header.number + 1 != child_header.number {
316            return Err(StatelessValidationError::InvalidAncestorChain); // Header number should be
317                                                                        // contiguous
318        }
319
320        child_header = parent_header
321    }
322
323    Ok(ancestor_hashes)
324}