1use crate::{assert::assert_equal, Error};
4use alloy_consensus::Header as RethHeader;
5use alloy_eips::eip4895::Withdrawals;
6use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256};
7use reth_chainspec::{ChainSpec, ChainSpecBuilder};
8use reth_db_api::{
9 cursor::DbDupCursorRO,
10 tables,
11 transaction::{DbTx, DbTxMut},
12};
13use reth_primitives::{Account as RethAccount, Bytecode, SealedHeader, StorageEntry};
14use serde::Deserialize;
15use std::{collections::BTreeMap, ops::Deref};
16
17#[derive(Debug, PartialEq, Eq, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct BlockchainTest {
21 pub genesis_block_header: Header,
23 #[serde(rename = "genesisRLP")]
25 pub genesis_rlp: Option<Bytes>,
26 pub blocks: Vec<Block>,
28 pub post_state: Option<BTreeMap<Address, Account>>,
30 pub post_state_hash: Option<B256>,
32 pub pre: State,
34 pub lastblockhash: B256,
36 pub network: ForkSpec,
38 #[serde(default)]
39 pub seal_engine: SealEngine,
41}
42
43#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
45#[serde(rename_all = "camelCase")]
46pub struct Header {
47 pub bloom: Bloom,
49 pub coinbase: Address,
51 pub difficulty: U256,
53 pub extra_data: Bytes,
55 pub gas_limit: U256,
57 pub gas_used: U256,
59 pub hash: B256,
61 pub mix_hash: B256,
63 pub nonce: B64,
65 pub number: U256,
67 pub parent_hash: B256,
69 pub receipt_trie: B256,
71 pub state_root: B256,
73 pub timestamp: U256,
75 pub transactions_trie: B256,
77 pub uncle_hash: B256,
79 pub base_fee_per_gas: Option<U256>,
81 pub withdrawals_root: Option<B256>,
83 pub blob_gas_used: Option<U256>,
85 pub excess_blob_gas: Option<U256>,
87 pub parent_beacon_block_root: Option<B256>,
89 pub requests_hash: Option<B256>,
91 pub target_blobs_per_block: Option<U256>,
93}
94
95impl From<Header> for SealedHeader {
96 fn from(value: Header) -> Self {
97 let header = RethHeader {
98 base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
99 beneficiary: value.coinbase,
100 difficulty: value.difficulty,
101 extra_data: value.extra_data,
102 gas_limit: value.gas_limit.to::<u64>(),
103 gas_used: value.gas_used.to::<u64>(),
104 mix_hash: value.mix_hash,
105 nonce: u64::from_be_bytes(value.nonce.0).into(),
106 number: value.number.to::<u64>(),
107 timestamp: value.timestamp.to::<u64>(),
108 transactions_root: value.transactions_trie,
109 receipts_root: value.receipt_trie,
110 ommers_hash: value.uncle_hash,
111 state_root: value.state_root,
112 parent_hash: value.parent_hash,
113 logs_bloom: value.bloom,
114 withdrawals_root: value.withdrawals_root,
115 blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
116 excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
117 parent_beacon_block_root: value.parent_beacon_block_root,
118 requests_hash: value.requests_hash,
119 };
120 Self::new(header, value.hash)
121 }
122}
123
124#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
126#[serde(rename_all = "camelCase")]
127pub struct Block {
128 pub block_header: Option<Header>,
130 pub rlp: Bytes,
132 pub transactions: Option<Vec<Transaction>>,
134 pub uncle_headers: Option<Vec<Header>>,
136 pub transaction_sequence: Option<Vec<TransactionSequence>>,
138 pub withdrawals: Option<Withdrawals>,
140}
141
142#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
144#[serde(deny_unknown_fields)]
145#[serde(rename_all = "camelCase")]
146pub struct TransactionSequence {
147 exception: String,
148 raw_bytes: Bytes,
149 valid: String,
150}
151
152#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
154pub struct State(BTreeMap<Address, Account>);
155
156impl State {
157 pub fn write_to_db(&self, tx: &impl DbTxMut) -> Result<(), Error> {
159 for (&address, account) in &self.0 {
160 let hashed_address = keccak256(address);
161 let has_code = !account.code.is_empty();
162 let code_hash = has_code.then(|| keccak256(&account.code));
163 let reth_account = RethAccount {
164 balance: account.balance,
165 nonce: account.nonce.to::<u64>(),
166 bytecode_hash: code_hash,
167 };
168 tx.put::<tables::PlainAccountState>(address, reth_account)?;
169 tx.put::<tables::HashedAccounts>(hashed_address, reth_account)?;
170
171 if let Some(code_hash) = code_hash {
172 tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.clone()))?;
173 }
174
175 for (k, v) in &account.storage {
176 if v.is_zero() {
177 continue
178 }
179 let storage_key = B256::from_slice(&k.to_be_bytes::<32>());
180 tx.put::<tables::PlainStorageState>(
181 address,
182 StorageEntry { key: storage_key, value: *v },
183 )?;
184 tx.put::<tables::HashedStorages>(
185 hashed_address,
186 StorageEntry { key: keccak256(storage_key), value: *v },
187 )?;
188 }
189 }
190 Ok(())
191 }
192}
193
194impl Deref for State {
195 type Target = BTreeMap<Address, Account>;
196
197 fn deref(&self) -> &Self::Target {
198 &self.0
199 }
200}
201
202#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
204#[serde(deny_unknown_fields)]
205pub struct Account {
206 pub balance: U256,
208 pub code: Bytes,
210 pub nonce: U256,
212 pub storage: BTreeMap<U256, U256>,
214}
215
216impl Account {
217 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
221 let account =
222 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
223 Error::Assertion(format!(
224 "Expected account ({address}) is missing from DB: {self:?}"
225 ))
226 })?;
227
228 assert_equal(self.balance, account.balance, "Balance does not match")?;
229 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
230
231 if let Some(bytecode_hash) = account.bytecode_hash {
232 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
233 } else {
234 assert_equal(
235 self.code.is_empty(),
236 true,
237 "Expected empty bytecode, got bytecode in db.",
238 )?;
239 }
240
241 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
242 for (slot, value) in &self.storage {
243 if let Some(entry) =
244 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
245 {
246 if U256::from_be_bytes(entry.key.0) == *slot {
247 assert_equal(
248 *value,
249 entry.value,
250 &format!("Storage for slot {slot:?} does not match"),
251 )?;
252 } else {
253 return Err(Error::Assertion(format!(
254 "Slot {slot:?} is missing from the database. Expected {value:?}"
255 )))
256 }
257 } else {
258 return Err(Error::Assertion(format!(
259 "Slot {slot:?} is missing from the database. Expected {value:?}"
260 )))
261 }
262 }
263
264 Ok(())
265 }
266}
267
268#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
270pub enum ForkSpec {
271 Frontier,
273 FrontierToHomesteadAt5,
275 Homestead,
277 HomesteadToDaoAt5,
279 HomesteadToEIP150At5,
281 EIP150,
283 EIP158, EIP158ToByzantiumAt5,
287 Byzantium,
289 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
293 Constantinople, ConstantinopleFix,
297 Istanbul,
299 Berlin,
301 BerlinToLondonAt5,
303 London,
305 Merge,
307 Shanghai,
309 #[serde(alias = "Merge+3540+3670")]
311 MergeEOF,
312 #[serde(alias = "Merge+3860")]
314 MergeMeterInitCode,
315 #[serde(alias = "Merge+3855")]
317 MergePush0,
318 Cancun,
320 #[serde(other)]
322 Unknown,
323}
324
325impl From<ForkSpec> for ChainSpec {
326 fn from(fork_spec: ForkSpec) -> Self {
327 let spec_builder = ChainSpecBuilder::mainnet();
328
329 match fork_spec {
330 ForkSpec::Frontier => spec_builder.frontier_activated(),
331 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => {
332 spec_builder.homestead_activated()
333 }
334 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
335 spec_builder.tangerine_whistle_activated()
336 }
337 ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
338 ForkSpec::Byzantium |
339 ForkSpec::EIP158ToByzantiumAt5 |
340 ForkSpec::ConstantinopleFix |
341 ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(),
342 ForkSpec::Istanbul => spec_builder.istanbul_activated(),
343 ForkSpec::Berlin => spec_builder.berlin_activated(),
344 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(),
345 ForkSpec::Merge |
346 ForkSpec::MergeEOF |
347 ForkSpec::MergeMeterInitCode |
348 ForkSpec::MergePush0 => spec_builder.paris_activated(),
349 ForkSpec::Shanghai => spec_builder.shanghai_activated(),
350 ForkSpec::Cancun => spec_builder.cancun_activated(),
351 ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
352 panic!("Overridden with PETERSBURG")
353 }
354 ForkSpec::Unknown => {
355 panic!("Unknown fork");
356 }
357 }
358 .build()
359 }
360}
361
362#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
364pub enum SealEngine {
365 #[default]
367 NoProof,
368}
369
370#[derive(Debug, PartialEq, Eq, Deserialize)]
372#[serde(rename_all = "camelCase")]
373pub struct Transaction {
374 #[serde(rename = "type")]
376 pub transaction_type: Option<U256>,
377 pub data: Bytes,
379 pub gas_limit: U256,
381 pub gas_price: Option<U256>,
383 pub nonce: U256,
385 pub r: U256,
387 pub s: U256,
389 pub v: U256,
391 pub value: U256,
393 pub chain_id: Option<U256>,
395 pub access_list: Option<AccessList>,
397 pub max_fee_per_gas: Option<U256>,
399 pub max_priority_fee_per_gas: Option<U256>,
401 pub hash: Option<B256>,
403}
404
405#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
407#[serde(rename_all = "camelCase")]
408pub struct AccessListItem {
409 pub address: Address,
411 pub storage_keys: Vec<B256>,
413}
414
415pub type AccessList = Vec<AccessListItem>;
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn header_deserialize() {
424 let test = r#"{
425 "baseFeePerGas" : "0x0a",
426 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
427 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
428 "difficulty" : "0x020000",
429 "extraData" : "0x00",
430 "gasLimit" : "0x10000000000000",
431 "gasUsed" : "0x10000000000000",
432 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
433 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
434 "nonce" : "0x0000000000000000",
435 "number" : "0x01",
436 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
437 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
438 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
439 "timestamp" : "0x03e8",
440 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
441 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
442 }"#;
443 let res = serde_json::from_str::<Header>(test);
444 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
445 }
446
447 #[test]
448 fn transaction_deserialize() {
449 let test = r#"[
450 {
451 "accessList" : [
452 ],
453 "chainId" : "0x01",
454 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
455 "gasLimit" : "0x10000000000000",
456 "maxFeePerGas" : "0x07d0",
457 "maxPriorityFeePerGas" : "0x00",
458 "nonce" : "0x01",
459 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
460 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
461 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
462 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
463 "type" : "0x02",
464 "v" : "0x01",
465 "value" : "0x00"
466 }
467 ]"#;
468
469 let res = serde_json::from_str::<Vec<Transaction>>(test);
470 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
471 }
472}