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::{cursor::DbDupCursorRO, tables, transaction::DbTx};
9use reth_primitives_traits::SealedHeader;
10use seismic_alloy_genesis::GenesisAccount;
11use serde::Deserialize;
12use std::{collections::BTreeMap, ops::Deref};
13
14use alloy_primitives::FlaggedStorage;
15
16#[derive(Debug, PartialEq, Eq, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct BlockchainTest {
20 pub genesis_block_header: Header,
22 #[serde(rename = "genesisRLP")]
24 pub genesis_rlp: Option<Bytes>,
25 pub blocks: Vec<Block>,
27 pub post_state: Option<BTreeMap<Address, Account>>,
29 pub pre: State,
31 pub lastblockhash: B256,
33 pub network: ForkSpec,
35 #[serde(default)]
36 pub seal_engine: SealEngine,
38}
39
40#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
42#[serde(rename_all = "camelCase")]
43pub struct Header {
44 pub bloom: Bloom,
46 pub coinbase: Address,
48 pub difficulty: U256,
50 pub extra_data: Bytes,
52 pub gas_limit: U256,
54 pub gas_used: U256,
56 pub hash: B256,
58 pub mix_hash: B256,
60 pub nonce: B64,
62 pub number: U256,
64 pub parent_hash: B256,
66 pub receipt_trie: B256,
68 pub state_root: B256,
70 pub timestamp: U256,
72 pub transactions_trie: B256,
74 pub uncle_hash: B256,
76 pub base_fee_per_gas: Option<U256>,
78 pub withdrawals_root: Option<B256>,
80 pub blob_gas_used: Option<U256>,
82 pub excess_blob_gas: Option<U256>,
84 pub parent_beacon_block_root: Option<B256>,
86 pub requests_hash: Option<B256>,
88 pub target_blobs_per_block: Option<U256>,
90}
91
92impl From<Header> for SealedHeader {
93 fn from(value: Header) -> Self {
94 let header = RethHeader {
95 base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
96 beneficiary: value.coinbase,
97 difficulty: value.difficulty,
98 extra_data: value.extra_data,
99 gas_limit: value.gas_limit.to::<u64>(),
100 gas_used: value.gas_used.to::<u64>(),
101 mix_hash: value.mix_hash,
102 nonce: u64::from_be_bytes(value.nonce.0).into(),
103 number: value.number.to::<u64>(),
104 timestamp: value.timestamp.to::<u64>(),
105 transactions_root: value.transactions_trie,
106 receipts_root: value.receipt_trie,
107 ommers_hash: value.uncle_hash,
108 state_root: value.state_root,
109 parent_hash: value.parent_hash,
110 logs_bloom: value.bloom,
111 withdrawals_root: value.withdrawals_root,
112 blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
113 excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
114 parent_beacon_block_root: value.parent_beacon_block_root,
115 requests_hash: value.requests_hash,
116 };
117 Self::new(header, value.hash)
118 }
119}
120
121#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
123#[serde(rename_all = "camelCase")]
124pub struct Block {
125 pub block_header: Option<Header>,
127 pub rlp: Bytes,
129 pub expect_exception: Option<String>,
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 into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
160 let is_private = false; self.0
162 .into_iter()
163 .map(|(address, account)| {
164 let storage = account
165 .storage
166 .iter()
167 .filter(|(_, v)| !v.is_zero())
168 .map(|(k, v)| {
169 (
170 B256::from_slice(&k.to_be_bytes::<32>()),
171 FlaggedStorage::new(
172 U256::from_be_bytes(v.to_be_bytes::<32>()),
173 is_private,
174 ),
175 )
176 })
177 .collect();
178 let account = GenesisAccount {
179 balance: account.balance,
180 nonce: Some(account.nonce.try_into().unwrap()),
181 code: Some(account.code).filter(|c| !c.is_empty()),
182 storage: Some(storage),
183 private_key: None,
184 };
185 (address, account)
186 })
187 .collect::<BTreeMap<_, _>>()
188 }
189}
190
191impl Deref for State {
192 type Target = BTreeMap<Address, Account>;
193
194 fn deref(&self) -> &Self::Target {
195 &self.0
196 }
197}
198
199#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
201#[serde(deny_unknown_fields)]
202pub struct Account {
203 pub balance: U256,
205 pub code: Bytes,
207 pub nonce: U256,
209 pub storage: BTreeMap<U256, U256>,
211}
212
213impl Account {
214 pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
218 let account =
219 tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
220 Error::Assertion(format!(
221 "Expected account ({address}) is missing from DB: {self:?}"
222 ))
223 })?;
224
225 assert_equal(self.balance, account.balance, "Balance does not match")?;
226 assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
227
228 if let Some(bytecode_hash) = account.bytecode_hash {
229 assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
230 } else {
231 assert_equal(
232 self.code.is_empty(),
233 true,
234 "Expected empty bytecode, got bytecode in db.",
235 )?;
236 }
237
238 let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
239 for (slot, value) in &self.storage {
240 if let Some(entry) =
241 storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
242 {
243 if U256::from_be_bytes(entry.key.0) == *slot {
244 assert_equal(
245 *value,
246 entry.value.value,
247 &format!("Storage for slot {slot:?} does not match"),
248 )?;
249 } else {
250 return Err(Error::Assertion(format!(
251 "Slot {slot:?} is missing from the database. Expected {value:?}"
252 )))
253 }
254 } else {
255 return Err(Error::Assertion(format!(
256 "Slot {slot:?} is missing from the database. Expected {value:?}"
257 )))
258 }
259 }
260
261 Ok(())
262 }
263}
264
265#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
267pub enum ForkSpec {
268 Frontier,
270 FrontierToHomesteadAt5,
272 Homestead,
274 HomesteadToDaoAt5,
276 HomesteadToEIP150At5,
278 EIP150,
280 EIP158, EIP158ToByzantiumAt5,
284 Byzantium,
286 ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
290 Constantinople, ConstantinopleFix,
294 Istanbul,
296 Berlin,
298 BerlinToLondonAt5,
300 London,
302 Merge,
304 Shanghai,
306 #[serde(alias = "Merge+3540+3670")]
308 MergeEOF,
309 #[serde(alias = "Merge+3860")]
311 MergeMeterInitCode,
312 #[serde(alias = "Merge+3855")]
314 MergePush0,
315 Cancun,
317 Prague,
319}
320
321impl From<ForkSpec> for ChainSpec {
322 fn from(fork_spec: ForkSpec) -> Self {
323 let spec_builder = ChainSpecBuilder::mainnet();
324
325 match fork_spec {
326 ForkSpec::Frontier => spec_builder.frontier_activated(),
327 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => {
328 spec_builder.homestead_activated()
329 }
330 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
331 spec_builder.tangerine_whistle_activated()
332 }
333 ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
334 ForkSpec::Byzantium |
335 ForkSpec::EIP158ToByzantiumAt5 |
336 ForkSpec::ConstantinopleFix |
337 ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(),
338 ForkSpec::Istanbul => spec_builder.istanbul_activated(),
339 ForkSpec::Berlin => spec_builder.berlin_activated(),
340 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(),
341 ForkSpec::Merge |
342 ForkSpec::MergeEOF |
343 ForkSpec::MergeMeterInitCode |
344 ForkSpec::MergePush0 => spec_builder.paris_activated(),
345 ForkSpec::Shanghai => spec_builder.shanghai_activated(),
346 ForkSpec::Cancun => spec_builder.cancun_activated(),
347 ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
348 panic!("Overridden with PETERSBURG")
349 }
350 ForkSpec::Prague => spec_builder.prague_activated(),
351 }
352 .build()
353 }
354}
355
356#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
358pub enum SealEngine {
359 #[default]
361 NoProof,
362}
363
364#[derive(Debug, PartialEq, Eq, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct Transaction {
368 #[serde(rename = "type")]
370 pub transaction_type: Option<U256>,
371 pub data: Bytes,
373 pub gas_limit: U256,
375 pub gas_price: Option<U256>,
377 pub nonce: U256,
379 pub r: U256,
381 pub s: U256,
383 pub v: U256,
385 pub value: U256,
387 pub chain_id: Option<U256>,
389 pub access_list: Option<AccessList>,
391 pub max_fee_per_gas: Option<U256>,
393 pub max_priority_fee_per_gas: Option<U256>,
395 pub hash: Option<B256>,
397}
398
399#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
401#[serde(rename_all = "camelCase")]
402pub struct AccessListItem {
403 pub address: Address,
405 pub storage_keys: Vec<B256>,
407}
408
409pub type AccessList = Vec<AccessListItem>;
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn header_deserialize() {
418 let test = r#"{
419 "baseFeePerGas" : "0x0a",
420 "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
421 "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
422 "difficulty" : "0x020000",
423 "extraData" : "0x00",
424 "gasLimit" : "0x10000000000000",
425 "gasUsed" : "0x10000000000000",
426 "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
427 "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
428 "nonce" : "0x0000000000000000",
429 "number" : "0x01",
430 "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
431 "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
432 "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
433 "timestamp" : "0x03e8",
434 "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
435 "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
436 }"#;
437 let res = serde_json::from_str::<Header>(test);
438 assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
439 }
440
441 #[test]
442 fn transaction_deserialize() {
443 let test = r#"[
444 {
445 "accessList" : [
446 ],
447 "chainId" : "0x01",
448 "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
449 "gasLimit" : "0x10000000000000",
450 "maxFeePerGas" : "0x07d0",
451 "maxPriorityFeePerGas" : "0x00",
452 "nonce" : "0x01",
453 "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
454 "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
455 "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
456 "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
457 "type" : "0x02",
458 "v" : "0x01",
459 "value" : "0x00"
460 }
461 ]"#;
462
463 let res = serde_json::from_str::<Vec<Transaction>>(test);
464 assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
465 }
466}