1use crate::{
4 models::{BlockchainTest, ForkSpec},
5 Case, Error, Suite,
6};
7use alloy_eip7928::bal::Bal;
8use alloy_rlp::Decodable;
9use rayon::iter::{IndexedParallelIterator, ParallelIterator};
10use reth_chainspec::ChainSpec;
11use reth_consensus::{Consensus, HeaderValidator};
12use reth_db_common::init::{insert_genesis_hashes, insert_genesis_history, insert_genesis_state};
13use reth_ethereum_consensus::{validate_block_post_execution, EthBeaconConsensus};
14use reth_ethereum_primitives::Block;
15use reth_evm::{
16 execute::{BlockExecutionOutput, Executor},
17 ConfigureEvm,
18};
19use reth_evm_ethereum::EthEvmConfig;
20use reth_primitives_traits::{ParallelBridgeBuffered, RecoveredBlock, SealedBlock};
21use reth_provider::{
22 test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
23 ExecutionOutcome, HashedPostStateProvider, HistoryWriter, OriginalValuesKnown, StateProvider,
24 StateWriteConfig, StateWriter, StaticFileProviderFactory, StaticFileSegment, StaticFileWriter,
25 StorageSettingsCache, TrieWriter,
26};
27use reth_revm::database::StateProviderDatabase;
28use reth_trie::StateRoot;
29use reth_trie_db::DatabaseStateRoot;
30use std::{
31 collections::BTreeMap,
32 fs,
33 path::{Path, PathBuf},
34 sync::Arc,
35};
36
37#[derive(Debug)]
39pub struct BlockchainTests {
40 suite_path: PathBuf,
41}
42
43impl BlockchainTests {
44 pub const fn new(suite_path: PathBuf) -> Self {
46 Self { suite_path }
47 }
48}
49
50impl Suite for BlockchainTests {
51 type Case = BlockchainTestCase;
52
53 fn suite_path(&self) -> &Path {
54 &self.suite_path
55 }
56}
57
58#[derive(Debug, PartialEq, Eq)]
60pub struct BlockchainTestCase {
61 pub tests: BTreeMap<String, BlockchainTest>,
63 pub skip: bool,
65}
66
67impl BlockchainTestCase {
68 const fn excluded_fork(network: ForkSpec) -> bool {
70 matches!(
71 network,
72 ForkSpec::ByzantiumToConstantinopleAt5 |
73 ForkSpec::Constantinople |
74 ForkSpec::ConstantinopleFix |
75 ForkSpec::MergeEOF |
76 ForkSpec::MergeMeterInitCode |
77 ForkSpec::MergePush0
78 )
79 }
80
81 #[inline]
87 fn is_uncle_sidechain_case(name: &str) -> bool {
88 name.contains("UncleFromSideChain")
89 }
90
91 #[inline]
98 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
99 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
100 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
101 })
102 }
103
104 pub fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
107 let expectation = Self::expected_failure(case);
108 match run_case(case) {
109 Ok(()) => {
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(())
118 }
119 }
120
121 Err(Error::BlockProcessingFailed { block_number, err }) => {
123 match expectation {
124 Some((expected, _)) if block_number == expected => Ok(()),
126
127 _ if Self::is_uncle_sidechain_case(name) => Ok(()),
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, 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 .into_iter()
179 .filter(|(_, case)| !Self::excluded_fork(case.network))
180 .par_bridge_buffered()
181 .with_min_len(64)
182 .try_for_each(|(name, case)| Self::run_single_case(&name, &case).map(|_| ()))
183 }
184}
185
186fn run_case(case: &BlockchainTest) -> Result<(), Error> {
199 let chain_spec = case.network.to_chain_spec();
201 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
202 let provider = factory.database_provider_rw().unwrap();
203
204 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
206 case.genesis_block_header.clone().into(),
207 Default::default(),
208 )
209 .try_recover()
210 .unwrap();
211
212 provider.insert_block(&genesis_block).map_err(|err| Error::block_failed(0, err))?;
213
214 provider
216 .static_file_provider()
217 .latest_writer(StaticFileSegment::Receipts)
218 .and_then(|mut writer| writer.increment_block(0))
219 .map_err(|err| Error::block_failed(0, err))?;
220
221 let genesis_state = case.pre.clone().into_genesis_state();
222 insert_genesis_state(&provider, genesis_state.iter())
223 .map_err(|err| Error::block_failed(0, err))?;
224 insert_genesis_hashes(&provider, genesis_state.iter())
225 .map_err(|err| Error::block_failed(0, err))?;
226 insert_genesis_history(&provider, genesis_state.iter())
227 .map_err(|err| Error::block_failed(0, err))?;
228
229 let (_, trie_updates) = reth_trie_db::with_adapter!(provider, |A| {
231 StateRoot::<reth_trie_db::DatabaseTrieCursorFactory<_, A>, _>::from_tx(provider.tx_ref())
232 .root_with_updates()
233 })
234 .map_err(|err| Error::block_failed(0, err))?;
235 provider.write_trie_updates(trie_updates).map_err(|err| Error::block_failed(0, err))?;
236
237 let blocks = decode_blocks(&case.blocks)?;
239
240 let executor_provider = EthEvmConfig::ethereum(chain_spec.clone());
241 let mut parent = genesis_block;
242 let mut bal_buf = Vec::new();
243
244 for (block_index, block) in blocks.iter().enumerate() {
245 let block_number = (block_index + 1) as u64;
247
248 provider.insert_block(block).map_err(|err| Error::block_failed(block_number, err))?;
250 provider
251 .static_file_provider()
252 .commit()
253 .map_err(|err| Error::block_failed(block_number, err))?;
254
255 pre_execution_checks(chain_spec.clone(), &parent, block)
257 .map_err(|err| Error::block_failed(block_number, err))?;
258
259 let state_provider = provider.latest();
261 let state_db = StateProviderDatabase((&state_provider).into_evm_state_provider());
262 let mut executor = executor_provider.batch_executor(state_db);
263
264 let result = executor
265 .execute_one(&(*block).clone())
266 .map_err(|err| Error::block_failed(block_number, err))?;
267 let block_access_list_hash =
270 executor.take_bal().map(|bal| Bal::from(bal).compute_hash_with_buf(&mut bal_buf));
271 let output = BlockExecutionOutput { state: executor.into_state().take_bundle(), result };
272
273 validate_block_post_execution(block, &chain_spec, &output, None, block_access_list_hash)
275 .map_err(|err| Error::block_failed(block_number, err))?;
276
277 let hashed_state = state_provider
279 .hashed_post_state(&output.state)
280 .map_err(|err| Error::block_failed(block_number, err))?;
281 let sorted = hashed_state.clone_into_sorted();
282 let (computed_state_root, trie_updates) = reth_trie_db::with_adapter!(provider, |A| {
283 StateRoot::<reth_trie_db::DatabaseTrieCursorFactory<_, A>, _>::overlay_root_with_updates(
284 provider.tx_ref(),
285 &sorted,
286 )
287 })
288 .map_err(|err| Error::block_failed(block_number, err))?;
289 if computed_state_root != block.state_root {
290 return Err(Error::block_failed(
291 block_number,
292 Error::Assertion("state root mismatch".to_string()),
293 ));
294 }
295
296 provider
298 .write_state(
299 &ExecutionOutcome::single(block.number, output),
300 OriginalValuesKnown::Yes,
301 StateWriteConfig::default(),
302 )
303 .map_err(|err| Error::block_failed(block_number, err))?;
304
305 provider
306 .write_hashed_state(&hashed_state.into_sorted())
307 .map_err(|err| Error::block_failed(block_number, err))?;
308 provider
310 .write_trie_updates(trie_updates)
311 .map_err(|err| Error::block_failed(block_number, err))?;
312 provider
313 .update_history_indices(block.number..=block.number)
314 .map_err(|err| Error::block_failed(block_number, err))?;
315
316 parent = block.clone()
318 }
319
320 match &case.post_state {
321 Some(expected_post_state) => {
322 for (address, account) in expected_post_state {
332 account.assert_db(*address, provider.tx_ref())?;
333 }
334 }
335 None => {
336 }
339 }
340
341 Ok(())
342}
343
344fn decode_blocks(
345 test_case_blocks: &[crate::models::Block],
346) -> Result<Vec<RecoveredBlock<Block>>, Error> {
347 let mut blocks = Vec::with_capacity(test_case_blocks.len());
348 for (block_index, block) in test_case_blocks.iter().enumerate() {
349 let block_number = (block_index + 1) as u64;
352
353 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
354 .map_err(|err| Error::block_failed(block_number, err))?;
355
356 let recovered_block =
357 decoded.try_recover().map_err(|err| Error::block_failed(block_number, err))?;
358
359 blocks.push(recovered_block);
360 }
361
362 Ok(blocks)
363}
364
365fn pre_execution_checks(
366 chain_spec: Arc<ChainSpec>,
367 parent: &RecoveredBlock<Block>,
368 block: &RecoveredBlock<Block>,
369) -> Result<(), Error> {
370 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
371
372 let sealed_header = block.sealed_header();
373
374 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
375 &consensus,
376 block.body(),
377 sealed_header,
378 )?;
379 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
380 consensus.validate_header(sealed_header)?;
381 consensus.validate_block_pre_execution(block)?;
382
383 Ok(())
384}
385
386pub fn should_skip(path: &Path) -> bool {
393 let path_str = path.to_str().expect("Path is not valid UTF-8");
394 let name = path.file_name().unwrap().to_str().unwrap();
395 matches!(
396 name,
397 | "ValueOverflow.json"
400 | "ValueOverflowParis.json"
401
402 | "typeTwoBerlin.json"
404
405 | "CreateTransactionHighNonce.json"
409
410 | "HighGasPrice.json"
413 | "HighGasPriceParis.json"
414
415 | "accessListExample.json"
419 | "basefeeExample.json"
420 | "eip1559.json"
421 | "mergeTest.json"
422
423 | "loopExp.json"
425 | "Call50000_sha256.json"
426 | "static_Call50000_sha256.json"
427 | "loopMul.json"
428 | "CALLBlake2f_MaxRounds.json"
429 | "shiftCombinations.json"
430
431 | "RevertInCreateInInit_Paris.json"
433 | "RevertInCreateInInit.json"
434 | "dynamicAccountOverwriteEmpty.json"
435 | "dynamicAccountOverwriteEmpty_Paris.json"
436 | "RevertInCreateInInitCreate2Paris.json"
437 | "create2collisionStorage.json"
438 | "RevertInCreateInInitCreate2.json"
439 | "create2collisionStorageParis.json"
440 | "InitCollision.json"
441 | "InitCollisionParis.json"
442 )
443 || path_contains(path_str, &["EIPTests", "stEOF"])
445}
446
447fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
449 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
450 path_str.contains(&rhs)
451}