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, TransactionSigned};
14use reth_evm::{execute::Executor, ConfigureEvm};
15use reth_evm_ethereum::EthEvmConfig;
16use reth_primitives_traits::{Block as BlockTrait, 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::{
24 trie::StatelessSparseTrie, validation::stateless_validation_with_trie, ExecutionWitness,
25 UncompressedPublicKey,
26};
27use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot};
28use reth_trie_db::DatabaseStateRoot;
29use std::{
30 collections::BTreeMap,
31 fs,
32 path::{Path, PathBuf},
33 sync::Arc,
34};
35
36#[derive(Debug)]
38pub struct BlockchainTests {
39 suite_path: PathBuf,
40}
41
42impl BlockchainTests {
43 pub const fn new(suite_path: PathBuf) -> Self {
45 Self { suite_path }
46 }
47}
48
49impl Suite for BlockchainTests {
50 type Case = BlockchainTestCase;
51
52 fn suite_path(&self) -> &Path {
53 &self.suite_path
54 }
55}
56
57#[derive(Debug, PartialEq, Eq)]
59pub struct BlockchainTestCase {
60 pub tests: BTreeMap<String, BlockchainTest>,
62 pub skip: bool,
64}
65
66impl BlockchainTestCase {
67 const fn excluded_fork(network: ForkSpec) -> bool {
69 matches!(
70 network,
71 ForkSpec::ByzantiumToConstantinopleAt5 |
72 ForkSpec::Constantinople |
73 ForkSpec::ConstantinopleFix |
74 ForkSpec::MergeEOF |
75 ForkSpec::MergeMeterInitCode |
76 ForkSpec::MergePush0
77 )
78 }
79
80 #[inline]
86 fn is_uncle_sidechain_case(name: &str) -> bool {
87 name.contains("UncleFromSideChain")
88 }
89
90 #[inline]
97 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
98 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
99 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
100 })
101 }
102
103 pub fn run_single_case(
107 name: &str,
108 case: &BlockchainTest,
109 ) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
110 let expectation = Self::expected_failure(case);
111 match run_case(case) {
112 Ok(program_inputs) => {
114 if let Some((block, msg)) = expectation {
116 Err(Error::Assertion(format!(
117 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
118 )))
119 } else {
120 Ok(program_inputs)
121 }
122 }
123
124 Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }) => {
126 match expectation {
127 Some((expected, _)) if block_number == expected => Ok(partial_program_inputs),
129
130 _ if Self::is_uncle_sidechain_case(name) => Ok(partial_program_inputs),
133
134 Some((expected, _)) => Err(Error::Assertion(format!(
136 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
137 ))),
138
139 None => Err(Error::BlockProcessingFailed { block_number, partial_program_inputs, err }),
141 }
142 }
143
144 Err(other) => Err(other),
152 }
153 }
154}
155
156impl Case for BlockchainTestCase {
157 fn load(path: &Path) -> Result<Self, Error> {
158 Ok(Self {
159 tests: {
160 let s = fs::read_to_string(path)
161 .map_err(|error| Error::Io { path: path.into(), error })?;
162 serde_json::from_str(&s)
163 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
164 },
165 skip: should_skip(path),
166 })
167 }
168
169 fn run(&self) -> Result<(), Error> {
174 if self.skip {
176 return Err(Error::Skipped);
177 }
178
179 self.tests
181 .iter()
182 .filter(|(_, case)| !Self::excluded_fork(case.network))
183 .par_bridge()
184 .try_for_each(|(name, case)| Self::run_single_case(name, case).map(|_| ()))?;
185
186 Ok(())
187 }
188}
189
190fn run_case(
205 case: &BlockchainTest,
206) -> Result<Vec<(RecoveredBlock<Block>, ExecutionWitness)>, Error> {
207 let chain_spec: Arc<ChainSpec> = Arc::new(case.network.into());
209 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
210 let provider = factory.database_provider_rw().unwrap();
211
212 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
214 case.genesis_block_header.clone().into(),
215 Default::default(),
216 )
217 .try_recover()
218 .unwrap();
219
220 provider
221 .insert_block(genesis_block.clone())
222 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
223
224 provider
226 .static_file_provider()
227 .latest_writer(StaticFileSegment::Receipts)
228 .and_then(|mut writer| writer.increment_block(0))
229 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
230
231 let genesis_state = case.pre.clone().into_genesis_state();
232 insert_genesis_state(&provider, genesis_state.iter())
233 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
234 insert_genesis_hashes(&provider, genesis_state.iter())
235 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
236 insert_genesis_history(&provider, genesis_state.iter())
237 .map_err(|err| Error::block_failed(0, Default::default(), err))?;
238
239 let blocks = decode_blocks(&case.blocks)?;
241
242 let executor_provider = EthEvmConfig::ethereum(chain_spec.clone());
243 let mut parent = genesis_block;
244 let mut program_inputs = Vec::new();
245
246 for (block_index, block) in blocks.iter().enumerate() {
247 let block_number = (block_index + 1) as u64;
249
250 provider
252 .insert_block(block.clone())
253 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
254 provider
256 .static_file_provider()
257 .commit()
258 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
259
260 pre_execution_checks(chain_spec.clone(), &parent, block).map_err(|err| {
262 program_inputs.push((block.clone(), execution_witness_with_parent(&parent)));
263 Error::block_failed(block_number, program_inputs.clone(), err)
264 })?;
265
266 let mut witness_record = ExecutionWitnessRecord::default();
267
268 let state_provider = provider.latest();
270 let state_db = StateProviderDatabase(&state_provider);
271 let executor = executor_provider.batch_executor(state_db);
272
273 let output = executor
274 .execute_with_state_closure_always(&(*block).clone(), |statedb: &State<_>| {
275 witness_record.record_executed_state(statedb);
276 })
277 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
278
279 validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests)
281 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
282
283 let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
286 witness_record;
287 let state = state_provider.witness(Default::default(), hashed_state)?;
288 let mut exec_witness = ExecutionWitness { state, codes, keys, headers: Default::default() };
289
290 let smallest = lowest_block_number.unwrap_or_else(|| {
291 block_number.saturating_sub(1)
294 });
295
296 let range = smallest..block_number;
297
298 exec_witness.headers = provider
299 .headers_range(range)?
300 .into_iter()
301 .map(|header| {
302 let mut serialized_header = Vec::new();
303 header.encode(&mut serialized_header);
304 serialized_header.into()
305 })
306 .collect();
307
308 program_inputs.push((block.clone(), exec_witness));
309
310 let hashed_state =
312 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
313 let (computed_state_root, _) =
314 StateRoot::overlay_root_with_updates(provider.tx_ref(), hashed_state.clone())
315 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
316 if computed_state_root != block.state_root {
317 return Err(Error::block_failed(
318 block_number,
319 program_inputs.clone(),
320 Error::Assertion("state root mismatch".to_string()),
321 ));
322 }
323
324 provider
326 .write_state(&ExecutionOutcome::single(block.number, output), OriginalValuesKnown::Yes)
327 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
328
329 provider
330 .write_hashed_state(&hashed_state.into_sorted())
331 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
332 provider
333 .update_history_indices(block.number..=block.number)
334 .map_err(|err| Error::block_failed(block_number, program_inputs.clone(), err))?;
335
336 parent = block.clone()
338 }
339
340 match &case.post_state {
341 Some(expected_post_state) => {
342 for (address, account) in expected_post_state {
352 account.assert_db(*address, provider.tx_ref())?;
353 }
354 }
355 None => {
356 }
359 }
360
361 for (recovered_block, execution_witness) in &program_inputs {
363 let block = recovered_block.clone().into_block();
364
365 let public_keys = recover_signers(block.body().transactions())
367 .expect("Failed to recover public keys from transaction signatures");
368
369 stateless_validation_with_trie::<StatelessSparseTrie, _, _>(
370 block,
371 public_keys,
372 execution_witness.clone(),
373 chain_spec.clone(),
374 EthEvmConfig::new(chain_spec.clone()),
375 )
376 .expect("stateless validation failed");
377 }
378
379 Ok(program_inputs)
380}
381
382fn decode_blocks(
383 test_case_blocks: &[crate::models::Block],
384) -> Result<Vec<RecoveredBlock<Block>>, Error> {
385 let mut blocks = Vec::with_capacity(test_case_blocks.len());
386 for (block_index, block) in test_case_blocks.iter().enumerate() {
387 let block_number = (block_index + 1) as u64;
390
391 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
392 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
393
394 let recovered_block = decoded
395 .clone()
396 .try_recover()
397 .map_err(|err| Error::block_failed(block_number, Default::default(), err))?;
398
399 blocks.push(recovered_block);
400 }
401
402 Ok(blocks)
403}
404
405fn pre_execution_checks(
406 chain_spec: Arc<ChainSpec>,
407 parent: &RecoveredBlock<Block>,
408 block: &RecoveredBlock<Block>,
409) -> Result<(), Error> {
410 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
411
412 let sealed_header = block.sealed_header();
413
414 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
415 &consensus,
416 block.body(),
417 sealed_header,
418 )?;
419 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
420 consensus.validate_header(sealed_header)?;
421 consensus.validate_block_pre_execution(block)?;
422
423 Ok(())
424}
425
426fn recover_signers<'a, I>(txs: I) -> Result<Vec<UncompressedPublicKey>, Box<dyn std::error::Error>>
428where
429 I: IntoIterator<Item = &'a TransactionSigned>,
430{
431 txs.into_iter()
432 .enumerate()
433 .map(|(i, tx)| {
434 tx.signature()
435 .recover_from_prehash(&tx.signature_hash())
436 .map(|keys| {
437 UncompressedPublicKey(
438 keys.to_encoded_point(false).as_bytes().try_into().unwrap(),
439 )
440 })
441 .map_err(|e| format!("failed to recover signature for tx #{i}: {e}").into())
442 })
443 .collect::<Result<Vec<UncompressedPublicKey>, _>>()
444}
445
446pub fn should_skip(path: &Path) -> bool {
453 let path_str = path.to_str().expect("Path is not valid UTF-8");
454 let name = path.file_name().unwrap().to_str().unwrap();
455 matches!(
456 name,
457 | "ValueOverflow.json"
460 | "ValueOverflowParis.json"
461
462 | "typeTwoBerlin.json"
464
465 | "CreateTransactionHighNonce.json"
469
470 | "HighGasPrice.json"
473 | "HighGasPriceParis.json"
474
475 | "accessListExample.json"
479 | "basefeeExample.json"
480 | "eip1559.json"
481 | "mergeTest.json"
482
483 | "loopExp.json"
485 | "Call50000_sha256.json"
486 | "static_Call50000_sha256.json"
487 | "loopMul.json"
488 | "CALLBlake2f_MaxRounds.json"
489 | "shiftCombinations.json"
490
491 | "RevertInCreateInInit_Paris.json"
493 | "RevertInCreateInInit.json"
494 | "dynamicAccountOverwriteEmpty.json"
495 | "dynamicAccountOverwriteEmpty_Paris.json"
496 | "RevertInCreateInInitCreate2Paris.json"
497 | "create2collisionStorage.json"
498 | "RevertInCreateInInitCreate2.json"
499 | "create2collisionStorageParis.json"
500 | "InitCollision.json"
501 | "InitCollisionParis.json"
502 )
503 || path_contains(path_str, &["EIPTests", "stEOF"])
505}
506
507fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
509 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
510 path_str.contains(&rhs)
511}
512
513fn execution_witness_with_parent(parent: &RecoveredBlock<Block>) -> ExecutionWitness {
514 let mut serialized_header = Vec::new();
515 parent.header().encode(&mut serialized_header);
516 ExecutionWitness { headers: vec![serialized_header.into()], ..Default::default() }
517}