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(¤t_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(), ¤t_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(¤t_block)
202 .map_err(|e| StatelessValidationError::StatelessExecutionFailed(e.to_string()))?;
203
204 // Post validation checks
205 validate_block_post_execution(¤t_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}