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