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 Osaka,
322}
323
324impl From<ForkSpec> for ChainSpec {
325 fn from(fork_spec: ForkSpec) -> Self {
326 let spec_builder = ChainSpecBuilder::mainnet().reset();
327
328 match fork_spec {
329 ForkSpec::Frontier => spec_builder.frontier_activated(),
330 ForkSpec::FrontierToHomesteadAt5 => spec_builder
331 .frontier_activated()
332 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
333 ForkSpec::Homestead => spec_builder.homestead_activated(),
334 ForkSpec::HomesteadToDaoAt5 => spec_builder
335 .homestead_activated()
336 .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
337 ForkSpec::HomesteadToEIP150At5 => spec_builder
338 .homestead_activated()
339 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
340 ForkSpec::EIP150 => spec_builder.tangerine_whistle_activated(),
341 ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
342 ForkSpec::EIP158ToByzantiumAt5 => spec_builder
343 .spurious_dragon_activated()
344 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
345 ForkSpec::Byzantium => spec_builder.byzantium_activated(),
346 ForkSpec::ByzantiumToConstantinopleAt5 => spec_builder
347 .byzantium_activated()
348 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
349 ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder
350 .byzantium_activated()
351 .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
352 ForkSpec::Constantinople => spec_builder.constantinople_activated(),
353 ForkSpec::ConstantinopleFix => spec_builder.petersburg_activated(),
354 ForkSpec::Istanbul => spec_builder.istanbul_activated(),
355 ForkSpec::Berlin => spec_builder.berlin_activated(),
356 ForkSpec::BerlinToLondonAt5 => spec_builder
357 .berlin_activated()
358 .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
359 ForkSpec::London => spec_builder.london_activated(),
360 ForkSpec::Merge |
361 ForkSpec::MergeEOF |
362 ForkSpec::MergeMeterInitCode |
363 ForkSpec::MergePush0 => spec_builder.paris_activated(),
364 ForkSpec::ParisToShanghaiAtTime15k => spec_builder
365 .paris_activated()
366 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
367 ForkSpec::Shanghai => spec_builder.shanghai_activated(),
368 ForkSpec::ShanghaiToCancunAtTime15k => spec_builder
369 .shanghai_activated()
370 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
371 ForkSpec::Cancun => spec_builder.cancun_activated(),
372 ForkSpec::CancunToPragueAtTime15k => spec_builder
373 .cancun_activated()
374 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
375 ForkSpec::Prague => spec_builder.prague_activated(),
376 ForkSpec::Osaka => spec_builder.osaka_activated(),
377 }
378 .build()
379 }
380}
381
382#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
384pub enum SealEngine {
385 #[default]
387 NoProof,
388}
389
390#[derive(Debug, PartialEq, Eq, Deserialize)]
392#[serde(rename_all = "camelCase")]
393pub struct Transaction {
394 #[serde(rename = "type")]
396 pub transaction_type: Option<U256>,
397 pub data: Bytes,
399 pub gas_limit: U256,
401 pub gas_price: Option<U256>,
403 pub nonce: U256,
405 pub r: U256,
407 pub s: U256,
409 pub v: U256,
411 pub value: U256,
413 pub chain_id: Option<U256>,
415 pub access_list: Option<AccessList>,
417 pub max_fee_per_gas: Option<U256>,
419 pub max_priority_fee_per_gas: Option<U256>,
421 pub hash: Option<B256>,
423}
424
425#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
427#[serde(rename_all = "camelCase")]
428pub struct AccessListItem {
429 pub address: Address,
431 pub storage_keys: Vec<B256>,
433}
434
435pub type AccessList = Vec<AccessListItem>;
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn header_deserialize() {
444 let test = r#"{
445 "baseFeePerGas" : "0x0a",
446 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
447 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
448 "difficulty" : "0x020000",
449 "extraData" : "0x00",
450 "gasLimit" : "0x10000000000000",
451 "gasUsed" : "0x10000000000000",
452 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
453 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
454 "nonce" : "0x0000000000000000",
455 "number" : "0x01",
456 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
457 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
458 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
459 "timestamp" : "0x03e8",
460 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
461 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
462 }"#;
463 let res = serde_json::from_str::<Header>(test);
464 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
465 }
466
467 #[test]
468 fn transaction_deserialize() {
469 let test = r#"[
470 {
471 "accessList" : [
472 ],
473 "chainId" : "0x01",
474 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
475 "gasLimit" : "0x10000000000000",
476 "maxFeePerGas" : "0x07d0",
477 "maxPriorityFeePerGas" : "0x00",
478 "nonce" : "0x01",
479 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
480 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
481 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
482 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
483 "type" : "0x02",
484 "v" : "0x01",
485 "value" : "0x00"
486 }
487 ]"#;
488
489 let res = serde_json::from_str::<Vec<Transaction>>(test);
490 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
491 }
492}