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::{execute::EthExecutorProvider, 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, StorageLocation,
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::{collections::BTreeMap, fs, path::Path, sync::Arc};
27
28#[derive(Debug)]
30pub struct BlockchainTests {
31 suite: String,
32}
33
34impl BlockchainTests {
35 pub const fn new(suite: String) -> Self {
37 Self { suite }
38 }
39}
40
41impl Suite for BlockchainTests {
42 type Case = BlockchainTestCase;
43
44 fn suite_name(&self) -> String {
45 format!("BlockchainTests/{}", self.suite)
46 }
47}
48
49#[derive(Debug, PartialEq, Eq)]
51pub struct BlockchainTestCase {
52 tests: BTreeMap<String, BlockchainTest>,
53 skip: bool,
54}
55
56impl BlockchainTestCase {
57 const fn excluded_fork(network: ForkSpec) -> bool {
59 matches!(
60 network,
61 ForkSpec::ByzantiumToConstantinopleAt5 |
62 ForkSpec::Constantinople |
63 ForkSpec::ConstantinopleFix |
64 ForkSpec::MergeEOF |
65 ForkSpec::MergeMeterInitCode |
66 ForkSpec::MergePush0
67 )
68 }
69
70 #[inline]
76 fn is_uncle_sidechain_case(name: &str) -> bool {
77 name.contains("UncleFromSideChain")
78 }
79
80 #[inline]
87 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
88 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
89 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
90 })
91 }
92
93 fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
96 let expectation = Self::expected_failure(case);
97 match run_case(case) {
98 Ok(()) => {
100 if let Some((block, msg)) = expectation {
102 Err(Error::Assertion(format!(
103 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
104 )))
105 } else {
106 Ok(())
107 }
108 }
109
110 Err(Error::BlockProcessingFailed { block_number }) => match expectation {
112 Some((expected, _)) if block_number == expected => Ok(()),
114
115 _ if Self::is_uncle_sidechain_case(name) => Ok(()),
118
119 Some((expected, _)) => Err(Error::Assertion(format!(
121 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
122 ))),
123
124 None => Err(Error::BlockProcessingFailed { block_number }),
126 },
127
128 Err(other) => Err(other),
136 }
137 }
138}
139
140impl Case for BlockchainTestCase {
141 fn load(path: &Path) -> Result<Self, Error> {
142 Ok(Self {
143 tests: {
144 let s = fs::read_to_string(path)
145 .map_err(|error| Error::Io { path: path.into(), error })?;
146 serde_json::from_str(&s)
147 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
148 },
149 skip: should_skip(path),
150 })
151 }
152
153 fn run(&self) -> Result<(), Error> {
158 if self.skip {
160 return Err(Error::Skipped)
161 }
162
163 self.tests
165 .iter()
166 .filter(|(_, case)| !Self::excluded_fork(case.network))
167 .par_bridge()
168 .try_for_each(|(name, case)| Self::run_single_case(name, case))?;
169
170 Ok(())
171 }
172}
173
174fn run_case(case: &BlockchainTest) -> Result<(), Error> {
187 let chain_spec: Arc<ChainSpec> = Arc::new(case.network.into());
189 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
190 let provider = factory.database_provider_rw().unwrap();
191
192 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
194 case.genesis_block_header.clone().into(),
195 Default::default(),
196 )
197 .try_recover()
198 .unwrap();
199
200 provider
201 .insert_block(genesis_block.clone(), StorageLocation::Database)
202 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
203
204 let genesis_state = case.pre.clone().into_genesis_state();
205 insert_genesis_state(&provider, genesis_state.iter())
206 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
207 insert_genesis_hashes(&provider, genesis_state.iter())
208 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
209 insert_genesis_history(&provider, genesis_state.iter())
210 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
211
212 let blocks = decode_blocks(&case.blocks)?;
214
215 let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone());
216 let mut parent = genesis_block;
217 let mut program_inputs = Vec::new();
218
219 for (block_index, block) in blocks.iter().enumerate() {
220 let block_number = (block_index + 1) as u64;
222
223 provider
225 .insert_block(block.clone(), StorageLocation::Database)
226 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
227
228 pre_execution_checks(chain_spec.clone(), &parent, block)
230 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
231
232 let mut witness_record = ExecutionWitnessRecord::default();
233
234 let state_provider = provider.latest();
236 let state_db = StateProviderDatabase(&state_provider);
237 let executor = executor_provider.batch_executor(state_db);
238
239 let output = executor
240 .execute_with_state_closure(&(*block).clone(), |statedb: &State<_>| {
241 witness_record.record_executed_state(statedb);
242 })
243 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
244
245 validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests)
247 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
248
249 let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
252 witness_record;
253 let state = state_provider.witness(Default::default(), hashed_state)?;
254 let mut exec_witness = ExecutionWitness { state, codes, keys, headers: Default::default() };
255
256 let smallest = lowest_block_number.unwrap_or_else(|| {
257 block_number.saturating_sub(1)
260 });
261
262 let range = smallest..block_number;
263
264 exec_witness.headers = provider
265 .headers_range(range)?
266 .into_iter()
267 .map(|header| {
268 let mut serialized_header = Vec::new();
269 header.encode(&mut serialized_header);
270 serialized_header.into()
271 })
272 .collect();
273
274 program_inputs.push((block.clone(), exec_witness));
275
276 let hashed_state =
278 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
279 let (computed_state_root, _) =
280 StateRoot::overlay_root_with_updates(provider.tx_ref(), hashed_state.clone())
281 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
282 if computed_state_root != block.state_root {
283 return Err(Error::BlockProcessingFailed { block_number })
284 }
285
286 provider
288 .write_state(
289 &ExecutionOutcome::single(block.number, output),
290 OriginalValuesKnown::Yes,
291 StorageLocation::Database,
292 )
293 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
294
295 provider
296 .write_hashed_state(&hashed_state.into_sorted())
297 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
298 provider
299 .update_history_indices(block.number..=block.number)
300 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
301
302 parent = block.clone()
304 }
305
306 let expected_post_state = case.post_state.as_ref().ok_or(Error::MissingPostState)?;
316 for (&address, account) in expected_post_state {
317 account.assert_db(address, provider.tx_ref())?;
318 }
319
320 for (block, execution_witness) in program_inputs {
322 stateless_validation(
323 block.into_block(),
324 execution_witness,
325 chain_spec.clone(),
326 EthEvmConfig::new(chain_spec.clone()),
327 )
328 .expect("stateless validation failed");
329 }
330
331 Ok(())
332}
333
334fn decode_blocks(
335 test_case_blocks: &[crate::models::Block],
336) -> Result<Vec<RecoveredBlock<Block>>, Error> {
337 let mut blocks = Vec::with_capacity(test_case_blocks.len());
338 for (block_index, block) in test_case_blocks.iter().enumerate() {
339 let block_number = (block_index + 1) as u64;
342
343 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
344 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
345
346 let recovered_block = decoded
347 .clone()
348 .try_recover()
349 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
350
351 blocks.push(recovered_block);
352 }
353
354 Ok(blocks)
355}
356
357fn pre_execution_checks(
358 chain_spec: Arc<ChainSpec>,
359 parent: &RecoveredBlock<Block>,
360 block: &RecoveredBlock<Block>,
361) -> Result<(), Error> {
362 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
363
364 let sealed_header = block.sealed_header();
365
366 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
367 &consensus,
368 block.body(),
369 sealed_header,
370 )?;
371 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
372 consensus.validate_header(sealed_header)?;
373 consensus.validate_block_pre_execution(block)?;
374
375 Ok(())
376}
377
378pub fn should_skip(path: &Path) -> bool {
385 let path_str = path.to_str().expect("Path is not valid UTF-8");
386 let name = path.file_name().unwrap().to_str().unwrap();
387 matches!(
388 name,
389 | "ValueOverflow.json"
392 | "ValueOverflowParis.json"
393
394 | "typeTwoBerlin.json"
396
397 | "CreateTransactionHighNonce.json"
401
402 | "HighGasPrice.json"
405 | "HighGasPriceParis.json"
406
407 | "accessListExample.json"
411 | "basefeeExample.json"
412 | "eip1559.json"
413 | "mergeTest.json"
414
415 | "loopExp.json"
417 | "Call50000_sha256.json"
418 | "static_Call50000_sha256.json"
419 | "loopMul.json"
420 | "CALLBlake2f_MaxRounds.json"
421 | "shiftCombinations.json"
422
423 | "RevertInCreateInInit_Paris.json"
425 | "RevertInCreateInInit.json"
426 | "dynamicAccountOverwriteEmpty.json"
427 | "dynamicAccountOverwriteEmpty_Paris.json"
428 | "RevertInCreateInInitCreate2Paris.json"
429 | "create2collisionStorage.json"
430 | "RevertInCreateInInitCreate2.json"
431 | "create2collisionStorageParis.json"
432 | "InitCollision.json"
433 | "InitCollisionParis.json"
434 )
435 || path_contains(path_str, &["EIPTests", "stEOF"])
437}
438
439fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
441 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
442 path_str.contains(&rhs)
443}