use crate::{assert::assert_equal, Error};
use alloy_consensus::Header as RethHeader;
use alloy_eips::eip4895::Withdrawals;
use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256};
use reth_chainspec::{ChainSpec, ChainSpecBuilder};
use reth_db::tables;
use reth_db_api::{
cursor::DbDupCursorRO,
transaction::{DbTx, DbTxMut},
};
use reth_primitives::{Account as RethAccount, Bytecode, SealedHeader, StorageEntry};
use serde::Deserialize;
use std::{collections::BTreeMap, ops::Deref};
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockchainTest {
pub genesis_block_header: Header,
#[serde(rename = "genesisRLP")]
pub genesis_rlp: Option<Bytes>,
pub blocks: Vec<Block>,
pub post_state: Option<BTreeMap<Address, Account>>,
pub post_state_hash: Option<B256>,
pub pre: State,
pub lastblockhash: B256,
pub network: ForkSpec,
#[serde(default)]
pub seal_engine: SealEngine,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Header {
pub bloom: Bloom,
pub coinbase: Address,
pub difficulty: U256,
pub extra_data: Bytes,
pub gas_limit: U256,
pub gas_used: U256,
pub hash: B256,
pub mix_hash: B256,
pub nonce: B64,
pub number: U256,
pub parent_hash: B256,
pub receipt_trie: B256,
pub state_root: B256,
pub timestamp: U256,
pub transactions_trie: B256,
pub uncle_hash: B256,
pub base_fee_per_gas: Option<U256>,
pub withdrawals_root: Option<B256>,
pub blob_gas_used: Option<U256>,
pub excess_blob_gas: Option<U256>,
pub parent_beacon_block_root: Option<B256>,
pub requests_hash: Option<B256>,
pub target_blobs_per_block: Option<U256>,
}
impl From<Header> for SealedHeader {
fn from(value: Header) -> Self {
let header = RethHeader {
base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
beneficiary: value.coinbase,
difficulty: value.difficulty,
extra_data: value.extra_data,
gas_limit: value.gas_limit.to::<u64>(),
gas_used: value.gas_used.to::<u64>(),
mix_hash: value.mix_hash,
nonce: u64::from_be_bytes(value.nonce.0).into(),
number: value.number.to::<u64>(),
timestamp: value.timestamp.to::<u64>(),
transactions_root: value.transactions_trie,
receipts_root: value.receipt_trie,
ommers_hash: value.uncle_hash,
state_root: value.state_root,
parent_hash: value.parent_hash,
logs_bloom: value.bloom,
withdrawals_root: value.withdrawals_root,
blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
parent_beacon_block_root: value.parent_beacon_block_root,
requests_hash: value.requests_hash,
target_blobs_per_block: value.target_blobs_per_block.map(|v| v.to::<u64>()),
};
Self::new(header, value.hash)
}
}
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub block_header: Option<Header>,
pub rlp: Bytes,
pub transactions: Option<Vec<Transaction>>,
pub uncle_headers: Option<Vec<Header>>,
pub transaction_sequence: Option<Vec<TransactionSequence>>,
pub withdrawals: Option<Withdrawals>,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct TransactionSequence {
exception: String,
raw_bytes: Bytes,
valid: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
pub struct State(BTreeMap<Address, Account>);
impl State {
pub fn write_to_db(&self, tx: &impl DbTxMut) -> Result<(), Error> {
for (&address, account) in &self.0 {
let hashed_address = keccak256(address);
let has_code = !account.code.is_empty();
let code_hash = has_code.then(|| keccak256(&account.code));
let reth_account = RethAccount {
balance: account.balance,
nonce: account.nonce.to::<u64>(),
bytecode_hash: code_hash,
};
tx.put::<tables::PlainAccountState>(address, reth_account)?;
tx.put::<tables::HashedAccounts>(hashed_address, reth_account)?;
if let Some(code_hash) = code_hash {
tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.clone()))?;
}
for (k, v) in &account.storage {
if v.is_zero() {
continue
}
let storage_key = B256::from_slice(&k.to_be_bytes::<32>());
tx.put::<tables::PlainStorageState>(
address,
StorageEntry { key: storage_key, value: *v },
)?;
tx.put::<tables::HashedStorages>(
hashed_address,
StorageEntry { key: keccak256(storage_key), value: *v },
)?;
}
}
Ok(())
}
}
impl Deref for State {
type Target = BTreeMap<Address, Account>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct Account {
pub balance: U256,
pub code: Bytes,
pub nonce: U256,
pub storage: BTreeMap<U256, U256>,
}
impl Account {
pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
let account =
tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
Error::Assertion(format!(
"Expected account ({address}) is missing from DB: {self:?}"
))
})?;
assert_equal(self.balance, account.balance, "Balance does not match")?;
assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
if let Some(bytecode_hash) = account.bytecode_hash {
assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
} else {
assert_equal(
self.code.is_empty(),
true,
"Expected empty bytecode, got bytecode in db.",
)?;
}
let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
for (slot, value) in &self.storage {
if let Some(entry) =
storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
{
if U256::from_be_bytes(entry.key.0) == *slot {
assert_equal(
*value,
entry.value,
&format!("Storage for slot {slot:?} does not match"),
)?;
} else {
return Err(Error::Assertion(format!(
"Slot {slot:?} is missing from the database. Expected {value:?}"
)))
}
} else {
return Err(Error::Assertion(format!(
"Slot {slot:?} is missing from the database. Expected {value:?}"
)))
}
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
pub enum ForkSpec {
Frontier,
FrontierToHomesteadAt5,
Homestead,
HomesteadToDaoAt5,
HomesteadToEIP150At5,
EIP150,
EIP158, EIP158ToByzantiumAt5,
Byzantium,
ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5,
Constantinople, ConstantinopleFix,
Istanbul,
Berlin,
BerlinToLondonAt5,
London,
Merge,
Shanghai,
#[serde(alias = "Merge+3540+3670")]
MergeEOF,
#[serde(alias = "Merge+3860")]
MergeMeterInitCode,
#[serde(alias = "Merge+3855")]
MergePush0,
Cancun,
#[serde(other)]
Unknown,
}
impl From<ForkSpec> for ChainSpec {
fn from(fork_spec: ForkSpec) -> Self {
let spec_builder = ChainSpecBuilder::mainnet();
match fork_spec {
ForkSpec::Frontier => spec_builder.frontier_activated(),
ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => {
spec_builder.homestead_activated()
}
ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
spec_builder.tangerine_whistle_activated()
}
ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(),
ForkSpec::Byzantium |
ForkSpec::EIP158ToByzantiumAt5 |
ForkSpec::ConstantinopleFix |
ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(),
ForkSpec::Istanbul => spec_builder.istanbul_activated(),
ForkSpec::Berlin => spec_builder.berlin_activated(),
ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(),
ForkSpec::Merge |
ForkSpec::MergeEOF |
ForkSpec::MergeMeterInitCode |
ForkSpec::MergePush0 => spec_builder.paris_activated(),
ForkSpec::Shanghai => spec_builder.shanghai_activated(),
ForkSpec::Cancun => spec_builder.cancun_activated(),
ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => {
panic!("Overridden with PETERSBURG")
}
ForkSpec::Unknown => {
panic!("Unknown fork");
}
}
.build()
}
}
#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
pub enum SealEngine {
#[default]
NoProof,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
#[serde(rename = "type")]
pub transaction_type: Option<U256>,
pub data: Bytes,
pub gas_limit: U256,
pub gas_price: Option<U256>,
pub nonce: U256,
pub r: U256,
pub s: U256,
pub v: U256,
pub value: U256,
pub chain_id: Option<U256>,
pub access_list: Option<AccessList>,
pub max_fee_per_gas: Option<U256>,
pub max_priority_fee_per_gas: Option<U256>,
pub hash: Option<B256>,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AccessListItem {
pub address: Address,
pub storage_keys: Vec<B256>,
}
pub type AccessList = Vec<AccessListItem>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_deserialize() {
let test = r#"{
"baseFeePerGas" : "0x0a",
"bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"difficulty" : "0x020000",
"extraData" : "0x00",
"gasLimit" : "0x10000000000000",
"gasUsed" : "0x10000000000000",
"hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
"mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce" : "0x0000000000000000",
"number" : "0x01",
"parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
"receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
"stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
"timestamp" : "0x03e8",
"transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
"uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
}"#;
let res = serde_json::from_str::<Header>(test);
assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
}
#[test]
fn transaction_deserialize() {
let test = r#"[
{
"accessList" : [
],
"chainId" : "0x01",
"data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
"gasLimit" : "0x10000000000000",
"maxFeePerGas" : "0x07d0",
"maxPriorityFeePerGas" : "0x00",
"nonce" : "0x01",
"r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
"s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
"sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"to" : "0xcccccccccccccccccccccccccccccccccccccccc",
"type" : "0x02",
"v" : "0x01",
"value" : "0x00"
}
]"#;
let res = serde_json::from_str::<Vec<Transaction>>(test);
assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
}
}