reth_stateless/
validation.rs

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