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::{HashedPostState, KeccakKeyHasher, StateRoot};
24use reth_trie_db::DatabaseStateRoot;
25use std::{
26 collections::BTreeMap,
27 fs,
28 path::{Path, PathBuf},
29 sync::Arc,
30};
31
32#[derive(Debug)]
34pub struct BlockchainTests {
35 suite_path: PathBuf,
36}
37
38impl BlockchainTests {
39 pub const fn new(suite_path: PathBuf) -> Self {
41 Self { suite_path }
42 }
43}
44
45impl Suite for BlockchainTests {
46 type Case = BlockchainTestCase;
47
48 fn suite_path(&self) -> &Path {
49 &self.suite_path
50 }
51}
52
53#[derive(Debug, PartialEq, Eq)]
55pub struct BlockchainTestCase {
56 pub tests: BTreeMap<String, BlockchainTest>,
58 pub skip: bool,
60}
61
62impl BlockchainTestCase {
63 const fn excluded_fork(network: ForkSpec) -> bool {
65 matches!(
66 network,
67 ForkSpec::ByzantiumToConstantinopleAt5 |
68 ForkSpec::Constantinople |
69 ForkSpec::ConstantinopleFix |
70 ForkSpec::MergeEOF |
71 ForkSpec::MergeMeterInitCode |
72 ForkSpec::MergePush0
73 )
74 }
75
76 #[inline]
82 fn is_uncle_sidechain_case(name: &str) -> bool {
83 name.contains("UncleFromSideChain")
84 }
85
86 #[inline]
93 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
94 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
95 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
96 })
97 }
98
99 pub fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
102 let expectation = Self::expected_failure(case);
103 match run_case(case) {
104 Ok(()) => {
106 if let Some((block, msg)) = expectation {
108 Err(Error::Assertion(format!(
109 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
110 )))
111 } else {
112 Ok(())
113 }
114 }
115
116 Err(Error::BlockProcessingFailed { block_number, err }) => {
118 match expectation {
119 Some((expected, _)) if block_number == expected => Ok(()),
121
122 _ if Self::is_uncle_sidechain_case(name) => Ok(()),
125
126 Some((expected, _)) => Err(Error::Assertion(format!(
128 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
129 ))),
130
131 None => Err(Error::BlockProcessingFailed { block_number, err }),
133 }
134 }
135
136 Err(other) => Err(other),
144 }
145 }
146}
147
148impl Case for BlockchainTestCase {
149 fn load(path: &Path) -> Result<Self, Error> {
150 Ok(Self {
151 tests: {
152 let s = fs::read_to_string(path)
153 .map_err(|error| Error::Io { path: path.into(), error })?;
154 serde_json::from_str(&s)
155 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
156 },
157 skip: should_skip(path),
158 })
159 }
160
161 fn run(self) -> Result<(), Error> {
166 if self.skip {
168 return Err(Error::Skipped);
169 }
170
171 self.tests
173 .into_iter()
174 .filter(|(_, case)| !Self::excluded_fork(case.network))
175 .par_bridge_buffered()
176 .with_min_len(64)
177 .try_for_each(|(name, case)| Self::run_single_case(&name, &case).map(|_| ()))
178 }
179}
180
181fn run_case(case: &BlockchainTest) -> Result<(), Error> {
194 let chain_spec = case.network.to_chain_spec();
196 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
197 let provider = factory.database_provider_rw().unwrap();
198
199 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
201 case.genesis_block_header.clone().into(),
202 Default::default(),
203 )
204 .try_recover()
205 .unwrap();
206
207 provider.insert_block(&genesis_block).map_err(|err| Error::block_failed(0, err))?;
208
209 provider
211 .static_file_provider()
212 .latest_writer(StaticFileSegment::Receipts)
213 .and_then(|mut writer| writer.increment_block(0))
214 .map_err(|err| Error::block_failed(0, err))?;
215
216 let genesis_state = case.pre.clone().into_genesis_state();
217 insert_genesis_state(&provider, genesis_state.iter())
218 .map_err(|err| Error::block_failed(0, err))?;
219 insert_genesis_hashes(&provider, genesis_state.iter())
220 .map_err(|err| Error::block_failed(0, err))?;
221 insert_genesis_history(&provider, genesis_state.iter())
222 .map_err(|err| Error::block_failed(0, err))?;
223
224 let blocks = decode_blocks(&case.blocks)?;
226
227 let executor_provider = EthEvmConfig::ethereum(chain_spec.clone());
228 let mut parent = genesis_block;
229
230 for (block_index, block) in blocks.iter().enumerate() {
231 let block_number = (block_index + 1) as u64;
233
234 provider.insert_block(block).map_err(|err| Error::block_failed(block_number, err))?;
236 provider
237 .static_file_provider()
238 .commit()
239 .map_err(|err| Error::block_failed(block_number, err))?;
240
241 pre_execution_checks(chain_spec.clone(), &parent, block)
243 .map_err(|err| Error::block_failed(block_number, err))?;
244
245 let state_provider = provider.latest();
247 let state_db = StateProviderDatabase(&state_provider);
248 let executor = executor_provider.batch_executor(state_db);
249
250 let output = executor
251 .execute(&(*block).clone())
252 .map_err(|err| Error::block_failed(block_number, err))?;
253
254 validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests, None)
256 .map_err(|err| Error::block_failed(block_number, err))?;
257
258 let hashed_state =
260 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
261 let sorted = hashed_state.clone_into_sorted();
262 let (computed_state_root, _) = reth_trie_db::with_adapter!(provider, |A| {
263 StateRoot::<reth_trie_db::DatabaseTrieCursorFactory<_, A>, _>::overlay_root_with_updates(
264 provider.tx_ref(),
265 &sorted,
266 )
267 })
268 .map_err(|err| Error::block_failed(block_number, err))?;
269 if computed_state_root != block.state_root {
270 return Err(Error::block_failed(
271 block_number,
272 Error::Assertion("state root mismatch".to_string()),
273 ));
274 }
275
276 provider
278 .write_state(
279 &ExecutionOutcome::single(block.number, output),
280 OriginalValuesKnown::Yes,
281 StateWriteConfig::default(),
282 )
283 .map_err(|err| Error::block_failed(block_number, err))?;
284
285 provider
286 .write_hashed_state(&hashed_state.into_sorted())
287 .map_err(|err| Error::block_failed(block_number, err))?;
288 provider
289 .update_history_indices(block.number..=block.number)
290 .map_err(|err| Error::block_failed(block_number, err))?;
291
292 parent = block.clone()
294 }
295
296 match &case.post_state {
297 Some(expected_post_state) => {
298 for (address, account) in expected_post_state {
308 account.assert_db(*address, provider.tx_ref())?;
309 }
310 }
311 None => {
312 }
315 }
316
317 Ok(())
318}
319
320fn decode_blocks(
321 test_case_blocks: &[crate::models::Block],
322) -> Result<Vec<RecoveredBlock<Block>>, Error> {
323 let mut blocks = Vec::with_capacity(test_case_blocks.len());
324 for (block_index, block) in test_case_blocks.iter().enumerate() {
325 let block_number = (block_index + 1) as u64;
328
329 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
330 .map_err(|err| Error::block_failed(block_number, err))?;
331
332 let recovered_block =
333 decoded.clone().try_recover().map_err(|err| Error::block_failed(block_number, err))?;
334
335 blocks.push(recovered_block);
336 }
337
338 Ok(blocks)
339}
340
341fn pre_execution_checks(
342 chain_spec: Arc<ChainSpec>,
343 parent: &RecoveredBlock<Block>,
344 block: &RecoveredBlock<Block>,
345) -> Result<(), Error> {
346 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
347
348 let sealed_header = block.sealed_header();
349
350 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
351 &consensus,
352 block.body(),
353 sealed_header,
354 )?;
355 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
356 consensus.validate_header(sealed_header)?;
357 consensus.validate_block_pre_execution(block)?;
358
359 Ok(())
360}
361
362pub fn should_skip(path: &Path) -> bool {
369 let path_str = path.to_str().expect("Path is not valid UTF-8");
370 let name = path.file_name().unwrap().to_str().unwrap();
371 matches!(
372 name,
373 | "ValueOverflow.json"
376 | "ValueOverflowParis.json"
377
378 | "typeTwoBerlin.json"
380
381 | "CreateTransactionHighNonce.json"
385
386 | "HighGasPrice.json"
389 | "HighGasPriceParis.json"
390
391 | "accessListExample.json"
395 | "basefeeExample.json"
396 | "eip1559.json"
397 | "mergeTest.json"
398
399 | "loopExp.json"
401 | "Call50000_sha256.json"
402 | "static_Call50000_sha256.json"
403 | "loopMul.json"
404 | "CALLBlake2f_MaxRounds.json"
405 | "shiftCombinations.json"
406
407 | "RevertInCreateInInit_Paris.json"
409 | "RevertInCreateInInit.json"
410 | "dynamicAccountOverwriteEmpty.json"
411 | "dynamicAccountOverwriteEmpty_Paris.json"
412 | "RevertInCreateInInitCreate2Paris.json"
413 | "create2collisionStorage.json"
414 | "RevertInCreateInInitCreate2.json"
415 | "create2collisionStorageParis.json"
416 | "InitCollision.json"
417 | "InitCollisionParis.json"
418 )
419 || path_contains(path_str, &["EIPTests", "stEOF"])
421}
422
423fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
425 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
426 path_str.contains(&rhs)
427}