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::tables;
9use reth_db_api::{
10 cursor::DbDupCursorRO,
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 target_blobs_per_block: value.target_blobs_per_block.map(|v| v.to::<u64>()),
120 };
121 Self::new(header, value.hash)
122 }
123}
124
125#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct Block {
129 pub block_header: Option<Header>,
131 pub rlp: Bytes,
133 pub transactions: Option<Vec<Transaction>>,
135 pub uncle_headers: Option<Vec<Header>>,
137 pub transaction_sequence: Option<Vec<TransactionSequence>>,
139 pub withdrawals: Option<Withdrawals>,
141}
142
143#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
145#[serde(deny_unknown_fields)]
146#[serde(rename_all = "camelCase")]
147pub struct TransactionSequence {
148 exception: String,
149 raw_bytes: Bytes,
150 valid: String,
151}
152
153#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
155pub struct State(BTreeMap<Address, Account>);
156
157impl State {
158 pub fn write_to_db(&self, tx: &impl DbTxMut) -> Result<(), Error> {
160 for (&address, account) in &self.0 {
161 let hashed_address = keccak256(address);
162 let has_code = !account.code.is_empty();
163 let code_hash = has_code.then(|| keccak256(&account.code));
164 let reth_account = RethAccount {
165 balance: account.balance,
166 nonce: account.nonce.to::<u64>(),
167 bytecode_hash: code_hash,
168 };
169 tx.put::<tables::PlainAccountState>(address, reth_account)?;
170 tx.put::<tables::HashedAccounts>(hashed_address, reth_account)?;
171
172 if let Some(code_hash) = code_hash {
173 tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.clone()))?;
174 }
175
176 for (k, v) in &account.storage {
177 if v.is_zero() {
178 continue
179 }
180 let storage_key = B256::from_slice(&k.to_be_bytes::<32>());
181 tx.put::<tables::PlainStorageState>(
182 address,
183 StorageEntry { key: storage_key, value: *v, ..Default::default() },
184 )?;
185 tx.put::<tables::HashedStorages>(
186 hashed_address,
187 StorageEntry { key: keccak256(storage_key), value: *v, ..Default::default() },
188 )?;
189 }
190 }
191 Ok(())
192 }
193}
194
195impl Deref for State {
196 type Target = BTreeMap<Address, Account>;
197
198 fn deref(&self) -> &Self::Target {
199 &self.0
200 }
201}
202
203#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
205#[serde(deny_unknown_fields)]
206pub struct Account {
207 pub balance: U256,
209 pub code: Bytes,
211 pub nonce: U256,
213 pub storage: BTreeMap<U256, U256>,
215}
216
217impl Account {
218 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
222 let account =
223 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
224 Error::Assertion(format!(
225 "Expected account ({address}) is missing from DB: {self:?}"
226 ))
227 })?;
228
229 assert_equal(self.balance, account.balance, "Balance does not match")?;
230 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
231
232 if let Some(bytecode_hash) = account.bytecode_hash {
233 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
234 } else {
235 assert_equal(
236 self.code.is_empty(),
237 true,
238 "Expected empty bytecode, got bytecode in db.",
239 )?;
240 }
241
242 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
243 for (slot, value) in &self.storage {
244 if let Some(entry) =
245 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
246 {
247 if U256::from_be_bytes(entry.key.0) == *slot {
248 assert_equal(
249 *value,
250 entry.value,
251 &format!("Storage for slot {slot:?} does not match"),
252 )?;
253 } else {
254 return Err(Error::Assertion(format!(
255 "Slot {slot:?} is missing from the database. Expected {value:?}"
256 )))
257 }
258 } else {
259 return Err(Error::Assertion(format!(
260 "Slot {slot:?} is missing from the database. Expected {value:?}"
261 )))
262 }
263 }
264
265 Ok(())
266 }
267}
268
269#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
271pub enum ForkSpec {
272 Frontier,
274 FrontierToHomesteadAt5,
276 Homestead,
278 HomesteadToDaoAt5,
280 HomesteadToEIP150At5,
282 EIP150,
284 EIP158, EIP158ToByzantiumAt5,
288 Byzantium,
290 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
294 Constantinople, ConstantinopleFix,
298 Istanbul,
300 Berlin,
302 BerlinToLondonAt5,
304 London,
306 Merge,
308 Shanghai,
310 #[serde(alias = "Merge+3540+3670")]
312 MergeEOF,
313 #[serde(alias = "Merge+3860")]
315 MergeMeterInitCode,
316 #[serde(alias = "Merge+3855")]
318 MergePush0,
319 Cancun,
321 #[serde(other)]
323 Unknown,
324}
325
326impl From<ForkSpec> for ChainSpec {
327 fn from(fork_spec: ForkSpec) -> Self {
328 let spec_builder = ChainSpecBuilder::mainnet();
329
330 match fork_spec {
331 ForkSpec::Frontier => spec_builder.frontier_activated(),
332 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => {
333 spec_builder.homestead_activated()
334 }
335 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
336 spec_builder.tangerine_whistle_activated()
337 }
338 ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
339 ForkSpec::Byzantium |
340 ForkSpec::EIP158ToByzantiumAt5 |
341 ForkSpec::ConstantinopleFix |
342 ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(),
343 ForkSpec::Istanbul => spec_builder.istanbul_activated(),
344 ForkSpec::Berlin => spec_builder.berlin_activated(),
345 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(),
346 ForkSpec::Merge |
347 ForkSpec::MergeEOF |
348 ForkSpec::MergeMeterInitCode |
349 ForkSpec::MergePush0 => spec_builder.paris_activated(),
350 ForkSpec::Shanghai => spec_builder.shanghai_activated(),
351 ForkSpec::Cancun => spec_builder.cancun_activated(),
352 ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
353 panic!("Overridden with PETERSBURG")
354 }
355 ForkSpec::Unknown => {
356 panic!("Unknown fork");
357 }
358 }
359 .build()
360 }
361}
362
363#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
365pub enum SealEngine {
366 #[default]
368 NoProof,
369}
370
371#[derive(Debug, PartialEq, Eq, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct Transaction {
375 #[serde(rename = "type")]
377 pub transaction_type: Option<U256>,
378 pub data: Bytes,
380 pub gas_limit: U256,
382 pub gas_price: Option<U256>,
384 pub nonce: U256,
386 pub r: U256,
388 pub s: U256,
390 pub v: U256,
392 pub value: U256,
394 pub chain_id: Option<U256>,
396 pub access_list: Option<AccessList>,
398 pub max_fee_per_gas: Option<U256>,
400 pub max_priority_fee_per_gas: Option<U256>,
402 pub hash: Option<B256>,
404}
405
406#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
408#[serde(rename_all = "camelCase")]
409pub struct AccessListItem {
410 pub address: Address,
412 pub storage_keys: Vec<B256>,
414}
415
416pub type AccessList = Vec<AccessListItem>;
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn header_deserialize() {
425 let test = r#"{
426 "baseFeePerGas" : "0x0a",
427 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
428 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
429 "difficulty" : "0x020000",
430 "extraData" : "0x00",
431 "gasLimit" : "0x10000000000000",
432 "gasUsed" : "0x10000000000000",
433 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
434 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
435 "nonce" : "0x0000000000000000",
436 "number" : "0x01",
437 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
438 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
439 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
440 "timestamp" : "0x03e8",
441 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
442 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
443 }"#;
444 let res = serde_json::from_str::<Header>(test);
445 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
446 }
447
448 #[test]
449 fn transaction_deserialize() {
450 let test = r#"[
451 {
452 "accessList" : [
453 ],
454 "chainId" : "0x01",
455 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
456 "gasLimit" : "0x10000000000000",
457 "maxFeePerGas" : "0x07d0",
458 "maxPriorityFeePerGas" : "0x00",
459 "nonce" : "0x01",
460 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
461 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
462 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
463 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
464 "type" : "0x02",
465 "v" : "0x01",
466 "value" : "0x00"
467 }
468 ]"#;
469
470 let res = serde_json::from_str::<Vec<Transaction>>(test);
471 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
472 }
473}