Skip to main content

ef_tests/
models.rs

1//! Shared models for <https://github.com/ethereum/tests>
2
3use crate::{assert::assert_equal, Error};
4use alloy_consensus::Header as RethHeader;
5use alloy_eips::eip4895::Withdrawals;
6use alloy_genesis::GenesisAccount;
7use alloy_primitives::{keccak256, map::HashMap, Address, Bloom, Bytes, B256, B64, U256};
8use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, ForkCondition};
9use reth_db_api::{cursor::DbDupCursorRO, tables, transaction::DbTx};
10use reth_primitives_traits::SealedHeader;
11use serde::Deserialize;
12use std::{
13    collections::BTreeMap,
14    ops::Deref,
15    sync::{Arc, OnceLock, RwLock},
16};
17
18/// The definition of a blockchain test.
19#[derive(Debug, PartialEq, Eq, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct BlockchainTest {
22    /// Genesis block header.
23    pub genesis_block_header: Header,
24    /// RLP encoded genesis block.
25    #[serde(rename = "genesisRLP")]
26    pub genesis_rlp: Option<Bytes>,
27    /// Block data.
28    pub blocks: Vec<Block>,
29    /// The expected post state.
30    pub post_state: Option<BTreeMap<Address, Account>>,
31    /// The test pre-state.
32    pub pre: State,
33    /// Hash of the best block.
34    pub lastblockhash: B256,
35    /// Network spec.
36    pub network: ForkSpec,
37    #[serde(default)]
38    /// Engine spec.
39    pub seal_engine: SealEngine,
40}
41
42/// A block header in an Ethereum blockchain test.
43#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
44#[serde(rename_all = "camelCase")]
45pub struct Header {
46    /// Bloom filter.
47    pub bloom: Bloom,
48    /// Coinbase.
49    pub coinbase: Address,
50    /// Difficulty.
51    pub difficulty: U256,
52    /// Extra data.
53    pub extra_data: Bytes,
54    /// Gas limit.
55    pub gas_limit: U256,
56    /// Gas used.
57    pub gas_used: U256,
58    /// Block Hash.
59    pub hash: B256,
60    /// Mix hash.
61    pub mix_hash: B256,
62    /// Seal nonce.
63    pub nonce: B64,
64    /// Block number.
65    pub number: U256,
66    /// Parent hash.
67    pub parent_hash: B256,
68    /// Receipt trie.
69    pub receipt_trie: B256,
70    /// State root.
71    pub state_root: B256,
72    /// Timestamp.
73    pub timestamp: U256,
74    /// Transactions trie.
75    pub transactions_trie: B256,
76    /// Uncle hash.
77    pub uncle_hash: B256,
78    /// Base fee per gas.
79    pub base_fee_per_gas: Option<U256>,
80    /// Withdrawals root.
81    pub withdrawals_root: Option<B256>,
82    /// Blob gas used.
83    pub blob_gas_used: Option<U256>,
84    /// Excess blob gas.
85    pub excess_blob_gas: Option<U256>,
86    /// Parent beacon block root.
87    pub parent_beacon_block_root: Option<B256>,
88    /// Requests root.
89    pub requests_hash: Option<B256>,
90    /// Target blobs per block.
91    pub target_blobs_per_block: Option<U256>,
92}
93
94impl From<Header> for SealedHeader {
95    fn from(value: Header) -> Self {
96        let header = RethHeader {
97            base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
98            beneficiary: value.coinbase,
99            difficulty: value.difficulty,
100            extra_data: value.extra_data,
101            gas_limit: value.gas_limit.to::<u64>(),
102            gas_used: value.gas_used.to::<u64>(),
103            mix_hash: value.mix_hash,
104            nonce: u64::from_be_bytes(value.nonce.0).into(),
105            number: value.number.to::<u64>(),
106            timestamp: value.timestamp.to::<u64>(),
107            transactions_root: value.transactions_trie,
108            receipts_root: value.receipt_trie,
109            ommers_hash: value.uncle_hash,
110            state_root: value.state_root,
111            parent_hash: value.parent_hash,
112            logs_bloom: value.bloom,
113            withdrawals_root: value.withdrawals_root,
114            blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
115            excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
116            parent_beacon_block_root: value.parent_beacon_block_root,
117            requests_hash: value.requests_hash,
118            block_access_list_hash: None,
119            slot_number: None,
120        };
121        Self::new(header, value.hash)
122    }
123}
124
125/// A block in an Ethereum blockchain test.
126#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct Block {
129    /// Block header.
130    pub block_header: Option<Header>,
131    /// RLP encoded block bytes
132    pub rlp: Bytes,
133    /// If the execution of the block should fail,
134    /// `expect_exception` is `Some`.
135    /// Its contents detail the reason for the failure.
136    pub expect_exception: Option<String>,
137    /// Transactions
138    pub transactions: Option<Vec<Transaction>>,
139    /// Uncle/ommer headers
140    pub uncle_headers: Option<Vec<Header>>,
141    /// Transaction Sequence
142    pub transaction_sequence: Option<Vec<TransactionSequence>>,
143    /// Withdrawals
144    pub withdrawals: Option<Withdrawals>,
145}
146
147/// Transaction sequence in block
148#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
149#[serde(deny_unknown_fields)]
150#[serde(rename_all = "camelCase")]
151pub struct TransactionSequence {
152    exception: String,
153    raw_bytes: Bytes,
154    valid: String,
155}
156
157/// Ethereum blockchain test data state.
158#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
159pub struct State(BTreeMap<Address, Account>);
160
161impl State {
162    /// Return state as genesis state.
163    pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
164        self.0
165            .into_iter()
166            .map(|(address, account)| {
167                let storage = account
168                    .storage
169                    .iter()
170                    .filter(|(_, v)| !v.is_zero())
171                    .map(|(k, v)| {
172                        (
173                            B256::from_slice(&k.to_be_bytes::<32>()),
174                            B256::from_slice(&v.to_be_bytes::<32>()),
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/// An account.
200#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
201#[serde(deny_unknown_fields)]
202pub struct Account {
203    /// Balance.
204    pub balance: U256,
205    /// Code.
206    pub code: Bytes,
207    /// Nonce.
208    pub nonce: U256,
209    /// Storage.
210    pub storage: BTreeMap<U256, U256>,
211}
212
213impl Account {
214    /// Check that the account matches what is in the database.
215    ///
216    /// In case of a mismatch, `Err(Error::Assertion)` is returned.
217    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,
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/// Fork specification.
266#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
267pub enum ForkSpec {
268    /// Frontier
269    Frontier,
270    /// Frontier to Homestead
271    FrontierToHomesteadAt5,
272    /// Homestead
273    Homestead,
274    /// Homestead to DAO
275    HomesteadToDaoAt5,
276    /// Homestead to Tangerine
277    HomesteadToEIP150At5,
278    /// Tangerine
279    EIP150,
280    /// Spurious Dragon
281    EIP158, // EIP-161: State trie clearing
282    /// Spurious Dragon to Byzantium
283    EIP158ToByzantiumAt5,
284    /// Byzantium
285    Byzantium,
286    /// Byzantium to Constantinople
287    ByzantiumToConstantinopleAt5, // SKIPPED
288    /// Byzantium to Constantinople
289    ByzantiumToConstantinopleFixAt5,
290    /// Constantinople
291    Constantinople, // SKIPPED
292    /// Constantinople fix
293    ConstantinopleFix,
294    /// Istanbul
295    Istanbul,
296    /// Berlin
297    Berlin,
298    /// Berlin to London
299    BerlinToLondonAt5,
300    /// London
301    London,
302    /// Paris aka The Merge
303    #[serde(alias = "Paris")]
304    Merge,
305    /// Paris to Shanghai at time 15k
306    ParisToShanghaiAtTime15k,
307    /// Shanghai
308    Shanghai,
309    /// Shanghai to Cancun at time 15k
310    ShanghaiToCancunAtTime15k,
311    /// Merge EOF test
312    #[serde(alias = "Merge+3540+3670")]
313    MergeEOF,
314    /// After Merge Init Code test
315    #[serde(alias = "Merge+3860")]
316    MergeMeterInitCode,
317    /// After Merge plus new PUSH0 opcode
318    #[serde(alias = "Merge+3855")]
319    MergePush0,
320    /// Cancun
321    Cancun,
322    /// Cancun to Prague at time 15k
323    CancunToPragueAtTime15k,
324    /// Prague
325    Prague,
326    /// Osaka
327    Osaka,
328}
329
330impl ForkSpec {
331    /// Converts this EF fork spec to a Reth [`ChainSpec`].
332    pub fn to_chain_spec(self) -> Arc<ChainSpec> {
333        static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
334        let map = MAP.get_or_init(Default::default);
335        if let Some(r) = map.read().unwrap().get(&self) {
336            return r.clone();
337        }
338        map.write()
339            .unwrap()
340            .entry(self)
341            .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
342            .clone()
343    }
344
345    fn to_chain_spec_inner(self) -> ChainSpec {
346        let spec_builder = ChainSpecBuilder::mainnet().reset();
347
348        match self {
349            Self::Frontier => spec_builder.frontier_activated(),
350            Self::FrontierToHomesteadAt5 => spec_builder
351                .frontier_activated()
352                .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
353            Self::Homestead => spec_builder.homestead_activated(),
354            Self::HomesteadToDaoAt5 => spec_builder
355                .homestead_activated()
356                .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
357            Self::HomesteadToEIP150At5 => spec_builder
358                .homestead_activated()
359                .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
360            Self::EIP150 => spec_builder.tangerine_whistle_activated(),
361            Self::EIP158 => spec_builder.spurious_dragon_activated(),
362            Self::EIP158ToByzantiumAt5 => spec_builder
363                .spurious_dragon_activated()
364                .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
365            Self::Byzantium => spec_builder.byzantium_activated(),
366            Self::ByzantiumToConstantinopleAt5 => spec_builder
367                .byzantium_activated()
368                .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
369            Self::ByzantiumToConstantinopleFixAt5 => spec_builder
370                .byzantium_activated()
371                .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
372            Self::Constantinople => spec_builder.constantinople_activated(),
373            Self::ConstantinopleFix => spec_builder.petersburg_activated(),
374            Self::Istanbul => spec_builder.istanbul_activated(),
375            Self::Berlin => spec_builder.berlin_activated(),
376            Self::BerlinToLondonAt5 => spec_builder
377                .berlin_activated()
378                .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
379            Self::London => spec_builder.london_activated(),
380            Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
381                spec_builder.paris_activated()
382            }
383            Self::ParisToShanghaiAtTime15k => spec_builder
384                .paris_activated()
385                .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
386            Self::Shanghai => spec_builder.shanghai_activated(),
387            Self::ShanghaiToCancunAtTime15k => spec_builder
388                .shanghai_activated()
389                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
390            Self::Cancun => spec_builder.cancun_activated(),
391            Self::CancunToPragueAtTime15k => spec_builder
392                .cancun_activated()
393                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
394            Self::Prague => spec_builder.prague_activated(),
395            Self::Osaka => spec_builder.osaka_activated(),
396        }
397        .build()
398    }
399}
400
401/// Possible seal engines.
402#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
403pub enum SealEngine {
404    /// No consensus checks.
405    #[default]
406    NoProof,
407}
408
409/// Ethereum blockchain test transaction data.
410#[derive(Debug, PartialEq, Eq, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct Transaction {
413    /// Transaction type
414    #[serde(rename = "type")]
415    pub transaction_type: Option<U256>,
416    /// Data.
417    pub data: Bytes,
418    /// Gas limit.
419    pub gas_limit: U256,
420    /// Gas price.
421    pub gas_price: Option<U256>,
422    /// Nonce.
423    pub nonce: U256,
424    /// Signature r part.
425    pub r: U256,
426    /// Signature s part.
427    pub s: U256,
428    /// Parity bit.
429    pub v: U256,
430    /// Transaction value.
431    pub value: U256,
432    /// Chain ID.
433    pub chain_id: Option<U256>,
434    /// Access list.
435    pub access_list: Option<AccessList>,
436    /// Max fee per gas.
437    pub max_fee_per_gas: Option<U256>,
438    /// Max priority fee per gas
439    pub max_priority_fee_per_gas: Option<U256>,
440    /// Transaction hash.
441    pub hash: Option<B256>,
442}
443
444/// Access list item
445#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
446#[serde(rename_all = "camelCase")]
447pub struct AccessListItem {
448    /// Account address
449    pub address: Address,
450    /// Storage key.
451    pub storage_keys: Vec<B256>,
452}
453
454/// Access list.
455pub type AccessList = Vec<AccessListItem>;
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460
461    #[test]
462    fn header_deserialize() {
463        let test = r#"{
464            "baseFeePerGas" : "0x0a",
465            "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
466            "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
467            "difficulty" : "0x020000",
468            "extraData" : "0x00",
469            "gasLimit" : "0x10000000000000",
470            "gasUsed" : "0x10000000000000",
471            "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
472            "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
473            "nonce" : "0x0000000000000000",
474            "number" : "0x01",
475            "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
476            "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
477            "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
478            "timestamp" : "0x03e8",
479            "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
480            "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
481        }"#;
482        let res = serde_json::from_str::<Header>(test);
483        assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
484    }
485
486    #[test]
487    fn transaction_deserialize() {
488        let test = r#"[
489            {
490                "accessList" : [
491                ],
492                "chainId" : "0x01",
493                "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
494                "gasLimit" : "0x10000000000000",
495                "maxFeePerGas" : "0x07d0",
496                "maxPriorityFeePerGas" : "0x00",
497                "nonce" : "0x01",
498                "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
499                "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
500                "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
501                "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
502                "type" : "0x02",
503                "v" : "0x01",
504                "value" : "0x00"
505            }
506        ]"#;
507
508        let res = serde_json::from_str::<Vec<Transaction>>(test);
509        assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
510    }
511}