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 };
120 Self::new(header, value.hash)
121 }
122}
123
124#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
126#[serde(rename_all = "camelCase")]
127pub struct Block {
128 pub block_header: Option<Header>,
130 pub rlp: Bytes,
132 pub expect_exception: Option<String>,
136 pub transactions: Option<Vec<Transaction>>,
138 pub uncle_headers: Option<Vec<Header>>,
140 pub transaction_sequence: Option<Vec<TransactionSequence>>,
142 pub withdrawals: Option<Withdrawals>,
144}
145
146#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
148#[serde(deny_unknown_fields)]
149#[serde(rename_all = "camelCase")]
150pub struct TransactionSequence {
151 exception: String,
152 raw_bytes: Bytes,
153 valid: String,
154}
155
156#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
158pub struct State(BTreeMap<Address, Account>);
159
160impl State {
161 pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
163 self.0
164 .into_iter()
165 .map(|(address, account)| {
166 let storage = account
167 .storage
168 .iter()
169 .filter(|(_, v)| !v.is_zero())
170 .map(|(k, v)| {
171 (
172 B256::from_slice(&k.to_be_bytes::<32>()),
173 B256::from_slice(&v.to_be_bytes::<32>()),
174 )
175 })
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 private_key: None,
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) =
240 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
241 {
242 if U256::from_be_bytes(entry.key.0) == *slot {
243 assert_equal(
244 *value,
245 entry.value,
246 &format!("Storage for slot {slot:?} does not match"),
247 )?;
248 } else {
249 return Err(Error::Assertion(format!(
250 "Slot {slot:?} is missing from the database. Expected {value:?}"
251 )))
252 }
253 } else {
254 return Err(Error::Assertion(format!(
255 "Slot {slot:?} is missing from the database. Expected {value:?}"
256 )))
257 }
258 }
259
260 Ok(())
261 }
262}
263
264#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
266pub enum ForkSpec {
267 Frontier,
269 FrontierToHomesteadAt5,
271 Homestead,
273 HomesteadToDaoAt5,
275 HomesteadToEIP150At5,
277 EIP150,
279 EIP158, EIP158ToByzantiumAt5,
283 Byzantium,
285 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
289 Constantinople, ConstantinopleFix,
293 Istanbul,
295 Berlin,
297 BerlinToLondonAt5,
299 London,
301 #[serde(alias = "Paris")]
303 Merge,
304 ParisToShanghaiAtTime15k,
306 Shanghai,
308 ShanghaiToCancunAtTime15k,
310 #[serde(alias = "Merge+3540+3670")]
312 MergeEOF,
313 #[serde(alias = "Merge+3860")]
315 MergeMeterInitCode,
316 #[serde(alias = "Merge+3855")]
318 MergePush0,
319 Cancun,
321 CancunToPragueAtTime15k,
323 Prague,
325 Osaka,
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 }
396 .build()
397 }
398}
399
400#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
402pub enum SealEngine {
403 #[default]
405 NoProof,
406}
407
408#[derive(Debug, PartialEq, Eq, Deserialize)]
410#[serde(rename_all = "camelCase")]
411pub struct Transaction {
412 #[serde(rename = "type")]
414 pub transaction_type: Option<U256>,
415 pub data: Bytes,
417 pub gas_limit: U256,
419 pub gas_price: Option<U256>,
421 pub nonce: U256,
423 pub r: U256,
425 pub s: U256,
427 pub v: U256,
429 pub value: U256,
431 pub chain_id: Option<U256>,
433 pub access_list: Option<AccessList>,
435 pub max_fee_per_gas: Option<U256>,
437 pub max_priority_fee_per_gas: Option<U256>,
439 pub hash: Option<B256>,
441}
442
443#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
445#[serde(rename_all = "camelCase")]
446pub struct AccessListItem {
447 pub address: Address,
449 pub storage_keys: Vec<B256>,
451}
452
453pub type AccessList = Vec<AccessListItem>;
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn header_deserialize() {
462 let test = r#"{
463 "baseFeePerGas" : "0x0a",
464 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
465 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
466 "difficulty" : "0x020000",
467 "extraData" : "0x00",
468 "gasLimit" : "0x10000000000000",
469 "gasUsed" : "0x10000000000000",
470 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
471 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
472 "nonce" : "0x0000000000000000",
473 "number" : "0x01",
474 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
475 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
476 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
477 "timestamp" : "0x03e8",
478 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
479 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
480 }"#;
481 let res = serde_json::from_str::<Header>(test);
482 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
483 }
484
485 #[test]
486 fn transaction_deserialize() {
487 let test = r#"[
488 {
489 "accessList" : [
490 ],
491 "chainId" : "0x01",
492 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
493 "gasLimit" : "0x10000000000000",
494 "maxFeePerGas" : "0x07d0",
495 "maxPriorityFeePerGas" : "0x00",
496 "nonce" : "0x01",
497 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
498 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
499 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
500 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
501 "type" : "0x02",
502 "v" : "0x01",
503 "value" : "0x00"
504 }
505 ]"#;
506
507 let res = serde_json::from_str::<Vec<Transaction>>(test);
508 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
509 }
510}