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(¤t_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(), ¤t_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(¤t_block)
231 .map_err(|e| StatelessValidationError::StatelessExecutionFailed(e.to_string()))?;
232
233 // Post validation checks
234 validate_block_post_execution(¤t_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}