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