1use crate::{
4 models::{BlockchainTest, ForkSpec},
5 Case, Error, Suite,
6};
7use alloy_rlp::{Decodable, Encodable};
8use rayon::iter::{ParallelBridge, ParallelIterator};
9use reth_chainspec::ChainSpec;
10use reth_consensus::{Consensus, HeaderValidator};
11use reth_db_common::init::{insert_genesis_hashes, insert_genesis_history, insert_genesis_state};
12use reth_ethereum_consensus::{validate_block_post_execution, EthBeaconConsensus};
13use reth_ethereum_primitives::Block;
14use reth_evm::{execute::Executor, ConfigureEvm};
15use reth_evm_ethereum::EthEvmConfig;
16use reth_primitives_traits::{RecoveredBlock, SealedBlock};
17use reth_provider::{
18 test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
19 ExecutionOutcome, HeaderProvider, HistoryWriter, OriginalValuesKnown, StateProofProvider,
20 StateWriter, StaticFileProviderFactory, StaticFileSegment, StaticFileWriter,
21};
22use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord, State};
23use reth_stateless::{validation::stateless_validation, ExecutionWitness};
24use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot};
25use reth_trie_db::DatabaseStateRoot;
26use std::{
27 collections::BTreeMap,
28 fs,
29 path::{Path, PathBuf},
30 sync::Arc,
31};
32
33#[derive(Debug)]
35pub struct BlockchainTests {
36 suite_path: PathBuf,
37}
38
39impl BlockchainTests {
40 pub const fn new(suite_path: PathBuf) -> Self {
42 Self { suite_path }
43 }
44}
45
46impl Suite for BlockchainTests {
47 type Case = BlockchainTestCase;
48
49 fn suite_path(&self) -> &Path {
50 &self.suite_path
51 }
52}
53
54#[derive(Debug, PartialEq, Eq)]
56pub struct BlockchainTestCase {
57 pub tests: BTreeMap<String, BlockchainTest>,
59 pub skip: bool,
61}
62
63impl BlockchainTestCase {
64 const fn excluded_fork(network: ForkSpec) -> bool {
66 matches!(
67 network,
68 ForkSpec::ByzantiumToConstantinopleAt5 |
69 ForkSpec::Constantinople |
70 ForkSpec::ConstantinopleFix |
71 ForkSpec::MergeEOF |
72 ForkSpec::MergeMeterInitCode |
73 ForkSpec::MergePush0
74 )
75 }
76
77 #[inline]
83 fn is_uncle_sidechain_case(name: &str) -> bool {
84 name.contains("UncleFromSideChain")
85 }
86
87 #[inline]
94 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
95 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
96 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
97 })
98 }
99
100 pub fn run_single_case(
104 name: &str,
105 case: &BlockchainTest,
106 ) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
107 let expectation = Self::expected_failure(case);
108 match run_case(case) {
109 Ok(program_inputs) => {
111 if let Some((block, msg)) = expectation {
113 Err(Error::Assertion(format!(
114 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
115 )))
116 } else {
117 Ok(program_inputs)
118 }
119 }
120
121 Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }) => {
123 match expectation {
124 Some((expected, _)) if block_number == expected => Ok(partial_program_inputs),
126
127 _ if Self::is_uncle_sidechain_case(name) => Ok(partial_program_inputs),
130
131 Some((expected, _)) => Err(Error::Assertion(format!(
133 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
134 ))),
135
136 None => Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }),
138 }
139 }
140
141 Err(other) => Err(other),
149 }
150 }
151}
152
153impl Case for BlockchainTestCase {
154 fn load(path: &Path) -> Result<Self, Error> {
155 Ok(Self {
156 tests: {
157 let s = fs::read_to_string(path)
158 .map_err(|error| Error::Io { path: path.into(), error })?;
159 serde_json::from_str(&s)
160 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
161 },
162 skip: should_skip(path),
163 })
164 }
165
166 fn run(&self) -> Result<(), Error> {
171 if self.skip {
173 return Err(Error::Skipped);
174 }
175
176 self.tests
178 .iter()
179 .filter(|(_, case)| !Self::excluded_fork(case.network))
180 .par_bridge()
181 .try_for_each(|(name, case)| Self::run_single_case(name, case).map(|_| ()))?;
182
183 Ok(())
184 }
185}
186
187fn run_case(
202 case: &BlockchainTest,
203) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
204 let chain_spec: Arc<ChainSpec> = Arc::new(case.network.into());
206 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
207 let provider = factory.database_provider_rw().unwrap();
208
209 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
211 case.genesis_block_header.clone().into(),
212 Default::default(),
213 )
214 .try_recover()
215 .unwrap();
216
217 provider
218 .insert_block(genesis_block.clone())
219 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
220
221 provider
223 .static_file_provider()
224 .latest_writer(StaticFileSegment::Receipts)
225 .and_then(|mut writer| writer.increment_block(0))
226 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
227
228 let genesis_state = case.pre.clone().into_genesis_state();
229 insert_genesis_state(&provider, genesis_state.iter())
230 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
231 insert_genesis_hashes(&provider, genesis_state.iter())
232 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
233 insert_genesis_history(&provider, genesis_state.iter())
234 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
235
236 let blocks = decode_blocks(&case.blocks)?;
238
239 let executor_provider = EthEvmConfig::ethereum(chain_spec.clone());
240 let mut parent = genesis_block;
241 let mut program_inputs = Vec::new();
242
243 for (block_index, block) in blocks.iter().enumerate() {
244 let block_number = (block_index + 1) as u64;
246
247 provider
249 .insert_block(block.clone())
250 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
251 provider
253 .static_file_provider()
254 .commit()
255 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
256
257 pre_execution_checks(chain_spec.clone(), &parent, block).map_err(|err| {
259 program_inputs.push((block.clone(), execution_witness_with_parent(&parent)));
260 Error::block_failed(block_number, program_inputs.clone(), err)
261 })?;
262
263 let mut witness_record = ExecutionWitnessRecord::default();
264
265 let state_provider = provider.latest();
267 let state_db = StateProviderDatabase(&state_provider);
268 let executor = executor_provider.batch_executor(state_db);
269
270 let output = executor
271 .execute_with_state_closure_always(&(*block).clone(), |statedb: &State<_>| {
272 witness_record.record_executed_state(statedb);
273 })
274 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
275
276 validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests)
278 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
279
280 let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
283 witness_record;
284 let state = state_provider.witness(Default::default(), hashed_state)?;
285 let mut exec_witness = ExecutionWitness { state, codes, keys, headers: Default::default() };
286
287 let smallest = lowest_block_number.unwrap_or_else(|| {
288 block_number.saturating_sub(1)
291 });
292
293 let range = smallest..block_number;
294
295 exec_witness.headers = provider
296 .headers_range(range)?
297 .into_iter()
298 .map(|header| {
299 let mut serialized_header = Vec::new();
300 header.encode(&mut serialized_header);
301 serialized_header.into()
302 })
303 .collect();
304
305 program_inputs.push((block.clone(), exec_witness));
306
307 let hashed_state =
309 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
310 let (computed_state_root, _) =
311 StateRoot::overlay_root_with_updates(provider.tx_ref(), hashed_state.clone())
312 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
313 if computed_state_root != block.state_root {
314 return Err(Error::block_failed(
315 block_number,
316 program_inputs.clone(),
317 Error::Assertion("state root mismatch".to_string()),
318 ));
319 }
320
321 provider
323 .write_state(&ExecutionOutcome::single(block.number, output), OriginalValuesKnown::Yes)
324 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
325
326 provider
327 .write_hashed_state(&hashed_state.into_sorted())
328 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
329 provider
330 .update_history_indices(block.number..=block.number)
331 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
332
333 parent = block.clone()
335 }
336
337 match &case.post_state {
338 Some(expected_post_state) => {
339 for (address, account) in expected_post_state {
349 account.assert_db(*address, provider.tx_ref())?;
350 }
351 }
352 None => {
353 }
356 }
357
358 for (block, execution_witness) in &program_inputs {
360 stateless_validation(
361 block.clone(),
362 execution_witness.clone(),
363 chain_spec.clone(),
364 EthEvmConfig::new(chain_spec.clone()),
365 )
366 .expect("stateless validation failed");
367 }
368
369 Ok(program_inputs)
370}
371
372fn decode_blocks(
373 test_case_blocks: &[crate::models::Block],
374) -> Result<Vec<RecoveredBlock<Block>>, Error> {
375 let mut blocks = Vec::with_capacity(test_case_blocks.len());
376 for (block_index, block) in test_case_blocks.iter().enumerate() {
377 let block_number = (block_index + 1) as u64;
380
381 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
382 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
383
384 let recovered_block = decoded
385 .clone()
386 .try_recover()
387 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
388
389 blocks.push(recovered_block);
390 }
391
392 Ok(blocks)
393}
394
395fn pre_execution_checks(
396 chain_spec: Arc<ChainSpec>,
397 parent: &RecoveredBlock<Block>,
398 block: &RecoveredBlock<Block>,
399) -> Result<(), Error> {
400 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
401
402 let sealed_header = block.sealed_header();
403
404 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
405 &consensus,
406 block.body(),
407 sealed_header,
408 )?;
409 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
410 consensus.validate_header(sealed_header)?;
411 consensus.validate_block_pre_execution(block)?;
412
413 Ok(())
414}
415
416pub fn should_skip(path: &Path) -> bool {
423 let path_str = path.to_str().expect("Path is not valid UTF-8");
424 let name = path.file_name().unwrap().to_str().unwrap();
425 matches!(
426 name,
427 | "ValueOverflow.json"
430 | "ValueOverflowParis.json"
431
432 | "typeTwoBerlin.json"
434
435 | "CreateTransactionHighNonce.json"
439
440 | "HighGasPrice.json"
443 | "HighGasPriceParis.json"
444
445 | "accessListExample.json"
449 | "basefeeExample.json"
450 | "eip1559.json"
451 | "mergeTest.json"
452
453 | "loopExp.json"
455 | "Call50000_sha256.json"
456 | "static_Call50000_sha256.json"
457 | "loopMul.json"
458 | "CALLBlake2f_MaxRounds.json"
459 | "shiftCombinations.json"
460
461 | "RevertInCreateInInit_Paris.json"
463 | "RevertInCreateInInit.json"
464 | "dynamicAccountOverwriteEmpty.json"
465 | "dynamicAccountOverwriteEmpty_Paris.json"
466 | "RevertInCreateInInitCreate2Paris.json"
467 | "create2collisionStorage.json"
468 | "RevertInCreateInInitCreate2.json"
469 | "create2collisionStorageParis.json"
470 | "InitCollision.json"
471 | "InitCollisionParis.json"
472 )
473 || path_contains(path_str, &["EIPTests", "stEOF"])
475}
476
477fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
479 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
480 path_str.contains(&rhs)
481}
482
483fn execution_witness_with_parent(parent: &RecoveredBlock<Block>) -> ExecutionWitness {
484 let mut serialized_header = Vec::new();
485 parent.header().encode(&mut serialized_header);
486 ExecutionWitness { headers: vec![serialized_header.into()], ..Default::default() }
487}