1use crate::{
4 models::{BlockchainTest, ForkSpec},
5 Case, Error, Suite,
6};
7use alloy_rlp::Decodable;
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::{BlockExecutorProvider, Executor};
15use reth_evm_ethereum::execute::EthExecutorProvider;
16use reth_primitives_traits::{RecoveredBlock, SealedBlock};
17use reth_provider::{
18 test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory,
19 ExecutionOutcome, HistoryWriter, OriginalValuesKnown, StateWriter, StorageLocation,
20};
21use reth_revm::database::StateProviderDatabase;
22use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot};
23use reth_trie_db::DatabaseStateRoot;
24use std::{collections::BTreeMap, fs, path::Path, sync::Arc};
25
26#[derive(Debug)]
28pub struct BlockchainTests {
29 suite: String,
30}
31
32impl BlockchainTests {
33 pub const fn new(suite: String) -> Self {
35 Self { suite }
36 }
37}
38
39impl Suite for BlockchainTests {
40 type Case = BlockchainTestCase;
41
42 fn suite_name(&self) -> String {
43 format!("BlockchainTests/{}", self.suite)
44 }
45}
46
47#[derive(Debug, PartialEq, Eq)]
49pub struct BlockchainTestCase {
50 tests: BTreeMap<String, BlockchainTest>,
51 skip: bool,
52}
53
54impl BlockchainTestCase {
55 const fn excluded_fork(network: ForkSpec) -> bool {
57 matches!(
58 network,
59 ForkSpec::ByzantiumToConstantinopleAt5 |
60 ForkSpec::Constantinople |
61 ForkSpec::ConstantinopleFix |
62 ForkSpec::MergeEOF |
63 ForkSpec::MergeMeterInitCode |
64 ForkSpec::MergePush0 |
65 ForkSpec::Unknown
66 )
67 }
68
69 #[inline]
75 fn is_uncle_sidechain_case(name: &str) -> bool {
76 name.contains("UncleFromSideChain")
77 }
78
79 #[inline]
86 fn expected_failure(case: &BlockchainTest) -> Option<(u64, String)> {
87 case.blocks.iter().enumerate().find_map(|(idx, blk)| {
88 blk.expect_exception.as_ref().map(|msg| ((idx + 1) as u64, msg.clone()))
89 })
90 }
91
92 fn run_single_case(name: &str, case: &BlockchainTest) -> Result<(), Error> {
95 let expectation = Self::expected_failure(case);
96 match run_case(case) {
97 Ok(()) => {
99 if let Some((block, msg)) = expectation {
101 Err(Error::Assertion(format!(
102 "Test case: {name}\nExpected failure at block {block} - {msg}, but all blocks succeeded",
103 )))
104 } else {
105 Ok(())
106 }
107 }
108
109 Err(Error::BlockProcessingFailed { block_number }) => match expectation {
111 Some((expected, _)) if block_number == expected => Ok(()),
113
114 _ if Self::is_uncle_sidechain_case(name) => Ok(()),
117
118 Some((expected, _)) => Err(Error::Assertion(format!(
120 "Test case: {name}\nExpected failure at block {expected}\nGot failure at block {block_number}",
121 ))),
122
123 None => Err(Error::BlockProcessingFailed { block_number }),
125 },
126
127 Err(other) => Err(other),
135 }
136 }
137}
138
139impl Case for BlockchainTestCase {
140 fn load(path: &Path) -> Result<Self, Error> {
141 Ok(Self {
142 tests: {
143 let s = fs::read_to_string(path)
144 .map_err(|error| Error::Io { path: path.into(), error })?;
145 serde_json::from_str(&s)
146 .map_err(|error| Error::CouldNotDeserialize { path: path.into(), error })?
147 },
148 skip: should_skip(path),
149 })
150 }
151
152 fn run(&self) -> Result<(), Error> {
157 if self.skip {
159 return Err(Error::Skipped)
160 }
161
162 self.tests
164 .iter()
165 .filter(|(_, case)| !Self::excluded_fork(case.network))
166 .par_bridge()
167 .try_for_each(|(name, case)| Self::run_single_case(name, case))?;
168
169 Ok(())
170 }
171}
172
173fn run_case(case: &BlockchainTest) -> Result<(), Error> {
186 let chain_spec: Arc<ChainSpec> = Arc::new(case.network.into());
188 let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
189 let provider = factory.database_provider_rw().unwrap();
190
191 let genesis_block = SealedBlock::<Block>::from_sealed_parts(
193 case.genesis_block_header.clone().into(),
194 Default::default(),
195 )
196 .try_recover()
197 .unwrap();
198
199 provider
200 .insert_block(genesis_block.clone(), StorageLocation::Database)
201 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
202
203 let genesis_state = case.pre.clone().into_genesis_state();
204 insert_genesis_state(&provider, genesis_state.iter())
205 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
206 insert_genesis_hashes(&provider, genesis_state.iter())
207 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
208 insert_genesis_history(&provider, genesis_state.iter())
209 .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?;
210
211 let blocks = decode_blocks(&case.blocks)?;
213
214 let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone());
215 let mut parent = genesis_block;
216
217 for (block_index, block) in blocks.iter().enumerate() {
218 let block_number = (block_index + 1) as u64;
220
221 provider
223 .insert_block(block.clone(), StorageLocation::Database)
224 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
225
226 pre_execution_checks(chain_spec.clone(), &parent, block)
228 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
229
230 let state_db = StateProviderDatabase(provider.latest());
232 let executor = executor_provider.executor(state_db);
233 let output =
234 executor.execute(block).map_err(|_| Error::BlockProcessingFailed { block_number })?;
235
236 validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests)
238 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
239
240 let hashed_state =
242 HashedPostState::from_bundle_state::<KeccakKeyHasher>(output.state.state());
243 let (computed_state_root, _) =
244 StateRoot::overlay_root_with_updates(provider.tx_ref(), hashed_state.clone())
245 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
246 if computed_state_root != block.state_root {
247 return Err(Error::BlockProcessingFailed { block_number })
248 }
249
250 provider
252 .write_state(
253 &ExecutionOutcome::single(block.number, output),
254 OriginalValuesKnown::Yes,
255 StorageLocation::Database,
256 )
257 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
258
259 provider
260 .write_hashed_state(&hashed_state.into_sorted())
261 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
262 provider
263 .update_history_indices(block.number..=block.number)
264 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
265
266 parent = block.clone()
268 }
269
270 let expected_post_state = case.post_state.as_ref().ok_or(Error::MissingPostState)?;
280 for (&address, account) in expected_post_state {
281 account.assert_db(address, provider.tx_ref())?;
282 }
283
284 Ok(())
285}
286
287fn decode_blocks(
288 test_case_blocks: &[crate::models::Block],
289) -> Result<Vec<RecoveredBlock<Block>>, Error> {
290 let mut blocks = Vec::with_capacity(test_case_blocks.len());
291 for (block_index, block) in test_case_blocks.iter().enumerate() {
292 let block_number = (block_index + 1) as u64;
295
296 let decoded = SealedBlock::<Block>::decode(&mut block.rlp.as_ref())
297 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
298
299 let recovered_block = decoded
300 .clone()
301 .try_recover()
302 .map_err(|_| Error::BlockProcessingFailed { block_number })?;
303
304 blocks.push(recovered_block);
305 }
306
307 Ok(blocks)
308}
309
310fn pre_execution_checks(
311 chain_spec: Arc<ChainSpec>,
312 parent: &RecoveredBlock<Block>,
313 block: &RecoveredBlock<Block>,
314) -> Result<(), Error> {
315 let consensus: EthBeaconConsensus<ChainSpec> = EthBeaconConsensus::new(chain_spec);
316
317 let sealed_header = block.sealed_header();
318
319 <EthBeaconConsensus<ChainSpec> as Consensus<Block>>::validate_body_against_header(
320 &consensus,
321 block.body(),
322 sealed_header,
323 )?;
324 consensus.validate_header_against_parent(sealed_header, parent.sealed_header())?;
325 consensus.validate_header(sealed_header)?;
326 consensus.validate_block_pre_execution(block)?;
327
328 Ok(())
329}
330
331pub fn should_skip(path: &Path) -> bool {
338 let path_str = path.to_str().expect("Path is not valid UTF-8");
339 let name = path.file_name().unwrap().to_str().unwrap();
340 matches!(
341 name,
342 | "ValueOverflow.json"
345 | "ValueOverflowParis.json"
346
347 | "typeTwoBerlin.json"
349
350 | "CreateTransactionHighNonce.json"
354
355 | "HighGasPrice.json"
358 | "HighGasPriceParis.json"
359
360 | "accessListExample.json"
364 | "basefeeExample.json"
365 | "eip1559.json"
366 | "mergeTest.json"
367
368 | "loopExp.json"
370 | "Call50000_sha256.json"
371 | "static_Call50000_sha256.json"
372 | "loopMul.json"
373 | "CALLBlake2f_MaxRounds.json"
374 | "shiftCombinations.json"
375
376 | "RevertInCreateInInit_Paris.json"
378 | "RevertInCreateInInit.json"
379 | "dynamicAccountOverwriteEmpty.json"
380 | "dynamicAccountOverwriteEmpty_Paris.json"
381 | "RevertInCreateInInitCreate2Paris.json"
382 | "create2collisionStorage.json"
383 | "RevertInCreateInInitCreate2.json"
384 | "create2collisionStorageParis.json"
385 | "InitCollision.json"
386 | "InitCollisionParis.json"
387 )
388 || path_contains(path_str, &["EIPTests", "stEOF"])
390}
391
392fn path_contains(path_str: &str, rhs: &[&str]) -> bool {
394 let rhs = rhs.join(std::path::MAIN_SEPARATOR_STR);
395 path_str.contains(&rhs)
396}