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    /// Block access list hash.
93    pub block_access_list_hash: Option<B256>,
94    /// Slot number.
95    pub slot_number: Option<U256>,
96}
97
98impl From<Header> for SealedHeader {
99    fn from(value: Header) -> Self {
100        let header = RethHeader {
101            base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
102            beneficiary: value.coinbase,
103            difficulty: value.difficulty,
104            extra_data: value.extra_data,
105            gas_limit: value.gas_limit.to::<u64>(),
106            gas_used: value.gas_used.to::<u64>(),
107            mix_hash: value.mix_hash,
108            nonce: u64::from_be_bytes(value.nonce.0).into(),
109            number: value.number.to::<u64>(),
110            timestamp: value.timestamp.to::<u64>(),
111            transactions_root: value.transactions_trie,
112            receipts_root: value.receipt_trie,
113            ommers_hash: value.uncle_hash,
114            state_root: value.state_root,
115            parent_hash: value.parent_hash,
116            logs_bloom: value.bloom,
117            withdrawals_root: value.withdrawals_root,
118            blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
119            excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
120            parent_beacon_block_root: value.parent_beacon_block_root,
121            requests_hash: value.requests_hash,
122            block_access_list_hash: value.block_access_list_hash,
123            slot_number: value.slot_number.map(|v| v.to::<u64>()),
124        };
125        Self::new(header, value.hash)
126    }
127}
128
129/// A block in an Ethereum blockchain test.
130#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
131#[serde(rename_all = "camelCase")]
132pub struct Block {
133    /// Block header.
134    pub block_header: Option<Header>,
135    /// RLP encoded block bytes
136    pub rlp: Bytes,
137    /// If the execution of the block should fail,
138    /// `expect_exception` is `Some`.
139    /// Its contents detail the reason for the failure.
140    pub expect_exception: Option<String>,
141    /// Transactions
142    pub transactions: Option<Vec<Transaction>>,
143    /// Uncle/ommer headers
144    pub uncle_headers: Option<Vec<Header>>,
145    /// Transaction Sequence
146    pub transaction_sequence: Option<Vec<TransactionSequence>>,
147    /// Withdrawals
148    pub withdrawals: Option<Withdrawals>,
149}
150
151/// Transaction sequence in block
152#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
153#[serde(deny_unknown_fields)]
154#[serde(rename_all = "camelCase")]
155pub struct TransactionSequence {
156    exception: String,
157    raw_bytes: Bytes,
158    valid: String,
159}
160
161/// Ethereum blockchain test data state.
162#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
163pub struct State(BTreeMap<Address, Account>);
164
165impl State {
166    /// Return state as genesis state.
167    pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
168        self.0
169            .into_iter()
170            .map(|(address, account)| {
171                let storage = account
172                    .storage
173                    .iter()
174                    .filter(|(_, v)| !v.is_zero())
175                    .map(|(k, v)| (B256::from(*k), B256::from(*v)))
176                    .collect();
177                let account = GenesisAccount {
178                    balance: account.balance,
179                    nonce: Some(account.nonce.try_into().unwrap()),
180                    code: Some(account.code).filter(|c| !c.is_empty()),
181                    storage: Some(storage),
182                    ..Default::default()
183                };
184                (address, account)
185            })
186            .collect::<BTreeMap<_, _>>()
187    }
188}
189
190impl Deref for State {
191    type Target = BTreeMap<Address, Account>;
192
193    fn deref(&self) -> &Self::Target {
194        &self.0
195    }
196}
197
198/// An account.
199#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
200#[serde(deny_unknown_fields)]
201pub struct Account {
202    /// Balance.
203    pub balance: U256,
204    /// Code.
205    pub code: Bytes,
206    /// Nonce.
207    pub nonce: U256,
208    /// Storage.
209    pub storage: BTreeMap<U256, U256>,
210}
211
212impl Account {
213    /// Check that the account matches what is in the database.
214    ///
215    /// In case of a mismatch, `Err(Error::Assertion)` is returned.
216    pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
217        let account =
218            tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
219                Error::Assertion(format!(
220                    "Expected account ({address}) is missing from DB: {self:?}"
221                ))
222            })?;
223
224        assert_equal(self.balance, account.balance, "Balance does not match")?;
225        assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
226
227        if let Some(bytecode_hash) = account.bytecode_hash {
228            assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
229        } else {
230            assert_equal(
231                self.code.is_empty(),
232                true,
233                "Expected empty bytecode, got bytecode in db.",
234            )?;
235        }
236
237        let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
238        for (slot, value) in &self.storage {
239            if let Some(entry) = storage_cursor.seek_by_key_subkey(address, B256::from(*slot))? {
240                if U256::from_be_bytes(entry.key.0) == *slot {
241                    assert_equal(
242                        *value,
243                        entry.value,
244                        &format!("Storage for slot {slot:?} does not match"),
245                    )?;
246                } else {
247                    return Err(Error::Assertion(format!(
248                        "Slot {slot:?} is missing from the database. Expected {value:?}"
249                    )))
250                }
251            } else {
252                return Err(Error::Assertion(format!(
253                    "Slot {slot:?} is missing from the database. Expected {value:?}"
254                )))
255            }
256        }
257
258        Ok(())
259    }
260}
261
262/// Fork specification.
263#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
264pub enum ForkSpec {
265    /// Frontier
266    Frontier,
267    /// Frontier to Homestead
268    FrontierToHomesteadAt5,
269    /// Homestead
270    Homestead,
271    /// Homestead to DAO
272    HomesteadToDaoAt5,
273    /// Homestead to Tangerine
274    HomesteadToEIP150At5,
275    /// Tangerine
276    EIP150,
277    /// Spurious Dragon
278    EIP158, // EIP-161: State trie clearing
279    /// Spurious Dragon to Byzantium
280    EIP158ToByzantiumAt5,
281    /// Byzantium
282    Byzantium,
283    /// Byzantium to Constantinople
284    ByzantiumToConstantinopleAt5, // SKIPPED
285    /// Byzantium to Constantinople
286    ByzantiumToConstantinopleFixAt5,
287    /// Constantinople
288    Constantinople, // SKIPPED
289    /// Constantinople fix
290    ConstantinopleFix,
291    /// Istanbul
292    Istanbul,
293    /// Berlin
294    Berlin,
295    /// Berlin to London
296    BerlinToLondonAt5,
297    /// London
298    London,
299    /// Paris aka The Merge
300    #[serde(alias = "Paris")]
301    Merge,
302    /// Paris to Shanghai at time 15k
303    ParisToShanghaiAtTime15k,
304    /// Shanghai
305    Shanghai,
306    /// Shanghai to Cancun at time 15k
307    ShanghaiToCancunAtTime15k,
308    /// Merge EOF test
309    #[serde(alias = "Merge+3540+3670")]
310    MergeEOF,
311    /// After Merge Init Code test
312    #[serde(alias = "Merge+3860")]
313    MergeMeterInitCode,
314    /// After Merge plus new PUSH0 opcode
315    #[serde(alias = "Merge+3855")]
316    MergePush0,
317    /// Cancun
318    Cancun,
319    /// Cancun to Prague at time 15k
320    CancunToPragueAtTime15k,
321    /// Prague
322    Prague,
323    /// Osaka
324    Osaka,
325    /// Amsterdam
326    Amsterdam,
327}
328
329impl ForkSpec {
330    /// Converts this EF fork spec to a Reth [`ChainSpec`].
331    pub fn to_chain_spec(self) -> Arc<ChainSpec> {
332        static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
333        let map = MAP.get_or_init(Default::default);
334        if let Some(r) = map.read().unwrap().get(&self) {
335            return r.clone();
336        }
337        map.write()
338            .unwrap()
339            .entry(self)
340            .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
341            .clone()
342    }
343
344    fn to_chain_spec_inner(self) -> ChainSpec {
345        let spec_builder = ChainSpecBuilder::mainnet().reset();
346
347        match self {
348            Self::Frontier => spec_builder.frontier_activated(),
349            Self::FrontierToHomesteadAt5 => spec_builder
350                .frontier_activated()
351                .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
352            Self::Homestead => spec_builder.homestead_activated(),
353            Self::HomesteadToDaoAt5 => spec_builder
354                .homestead_activated()
355                .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
356            Self::HomesteadToEIP150At5 => spec_builder
357                .homestead_activated()
358                .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
359            Self::EIP150 => spec_builder.tangerine_whistle_activated(),
360            Self::EIP158 => spec_builder.spurious_dragon_activated(),
361            Self::EIP158ToByzantiumAt5 => spec_builder
362                .spurious_dragon_activated()
363                .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
364            Self::Byzantium => spec_builder.byzantium_activated(),
365            Self::ByzantiumToConstantinopleAt5 => spec_builder
366                .byzantium_activated()
367                .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
368            Self::ByzantiumToConstantinopleFixAt5 => spec_builder
369                .byzantium_activated()
370                .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
371            Self::Constantinople => spec_builder.constantinople_activated(),
372            Self::ConstantinopleFix => spec_builder.petersburg_activated(),
373            Self::Istanbul => spec_builder.istanbul_activated(),
374            Self::Berlin => spec_builder.berlin_activated(),
375            Self::BerlinToLondonAt5 => spec_builder
376                .berlin_activated()
377                .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
378            Self::London => spec_builder.london_activated(),
379            Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
380                spec_builder.paris_activated()
381            }
382            Self::ParisToShanghaiAtTime15k => spec_builder
383                .paris_activated()
384                .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
385            Self::Shanghai => spec_builder.shanghai_activated(),
386            Self::ShanghaiToCancunAtTime15k => spec_builder
387                .shanghai_activated()
388                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
389            Self::Cancun => spec_builder.cancun_activated(),
390            Self::CancunToPragueAtTime15k => spec_builder
391                .cancun_activated()
392                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
393            Self::Prague => spec_builder.prague_activated(),
394            Self::Osaka => spec_builder.osaka_activated(),
395            Self::Amsterdam => spec_builder.amsterdam_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}