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};
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 Merge,
298 Shanghai,
300 #[serde(alias = "Merge+3540+3670")]
302 MergeEOF,
303 #[serde(alias = "Merge+3860")]
305 MergeMeterInitCode,
306 #[serde(alias = "Merge+3855")]
308 MergePush0,
309 Cancun,
311 Prague,
313}
314
315impl From<ForkSpec> for ChainSpec {
316 fn from(fork_spec: ForkSpec) -> Self {
317 let spec_builder = ChainSpecBuilder::mainnet();
318
319 match fork_spec {
320 ForkSpec::Frontier => spec_builder.frontier_activated(),
321 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => {
322 spec_builder.homestead_activated()
323 }
324 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
325 spec_builder.tangerine_whistle_activated()
326 }
327 ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
328 ForkSpec::Byzantium |
329 ForkSpec::EIP158ToByzantiumAt5 |
330 ForkSpec::ConstantinopleFix |
331 ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(),
332 ForkSpec::Istanbul => spec_builder.istanbul_activated(),
333 ForkSpec::Berlin => spec_builder.berlin_activated(),
334 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(),
335 ForkSpec::Merge |
336 ForkSpec::MergeEOF |
337 ForkSpec::MergeMeterInitCode |
338 ForkSpec::MergePush0 => spec_builder.paris_activated(),
339 ForkSpec::Shanghai => spec_builder.shanghai_activated(),
340 ForkSpec::Cancun => spec_builder.cancun_activated(),
341 ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
342 panic!("Overridden with PETERSBURG")
343 }
344 ForkSpec::Prague => spec_builder.prague_activated(),
345 }
346 .build()
347 }
348}
349
350#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
352pub enum SealEngine {
353 #[default]
355 NoProof,
356}
357
358#[derive(Debug, PartialEq, Eq, Deserialize)]
360#[serde(rename_all = "camelCase")]
361pub struct Transaction {
362 #[serde(rename = "type")]
364 pub transaction_type: Option<U256>,
365 pub data: Bytes,
367 pub gas_limit: U256,
369 pub gas_price: Option<U256>,
371 pub nonce: U256,
373 pub r: U256,
375 pub s: U256,
377 pub v: U256,
379 pub value: U256,
381 pub chain_id: Option<U256>,
383 pub access_list: Option<AccessList>,
385 pub max_fee_per_gas: Option<U256>,
387 pub max_priority_fee_per_gas: Option<U256>,
389 pub hash: Option<B256>,
391}
392
393#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
395#[serde(rename_all = "camelCase")]
396pub struct AccessListItem {
397 pub address: Address,
399 pub storage_keys: Vec<B256>,
401}
402
403pub type AccessList = Vec<AccessListItem>;
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn header_deserialize() {
412 let test = r#"{
413 "baseFeePerGas" : "0x0a",
414 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
415 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
416 "difficulty" : "0x020000",
417 "extraData" : "0x00",
418 "gasLimit" : "0x10000000000000",
419 "gasUsed" : "0x10000000000000",
420 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
421 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
422 "nonce" : "0x0000000000000000",
423 "number" : "0x01",
424 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
425 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
426 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
427 "timestamp" : "0x03e8",
428 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
429 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
430 }"#;
431 let res = serde_json::from_str::<Header>(test);
432 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
433 }
434
435 #[test]
436 fn transaction_deserialize() {
437 let test = r#"[
438 {
439 "accessList" : [
440 ],
441 "chainId" : "0x01",
442 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
443 "gasLimit" : "0x10000000000000",
444 "maxFeePerGas" : "0x07d0",
445 "maxPriorityFeePerGas" : "0x00",
446 "nonce" : "0x01",
447 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
448 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
449 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
450 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
451 "type" : "0x02",
452 "v" : "0x01",
453 "value" : "0x00"
454 }
455 ]"#;
456
457 let res = serde_json::from_str::<Vec<Transaction>>(test);
458 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
459 }
460}