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}
93
94impl From<Header> for SealedHeader {
95 fn from(value: Header) -> Self {
96 let header = RethHeader {
97 base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
98 beneficiary: value.coinbase,
99 difficulty: value.difficulty,
100 extra_data: value.extra_data,
101 gas_limit: value.gas_limit.to::<u64>(),
102 gas_used: value.gas_used.to::<u64>(),
103 mix_hash: value.mix_hash,
104 nonce: u64::from_be_bytes(value.nonce.0).into(),
105 number: value.number.to::<u64>(),
106 timestamp: value.timestamp.to::<u64>(),
107 transactions_root: value.transactions_trie,
108 receipts_root: value.receipt_trie,
109 ommers_hash: value.uncle_hash,
110 state_root: value.state_root,
111 parent_hash: value.parent_hash,
112 logs_bloom: value.bloom,
113 withdrawals_root: value.withdrawals_root,
114 blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
115 excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
116 parent_beacon_block_root: value.parent_beacon_block_root,
117 requests_hash: value.requests_hash,
118 block_access_list_hash: None,
119 slot_number: None,
120 };
121 Self::new(header, value.hash)
122 }
123}
124
125#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct Block {
129 pub block_header: Option<Header>,
131 pub rlp: Bytes,
133 pub expect_exception: Option<String>,
137 pub transactions: Option<Vec<Transaction>>,
139 pub uncle_headers: Option<Vec<Header>>,
141 pub transaction_sequence: Option<Vec<TransactionSequence>>,
143 pub withdrawals: Option<Withdrawals>,
145}
146
147#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
149#[serde(deny_unknown_fields)]
150#[serde(rename_all = "camelCase")]
151pub struct TransactionSequence {
152 exception: String,
153 raw_bytes: Bytes,
154 valid: String,
155}
156
157#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
159pub struct State(BTreeMap<Address, Account>);
160
161impl State {
162 pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
164 self.0
165 .into_iter()
166 .map(|(address, account)| {
167 let storage = account
168 .storage
169 .iter()
170 .filter(|(_, v)| !v.is_zero())
171 .map(|(k, v)| {
172 (
173 B256::from_slice(&k.to_be_bytes::<32>()),
174 B256::from_slice(&v.to_be_bytes::<32>()),
175 )
176 })
177 .collect();
178 let account = GenesisAccount {
179 balance: account.balance,
180 nonce: Some(account.nonce.try_into().unwrap()),
181 code: Some(account.code).filter(|c| !c.is_empty()),
182 storage: Some(storage),
183 private_key: None,
184 };
185 (address, account)
186 })
187 .collect::<BTreeMap<_, _>>()
188 }
189}
190
191impl Deref for State {
192 type Target = BTreeMap<Address, Account>;
193
194 fn deref(&self) -> &Self::Target {
195 &self.0
196 }
197}
198
199#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
201#[serde(deny_unknown_fields)]
202pub struct Account {
203 pub balance: U256,
205 pub code: Bytes,
207 pub nonce: U256,
209 pub storage: BTreeMap<U256, U256>,
211}
212
213impl Account {
214 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
218 let account =
219 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
220 Error::Assertion(format!(
221 "Expected account ({address}) is missing from DB: {self:?}"
222 ))
223 })?;
224
225 assert_equal(self.balance, account.balance, "Balance does not match")?;
226 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
227
228 if let Some(bytecode_hash) = account.bytecode_hash {
229 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
230 } else {
231 assert_equal(
232 self.code.is_empty(),
233 true,
234 "Expected empty bytecode, got bytecode in db.",
235 )?;
236 }
237
238 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
239 for (slot, value) in &self.storage {
240 if let Some(entry) =
241 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
242 {
243 if U256::from_be_bytes(entry.key.0) == *slot {
244 assert_equal(
245 *value,
246 entry.value,
247 &format!("Storage for slot {slot:?} does not match"),
248 )?;
249 } else {
250 return Err(Error::Assertion(format!(
251 "Slot {slot:?} is missing from the database. Expected {value:?}"
252 )))
253 }
254 } else {
255 return Err(Error::Assertion(format!(
256 "Slot {slot:?} is missing from the database. Expected {value:?}"
257 )))
258 }
259 }
260
261 Ok(())
262 }
263}
264
265#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
267pub enum ForkSpec {
268 Frontier,
270 FrontierToHomesteadAt5,
272 Homestead,
274 HomesteadToDaoAt5,
276 HomesteadToEIP150At5,
278 EIP150,
280 EIP158, EIP158ToByzantiumAt5,
284 Byzantium,
286 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
290 Constantinople, ConstantinopleFix,
294 Istanbul,
296 Berlin,
298 BerlinToLondonAt5,
300 London,
302 #[serde(alias = "Paris")]
304 Merge,
305 ParisToShanghaiAtTime15k,
307 Shanghai,
309 ShanghaiToCancunAtTime15k,
311 #[serde(alias = "Merge+3540+3670")]
313 MergeEOF,
314 #[serde(alias = "Merge+3860")]
316 MergeMeterInitCode,
317 #[serde(alias = "Merge+3855")]
319 MergePush0,
320 Cancun,
322 CancunToPragueAtTime15k,
324 Prague,
326 Osaka,
328}
329
330impl ForkSpec {
331 pub fn to_chain_spec(self) -> Arc<ChainSpec> {
333 static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
334 let map = MAP.get_or_init(Default::default);
335 if let Some(r) = map.read().unwrap().get(&self) {
336 return r.clone();
337 }
338 map.write()
339 .unwrap()
340 .entry(self)
341 .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
342 .clone()
343 }
344
345 fn to_chain_spec_inner(self) -> ChainSpec {
346 let spec_builder = ChainSpecBuilder::mainnet().reset();
347
348 match self {
349 Self::Frontier => spec_builder.frontier_activated(),
350 Self::FrontierToHomesteadAt5 => spec_builder
351 .frontier_activated()
352 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
353 Self::Homestead => spec_builder.homestead_activated(),
354 Self::HomesteadToDaoAt5 => spec_builder
355 .homestead_activated()
356 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
357 Self::HomesteadToEIP150At5 => spec_builder
358 .homestead_activated()
359 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
360 Self::EIP150 => spec_builder.tangerine_whistle_activated(),
361 Self::EIP158 => spec_builder.spurious_dragon_activated(),
362 Self::EIP158ToByzantiumAt5 => spec_builder
363 .spurious_dragon_activated()
364 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
365 Self::Byzantium => spec_builder.byzantium_activated(),
366 Self::ByzantiumToConstantinopleAt5 => spec_builder
367 .byzantium_activated()
368 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
369 Self::ByzantiumToConstantinopleFixAt5 => spec_builder
370 .byzantium_activated()
371 .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
372 Self::Constantinople => spec_builder.constantinople_activated(),
373 Self::ConstantinopleFix => spec_builder.petersburg_activated(),
374 Self::Istanbul => spec_builder.istanbul_activated(),
375 Self::Berlin => spec_builder.berlin_activated(),
376 Self::BerlinToLondonAt5 => spec_builder
377 .berlin_activated()
378 .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
379 Self::London => spec_builder.london_activated(),
380 Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
381 spec_builder.paris_activated()
382 }
383 Self::ParisToShanghaiAtTime15k => spec_builder
384 .paris_activated()
385 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
386 Self::Shanghai => spec_builder.shanghai_activated(),
387 Self::ShanghaiToCancunAtTime15k => spec_builder
388 .shanghai_activated()
389 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
390 Self::Cancun => spec_builder.cancun_activated(),
391 Self::CancunToPragueAtTime15k => spec_builder
392 .cancun_activated()
393 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
394 Self::Prague => spec_builder.prague_activated(),
395 Self::Osaka => spec_builder.osaka_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}