1use crate::{assert::assert_equal, Error};
4use alloy_consensus::Header as RethHeader;
5use alloy_eips::eip4895::Withdrawals;
6use alloy_genesis::GenesisAccount;
7use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256};
8use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, ForkCondition};
9use reth_db_api::{cursor::DbDupCursorRO, tables, transaction::DbTx};
10use reth_primitives_traits::SealedHeader;
11use revm::primitives::HashMap;
12use serde::Deserialize;
13use std::{
14 collections::BTreeMap,
15 ops::Deref,
16 sync::{Arc, OnceLock, RwLock},
17};
18
19#[derive(Debug, PartialEq, Eq, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct BlockchainTest {
23 pub genesis_block_header: Header,
25 #[serde(rename = "genesisRLP")]
27 pub genesis_rlp: Option<Bytes>,
28 pub blocks: Vec<Block>,
30 pub post_state: Option<BTreeMap<Address, Account>>,
32 pub pre: State,
34 pub lastblockhash: B256,
36 pub network: ForkSpec,
38 #[serde(default)]
39 pub seal_engine: SealEngine,
41}
42
43#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
45#[serde(rename_all = "camelCase")]
46pub struct Header {
47 pub bloom: Bloom,
49 pub coinbase: Address,
51 pub difficulty: U256,
53 pub extra_data: Bytes,
55 pub gas_limit: U256,
57 pub gas_used: U256,
59 pub hash: B256,
61 pub mix_hash: B256,
63 pub nonce: B64,
65 pub number: U256,
67 pub parent_hash: B256,
69 pub receipt_trie: B256,
71 pub state_root: B256,
73 pub timestamp: U256,
75 pub transactions_trie: B256,
77 pub uncle_hash: B256,
79 pub base_fee_per_gas: Option<U256>,
81 pub withdrawals_root: Option<B256>,
83 pub blob_gas_used: Option<U256>,
85 pub excess_blob_gas: Option<U256>,
87 pub parent_beacon_block_root: Option<B256>,
89 pub requests_hash: Option<B256>,
91 pub target_blobs_per_block: Option<U256>,
93}
94
95impl From<Header> for SealedHeader {
96 fn from(value: Header) -> Self {
97 let header = RethHeader {
98 base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
99 beneficiary: value.coinbase,
100 difficulty: value.difficulty,
101 extra_data: value.extra_data,
102 gas_limit: value.gas_limit.to::<u64>(),
103 gas_used: value.gas_used.to::<u64>(),
104 mix_hash: value.mix_hash,
105 nonce: u64::from_be_bytes(value.nonce.0).into(),
106 number: value.number.to::<u64>(),
107 timestamp: value.timestamp.to::<u64>(),
108 transactions_root: value.transactions_trie,
109 receipts_root: value.receipt_trie,
110 ommers_hash: value.uncle_hash,
111 state_root: value.state_root,
112 parent_hash: value.parent_hash,
113 logs_bloom: value.bloom,
114 withdrawals_root: value.withdrawals_root,
115 blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
116 excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
117 parent_beacon_block_root: value.parent_beacon_block_root,
118 requests_hash: value.requests_hash,
119 block_access_list_hash: None,
120 slot_number: None,
121 };
122 Self::new(header, value.hash)
123 }
124}
125
126#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
128#[serde(rename_all = "camelCase")]
129pub struct Block {
130 pub block_header: Option<Header>,
132 pub rlp: Bytes,
134 pub expect_exception: Option<String>,
138 pub transactions: Option<Vec<Transaction>>,
140 pub uncle_headers: Option<Vec<Header>>,
142 pub transaction_sequence: Option<Vec<TransactionSequence>>,
144 pub withdrawals: Option<Withdrawals>,
146}
147
148#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
150#[serde(deny_unknown_fields)]
151#[serde(rename_all = "camelCase")]
152pub struct TransactionSequence {
153 exception: String,
154 raw_bytes: Bytes,
155 valid: String,
156}
157
158#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
160pub struct State(BTreeMap<Address, Account>);
161
162impl State {
163 pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
165 self.0
166 .into_iter()
167 .map(|(address, account)| {
168 let storage = account
169 .storage
170 .iter()
171 .filter(|(_, v)| !v.is_zero())
172 .map(|(k, v)| {
173 (
174 B256::from_slice(&k.to_be_bytes::<32>()),
175 B256::from_slice(&v.to_be_bytes::<32>()),
176 )
177 })
178 .collect();
179 let account = GenesisAccount {
180 balance: account.balance,
181 nonce: Some(account.nonce.try_into().unwrap()),
182 code: Some(account.code).filter(|c| !c.is_empty()),
183 storage: Some(storage),
184 private_key: None,
185 };
186 (address, account)
187 })
188 .collect::<BTreeMap<_, _>>()
189 }
190}
191
192impl Deref for State {
193 type Target = BTreeMap<Address, Account>;
194
195 fn deref(&self) -> &Self::Target {
196 &self.0
197 }
198}
199
200#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
202#[serde(deny_unknown_fields)]
203pub struct Account {
204 pub balance: U256,
206 pub code: Bytes,
208 pub nonce: U256,
210 pub storage: BTreeMap<U256, U256>,
212}
213
214impl Account {
215 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
219 let account =
220 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
221 Error::Assertion(format!(
222 "Expected account ({address}) is missing from DB: {self:?}"
223 ))
224 })?;
225
226 assert_equal(self.balance, account.balance, "Balance does not match")?;
227 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
228
229 if let Some(bytecode_hash) = account.bytecode_hash {
230 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
231 } else {
232 assert_equal(
233 self.code.is_empty(),
234 true,
235 "Expected empty bytecode, got bytecode in db.",
236 )?;
237 }
238
239 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
240 for (slot, value) in &self.storage {
241 if let Some(entry) =
242 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
243 {
244 if U256::from_be_bytes(entry.key.0) == *slot {
245 assert_equal(
246 *value,
247 entry.value,
248 &format!("Storage for slot {slot:?} does not match"),
249 )?;
250 } else {
251 return Err(Error::Assertion(format!(
252 "Slot {slot:?} is missing from the database. Expected {value:?}"
253 )))
254 }
255 } else {
256 return Err(Error::Assertion(format!(
257 "Slot {slot:?} is missing from the database. Expected {value:?}"
258 )))
259 }
260 }
261
262 Ok(())
263 }
264}
265
266#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
268pub enum ForkSpec {
269 Frontier,
271 FrontierToHomesteadAt5,
273 Homestead,
275 HomesteadToDaoAt5,
277 HomesteadToEIP150At5,
279 EIP150,
281 EIP158, EIP158ToByzantiumAt5,
285 Byzantium,
287 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
291 Constantinople, ConstantinopleFix,
295 Istanbul,
297 Berlin,
299 BerlinToLondonAt5,
301 London,
303 #[serde(alias = "Paris")]
305 Merge,
306 ParisToShanghaiAtTime15k,
308 Shanghai,
310 ShanghaiToCancunAtTime15k,
312 #[serde(alias = "Merge+3540+3670")]
314 MergeEOF,
315 #[serde(alias = "Merge+3860")]
317 MergeMeterInitCode,
318 #[serde(alias = "Merge+3855")]
320 MergePush0,
321 Cancun,
323 CancunToPragueAtTime15k,
325 Prague,
327 Osaka,
329}
330
331impl ForkSpec {
332 pub fn to_chain_spec(self) -> Arc<ChainSpec> {
334 static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
335 let map = MAP.get_or_init(Default::default);
336 if let Some(r) = map.read().unwrap().get(&self) {
337 return r.clone();
338 }
339 map.write()
340 .unwrap()
341 .entry(self)
342 .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
343 .clone()
344 }
345
346 fn to_chain_spec_inner(self) -> ChainSpec {
347 let spec_builder = ChainSpecBuilder::mainnet().reset();
348
349 match self {
350 Self::Frontier => spec_builder.frontier_activated(),
351 Self::FrontierToHomesteadAt5 => spec_builder
352 .frontier_activated()
353 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
354 Self::Homestead => spec_builder.homestead_activated(),
355 Self::HomesteadToDaoAt5 => spec_builder
356 .homestead_activated()
357 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
358 Self::HomesteadToEIP150At5 => spec_builder
359 .homestead_activated()
360 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
361 Self::EIP150 => spec_builder.tangerine_whistle_activated(),
362 Self::EIP158 => spec_builder.spurious_dragon_activated(),
363 Self::EIP158ToByzantiumAt5 => spec_builder
364 .spurious_dragon_activated()
365 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
366 Self::Byzantium => spec_builder.byzantium_activated(),
367 Self::ByzantiumToConstantinopleAt5 => spec_builder
368 .byzantium_activated()
369 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
370 Self::ByzantiumToConstantinopleFixAt5 => spec_builder
371 .byzantium_activated()
372 .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
373 Self::Constantinople => spec_builder.constantinople_activated(),
374 Self::ConstantinopleFix => spec_builder.petersburg_activated(),
375 Self::Istanbul => spec_builder.istanbul_activated(),
376 Self::Berlin => spec_builder.berlin_activated(),
377 Self::BerlinToLondonAt5 => spec_builder
378 .berlin_activated()
379 .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
380 Self::London => spec_builder.london_activated(),
381 Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
382 spec_builder.paris_activated()
383 }
384 Self::ParisToShanghaiAtTime15k => spec_builder
385 .paris_activated()
386 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
387 Self::Shanghai => spec_builder.shanghai_activated(),
388 Self::ShanghaiToCancunAtTime15k => spec_builder
389 .shanghai_activated()
390 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
391 Self::Cancun => spec_builder.cancun_activated(),
392 Self::CancunToPragueAtTime15k => spec_builder
393 .cancun_activated()
394 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
395 Self::Prague => spec_builder.prague_activated(),
396 Self::Osaka => spec_builder.osaka_activated(),
397 }
398 .build()
399 }
400}
401
402#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
404pub enum SealEngine {
405 #[default]
407 NoProof,
408}
409
410#[derive(Debug, PartialEq, Eq, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct Transaction {
414 #[serde(rename = "type")]
416 pub transaction_type: Option<U256>,
417 pub data: Bytes,
419 pub gas_limit: U256,
421 pub gas_price: Option<U256>,
423 pub nonce: U256,
425 pub r: U256,
427 pub s: U256,
429 pub v: U256,
431 pub value: U256,
433 pub chain_id: Option<U256>,
435 pub access_list: Option<AccessList>,
437 pub max_fee_per_gas: Option<U256>,
439 pub max_priority_fee_per_gas: Option<U256>,
441 pub hash: Option<B256>,
443}
444
445#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
447#[serde(rename_all = "camelCase")]
448pub struct AccessListItem {
449 pub address: Address,
451 pub storage_keys: Vec<B256>,
453}
454
455pub type AccessList = Vec<AccessListItem>;
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn header_deserialize() {
464 let test = r#"{
465 "baseFeePerGas" : "0x0a",
466 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
467 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
468 "difficulty" : "0x020000",
469 "extraData" : "0x00",
470 "gasLimit" : "0x10000000000000",
471 "gasUsed" : "0x10000000000000",
472 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
473 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
474 "nonce" : "0x0000000000000000",
475 "number" : "0x01",
476 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
477 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
478 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
479 "timestamp" : "0x03e8",
480 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
481 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
482 }"#;
483 let res = serde_json::from_str::<Header>(test);
484 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
485 }
486
487 #[test]
488 fn transaction_deserialize() {
489 let test = r#"[
490 {
491 "accessList" : [
492 ],
493 "chainId" : "0x01",
494 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
495 "gasLimit" : "0x10000000000000",
496 "maxFeePerGas" : "0x07d0",
497 "maxPriorityFeePerGas" : "0x00",
498 "nonce" : "0x01",
499 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
500 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
501 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
502 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
503 "type" : "0x02",
504 "v" : "0x01",
505 "value" : "0x00"
506 }
507 ]"#;
508
509 let res = serde_json::from_str::<Vec<Transaction>>(test);
510 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
511 }
512}