1use crate::{
4 models::{BlockchainTest, ForkSpec},
5 Case, Error, Suite,
6};
7use alloy_rlp::Decodable;
8use rayon::iter::{IndexedParallelIterator, 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::{ParallelBridgeBuffered, RecoveredBlock, SealedBlock};
17use reth_provider::{
18 test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
19 ExecutionOutcome, HistoryWriter, OriginalValuesKnown, StateWriteConfig, StateWriter,
20 StaticFileProviderFactory, StaticFileSegment, StaticFileWriter, StorageSettingsCache,
21};
22use reth_revm::database::StateProviderDatabase;
23use reth_trie::{
24 hashed_cursor::zero_destroyed_account_storage, HashedPostState, KeccakKeyHasher, StateRoot,
25};
26use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseStateRoot};
27use std::{
28 collections::BTreeMap,
29 fs,
30 path::{Path, PathBuf},
31 sync::Arc,
32};
33
34#[derive(Debug)]
36pub struct BlockchainTests {
37 suite_path: PathBuf,
38}
39
40impl BlockchainTests {
41 pub const fn new(suite_path: PathBuf) -> Self {
43 Self { suite_path }
44 }
45}
46
47impl Suite for BlockchainTests {
48 type Case = BlockchainTestCase;
49
50 fn suite_path(&self) -> &Path {
51 &self.suite_path
52 }
53}
54
55#[derive(Debug, PartialEq, Eq)]
57pub struct BlockchainTestCase {
58 pub tests: BTreeMap<String, BlockchainTest>,
60 pub skip: bool,
62}
63
64impl BlockchainTestCase {
65 const fn excluded_fork(network: ForkSpec) -> bool {
67 matches!(
68 network,
69 ForkSpec::ByzantiumToConstantinopleAt5 |
70 ForkSpec::Constantinople |
71 ForkSpec::ConstantinopleFix |
72 ForkSpec::MergeEOF |
73 ForkSpec::MergeMeterInitCode |
74 ForkSpec::MergePush0
75 )
76 }
77
78 #[inline]
84 fn is_uncle_sidechain_case(name: &str) -> bool {
85 name.contains("UncleFromSideChain")
86 }
87
88 #[inline]
95 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
96 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
97 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
98 })
99 }
100
101 pub fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
104 let expectation = Self::expected_failure(case);
105 match run_case(case) {
106 Ok(()) => {
108 if let Some((block, msg)) = expectation {
110 Err(Error::Assertion(format!(
111 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
112 )))
113 } else {
114 Ok(())
115 }
116 }
117
118 Err(Error::BlockProcessingFailed { block_number, err }) => {
120 match expectation {
121 Some((expected, _)) if block_number == expected => Ok(()),
123
124 _ if Self::is_uncle_sidechain_case(name) => Ok(()),
127
128 Some((expected, _)) => Err(Error::Assertion(format!(
130 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
131 ))),
132
133 None => Err(Error::BlockProcessingFailed { block_number, err }),
135 }
136 }
137
138 Err(other) => Err(other),
146 }
147 }
148}
149
150impl Case for BlockchainTestCase {
151 fn load(path: &Path) -> Result<Self, Error> {
152 Ok(Self {
153 tests: {
154 let s = fs::read_to_string(path)
155 .map_err(|error| Error::Io { path: path.into(), error })?;
156 serde_json::from_str(&s)
157 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
158 },
159 skip: should_skip(path),
160 })
161 }
162
163 fn run(self) -> Result<(), Error> {
168 if self.skip {
170 return Err(Error::Skipped);
171 }
172
173 self.tests
175 .into_iter()
176 .filter(|(_, case)| !Self::excluded_fork(case.network))
177 .par_bridge_buffered()
178 .with_min_len(64)
179 .try_for_each(|(name, case)| Self::run_single_case(&name, &case).map(|_| ()))
180 }
181}
182
183fn run_case(case: &BlockchainTest) -> Result<(), Error> {
196 let chain_spec = case.network.to_chain_spec();
198 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
199 let provider = factory.database_provider_rw().unwrap();
200
201 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
203 case.genesis_block_header.clone().into(),
204 Default::default(),
205 )
206 .try_recover()
207 .unwrap();
208
209 provider.insert_block(&genesis_block).map_err(|err| Error::block_failed(0, err))?;
210
211 provider
213 .static_file_provider()
214 .latest_writer(StaticFileSegment::Receipts)
215 .and_then(|mut writer| writer.increment_block(0))
216 .map_err(|err| Error::block_failed(0, err))?;
217
218 let genesis_state = case.pre.clone().into_genesis_state();
219 insert_genesis_state(&provider, genesis_state.iter())
220 .map_err(|err| Error::block_failed(0, err))?;
221 insert_genesis_hashes(&provider, genesis_state.iter())
222 .map_err(|err| Error::block_failed(0, err))?;
223 insert_genesis_history(&provider, genesis_state.iter())
224 .map_err(|err| Error::block_failed(0, err))?;
225
226 let blocks = decode_blocks(&case.blocks)?;
228
229 let executor_provider = EthEvmConfig::ethereum(chain_spec.clone());
230 let mut parent = genesis_block;
231
232 for (block_index, block) in blocks.iter().enumerate() {
233 let block_number = (block_index + 1) as u64;
235
236 provider.insert_block(block).map_err(|err| Error::block_failed(block_number, err))?;
238 provider
239 .static_file_provider()
240 .commit()
241 .map_err(|err| Error::block_failed(block_number, err))?;
242
243 pre_execution_checks(chain_spec.clone(), &parent, block)
245 .map_err(|err| Error::block_failed(block_number, err))?;
246
247 let state_provider = provider.latest();
249 let state_db = StateProviderDatabase(&state_provider);
250 let executor = executor_provider.batch_executor(state_db);
251
252 let output = executor
253 .execute(&(*block).clone())
254 .map_err(|err| Error::block_failed(block_number, err))?;
255
256 validate_block_post_execution(block, &chain_spec, &output, None, None)
258 .map_err(|err| Error::block_failed(block_number, err))?;
259
260 let mut hashed_state =
262 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
263 zero_destroyed_account_storage(
264 &DatabaseHashedCursorFactory::new(provider.tx_ref()),
265 output.state.state(),
266 &mut hashed_state,
267 )
268 .map_err(|err| Error::block_failed(block_number, err))?;
269 let sorted = hashed_state.clone_into_sorted();
270 let (computed_state_root, _) = reth_trie_db::with_adapter!(provider, |A| {
271 StateRoot::<reth_trie_db::DatabaseTrieCursorFactory<_, A>, _>::overlay_root_with_updates(
272 provider.tx_ref(),
273 &sorted,
274 )
275 })
276 .map_err(|err| Error::block_failed(block_number, err))?;
277 if computed_state_root != block.state_root {
278 return Err(Error::block_failed(
279 block_number,
280 Error::Assertion("state root mismatch".to_string()),
281 ));
282 }
283
284 provider
286 .write_state(
287 &ExecutionOutcome::single(block.number, output),
288 OriginalValuesKnown::Yes,
289 StateWriteConfig::default(),
290 )
291 .map_err(|err| Error::block_failed(block_number, err))?;
292
293 provider
294 .write_hashed_state(&hashed_state.into_sorted())
295 .map_err(|err| Error::block_failed(block_number, err))?;
296 provider
297 .update_history_indices(block.number..=block.number)
298 .map_err(|err| Error::block_failed(block_number, err))?;
299
300 parent = block.clone()
302 }
303
304 match &case.post_state {
305 Some(expected_post_state) => {
306 for (address, account) in expected_post_state {
316 account.assert_db(*address, provider.tx_ref())?;
317 }
318 }
319 None => {
320 }
323 }
324
325 Ok(())
326}
327
328fn decode_blocks(
329 test_case_blocks: &[crate::models::Block],
330) -> Result<Vec<RecoveredBlock<Block>>, Error> {
331 let mut blocks = Vec::with_capacity(test_case_blocks.len());
332 for (block_index, block) in test_case_blocks.iter().enumerate() {
333 let block_number = (block_index + 1) as u64;
336
337 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
338 .map_err(|err| Error::block_failed(block_number, err))?;
339
340 let recovered_block =
341 decoded.clone().try_recover().map_err(|err| Error::block_failed(block_number, err))?;
342
343 blocks.push(recovered_block);
344 }
345
346 Ok(blocks)
347}
348
349fn pre_execution_checks(
350 chain_spec: Arc<ChainSpec>,
351 parent: &RecoveredBlock<Block>,
352 block: &RecoveredBlock<Block>,
353) -> Result<(), Error> {
354 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
355
356 let sealed_header = block.sealed_header();
357
358 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
359 &consensus,
360 block.body(),
361 sealed_header,
362 )?;
363 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
364 consensus.validate_header(sealed_header)?;
365 consensus.validate_block_pre_execution(block)?;
366
367 Ok(())
368}
369
370pub fn should_skip(path: &Path) -> bool {
377 let path_str = path.to_str().expect("Path is not valid UTF-8");
378 let name = path.file_name().unwrap().to_str().unwrap();
379 matches!(
380 name,
381 | "ValueOverflow.json"
384 | "ValueOverflowParis.json"
385
386 | "typeTwoBerlin.json"
388
389 | "CreateTransactionHighNonce.json"
393
394 | "HighGasPrice.json"
397 | "HighGasPriceParis.json"
398
399 | "accessListExample.json"
403 | "basefeeExample.json"
404 | "eip1559.json"
405 | "mergeTest.json"
406
407 | "loopExp.json"
409 | "Call50000_sha256.json"
410 | "static_Call50000_sha256.json"
411 | "loopMul.json"
412 | "CALLBlake2f_MaxRounds.json"
413 | "shiftCombinations.json"
414
415 | "RevertInCreateInInit_Paris.json"
417 | "RevertInCreateInInit.json"
418 | "dynamicAccountOverwriteEmpty.json"
419 | "dynamicAccountOverwriteEmpty_Paris.json"
420 | "RevertInCreateInInitCreate2Paris.json"
421 | "create2collisionStorage.json"
422 | "RevertInCreateInInitCreate2.json"
423 | "create2collisionStorageParis.json"
424 | "InitCollision.json"
425 | "InitCollisionParis.json"
426 )
427 || path_contains(path_str, &["EIPTests", "stEOF"])
429}
430
431fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
433 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
434 path_str.contains(&rhs)
435}