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, map::HashMap, 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 serde::Deserialize;
12use std::{
13 collections::BTreeMap,
14 ops::Deref,
15 sync::{Arc, OnceLock, RwLock},
16};
17
18#[derive(Debug, PartialEq, Eq, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct BlockchainTest {
22 pub genesis_block_header: Header,
24 #[serde(rename = "genesisRLP")]
26 pub genesis_rlp: Option<Bytes>,
27 pub blocks: Vec<Block>,
29 pub post_state: Option<BTreeMap<Address, Account>>,
31 pub pre: State,
33 pub lastblockhash: B256,
35 pub network: ForkSpec,
37 #[serde(default)]
38 pub seal_engine: SealEngine,
40}
41
42#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
44#[serde(rename_all = "camelCase")]
45pub struct Header {
46 pub bloom: Bloom,
48 pub coinbase: Address,
50 pub difficulty: U256,
52 pub extra_data: Bytes,
54 pub gas_limit: U256,
56 pub gas_used: U256,
58 pub hash: B256,
60 pub mix_hash: B256,
62 pub nonce: B64,
64 pub number: U256,
66 pub parent_hash: B256,
68 pub receipt_trie: B256,
70 pub state_root: B256,
72 pub timestamp: U256,
74 pub transactions_trie: B256,
76 pub uncle_hash: B256,
78 pub base_fee_per_gas: Option<U256>,
80 pub withdrawals_root: Option<B256>,
82 pub blob_gas_used: Option<U256>,
84 pub excess_blob_gas: Option<U256>,
86 pub parent_beacon_block_root: Option<B256>,
88 pub requests_hash: Option<B256>,
90 pub target_blobs_per_block: Option<U256>,
92 pub block_access_list_hash: Option<B256>,
94 pub slot_number: Option<U256>,
96}
97
98impl From<Header> for SealedHeader {
99 fn from(value: Header) -> Self {
100 let header = RethHeader {
101 base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
102 beneficiary: value.coinbase,
103 difficulty: value.difficulty,
104 extra_data: value.extra_data,
105 gas_limit: value.gas_limit.to::<u64>(),
106 gas_used: value.gas_used.to::<u64>(),
107 mix_hash: value.mix_hash,
108 nonce: u64::from_be_bytes(value.nonce.0).into(),
109 number: value.number.to::<u64>(),
110 timestamp: value.timestamp.to::<u64>(),
111 transactions_root: value.transactions_trie,
112 receipts_root: value.receipt_trie,
113 ommers_hash: value.uncle_hash,
114 state_root: value.state_root,
115 parent_hash: value.parent_hash,
116 logs_bloom: value.bloom,
117 withdrawals_root: value.withdrawals_root,
118 blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
119 excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
120 parent_beacon_block_root: value.parent_beacon_block_root,
121 requests_hash: value.requests_hash,
122 block_access_list_hash: value.block_access_list_hash,
123 slot_number: value.slot_number.map(|v| v.to::<u64>()),
124 };
125 Self::new(header, value.hash)
126 }
127}
128
129#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
131#[serde(rename_all = "camelCase")]
132pub struct Block {
133 pub block_header: Option<Header>,
135 pub rlp: Bytes,
137 pub expect_exception: Option<String>,
141 pub transactions: Option<Vec<Transaction>>,
143 pub uncle_headers: Option<Vec<Header>>,
145 pub transaction_sequence: Option<Vec<TransactionSequence>>,
147 pub withdrawals: Option<Withdrawals>,
149}
150
151#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
153#[serde(deny_unknown_fields)]
154#[serde(rename_all = "camelCase")]
155pub struct TransactionSequence {
156 exception: String,
157 raw_bytes: Bytes,
158 valid: String,
159}
160
161#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
163pub struct State(BTreeMap<Address, Account>);
164
165impl State {
166 pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
168 self.0
169 .into_iter()
170 .map(|(address, account)| {
171 let storage = account
172 .storage
173 .iter()
174 .filter(|(_, v)| !v.is_zero())
175 .map(|(k, v)| (B256::from(*k), B256::from(*v)))
176 .collect();
177 let account = GenesisAccount {
178 balance: account.balance,
179 nonce: Some(account.nonce.try_into().unwrap()),
180 code: Some(account.code).filter(|c| !c.is_empty()),
181 storage: Some(storage),
182 ..Default::default()
183 };
184 (address, account)
185 })
186 .collect::<BTreeMap<_, _>>()
187 }
188}
189
190impl Deref for State {
191 type Target = BTreeMap<Address, Account>;
192
193 fn deref(&self) -> &Self::Target {
194 &self.0
195 }
196}
197
198#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
200#[serde(deny_unknown_fields)]
201pub struct Account {
202 pub balance: U256,
204 pub code: Bytes,
206 pub nonce: U256,
208 pub storage: BTreeMap<U256, U256>,
210}
211
212impl Account {
213 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
217 let account =
218 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
219 Error::Assertion(format!(
220 "Expected account ({address}) is missing from DB: {self:?}"
221 ))
222 })?;
223
224 assert_equal(self.balance, account.balance, "Balance does not match")?;
225 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
226
227 if let Some(bytecode_hash) = account.bytecode_hash {
228 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
229 } else {
230 assert_equal(
231 self.code.is_empty(),
232 true,
233 "Expected empty bytecode, got bytecode in db.",
234 )?;
235 }
236
237 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
238 for (slot, value) in &self.storage {
239 if let Some(entry) = storage_cursor.seek_by_key_subkey(address, B256::from(*slot))? {
240 if U256::from_be_bytes(entry.key.0) == *slot {
241 assert_equal(
242 *value,
243 entry.value,
244 &format!("Storage for slot {slot:?} does not match"),
245 )?;
246 } else {
247 return Err(Error::Assertion(format!(
248 "Slot {slot:?} is missing from the database. Expected {value:?}"
249 )))
250 }
251 } else {
252 return Err(Error::Assertion(format!(
253 "Slot {slot:?} is missing from the database. Expected {value:?}"
254 )))
255 }
256 }
257
258 Ok(())
259 }
260}
261
262#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
264pub enum ForkSpec {
265 Frontier,
267 FrontierToHomesteadAt5,
269 Homestead,
271 HomesteadToDaoAt5,
273 HomesteadToEIP150At5,
275 EIP150,
277 EIP158, EIP158ToByzantiumAt5,
281 Byzantium,
283 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
287 Constantinople, ConstantinopleFix,
291 Istanbul,
293 Berlin,
295 BerlinToLondonAt5,
297 London,
299 #[serde(alias = "Paris")]
301 Merge,
302 ParisToShanghaiAtTime15k,
304 Shanghai,
306 ShanghaiToCancunAtTime15k,
308 #[serde(alias = "Merge+3540+3670")]
310 MergeEOF,
311 #[serde(alias = "Merge+3860")]
313 MergeMeterInitCode,
314 #[serde(alias = "Merge+3855")]
316 MergePush0,
317 Cancun,
319 CancunToPragueAtTime15k,
321 Prague,
323 Osaka,
325 Amsterdam,
327}
328
329impl ForkSpec {
330 pub fn to_chain_spec(self) -> Arc<ChainSpec> {
332 static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
333 let map = MAP.get_or_init(Default::default);
334 if let Some(r) = map.read().unwrap().get(&self) {
335 return r.clone();
336 }
337 map.write()
338 .unwrap()
339 .entry(self)
340 .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
341 .clone()
342 }
343
344 fn to_chain_spec_inner(self) -> ChainSpec {
345 let spec_builder = ChainSpecBuilder::mainnet().reset();
346
347 match self {
348 Self::Frontier => spec_builder.frontier_activated(),
349 Self::FrontierToHomesteadAt5 => spec_builder
350 .frontier_activated()
351 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
352 Self::Homestead => spec_builder.homestead_activated(),
353 Self::HomesteadToDaoAt5 => spec_builder
354 .homestead_activated()
355 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
356 Self::HomesteadToEIP150At5 => spec_builder
357 .homestead_activated()
358 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
359 Self::EIP150 => spec_builder.tangerine_whistle_activated(),
360 Self::EIP158 => spec_builder.spurious_dragon_activated(),
361 Self::EIP158ToByzantiumAt5 => spec_builder
362 .spurious_dragon_activated()
363 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
364 Self::Byzantium => spec_builder.byzantium_activated(),
365 Self::ByzantiumToConstantinopleAt5 => spec_builder
366 .byzantium_activated()
367 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
368 Self::ByzantiumToConstantinopleFixAt5 => spec_builder
369 .byzantium_activated()
370 .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
371 Self::Constantinople => spec_builder.constantinople_activated(),
372 Self::ConstantinopleFix => spec_builder.petersburg_activated(),
373 Self::Istanbul => spec_builder.istanbul_activated(),
374 Self::Berlin => spec_builder.berlin_activated(),
375 Self::BerlinToLondonAt5 => spec_builder
376 .berlin_activated()
377 .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
378 Self::London => spec_builder.london_activated(),
379 Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
380 spec_builder.paris_activated()
381 }
382 Self::ParisToShanghaiAtTime15k => spec_builder
383 .paris_activated()
384 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
385 Self::Shanghai => spec_builder.shanghai_activated(),
386 Self::ShanghaiToCancunAtTime15k => spec_builder
387 .shanghai_activated()
388 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
389 Self::Cancun => spec_builder.cancun_activated(),
390 Self::CancunToPragueAtTime15k => spec_builder
391 .cancun_activated()
392 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
393 Self::Prague => spec_builder.prague_activated(),
394 Self::Osaka => spec_builder.osaka_activated(),
395 Self::Amsterdam => spec_builder.amsterdam_activated(),
396 }
397 .build()
398 }
399}
400
401#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
403pub enum SealEngine {
404 #[default]
406 NoProof,
407}
408
409#[derive(Debug, PartialEq, Eq, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct Transaction {
413 #[serde(rename = "type")]
415 pub transaction_type: Option<U256>,
416 pub data: Bytes,
418 pub gas_limit: U256,
420 pub gas_price: Option<U256>,
422 pub nonce: U256,
424 pub r: U256,
426 pub s: U256,
428 pub v: U256,
430 pub value: U256,
432 pub chain_id: Option<U256>,
434 pub access_list: Option<AccessList>,
436 pub max_fee_per_gas: Option<U256>,
438 pub max_priority_fee_per_gas: Option<U256>,
440 pub hash: Option<B256>,
442}
443
444#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
446#[serde(rename_all = "camelCase")]
447pub struct AccessListItem {
448 pub address: Address,
450 pub storage_keys: Vec<B256>,
452}
453
454pub type AccessList = Vec<AccessListItem>;
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn header_deserialize() {
463 let test = r#"{
464 "baseFeePerGas" : "0x0a",
465 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
466 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
467 "difficulty" : "0x020000",
468 "extraData" : "0x00",
469 "gasLimit" : "0x10000000000000",
470 "gasUsed" : "0x10000000000000",
471 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
472 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
473 "nonce" : "0x0000000000000000",
474 "number" : "0x01",
475 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
476 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
477 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
478 "timestamp" : "0x03e8",
479 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
480 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
481 }"#;
482 let res = serde_json::from_str::<Header>(test);
483 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
484 }
485
486 #[test]
487 fn transaction_deserialize() {
488 let test = r#"[
489 {
490 "accessList" : [
491 ],
492 "chainId" : "0x01",
493 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
494 "gasLimit" : "0x10000000000000",
495 "maxFeePerGas" : "0x07d0",
496 "maxPriorityFeePerGas" : "0x00",
497 "nonce" : "0x01",
498 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
499 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
500 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
501 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
502 "type" : "0x02",
503 "v" : "0x01",
504 "value" : "0x00"
505 }
506 ]"#;
507
508 let res = serde_json::from_str::<Vec<Transaction>>(test);
509 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
510 }
511}