Skip to main content

ethrex_common/types/
genesis.rs

1use bytes::Bytes;
2use ethereum_types::{Address, Bloom, H256, U256};
3use ethrex_crypto::{NativeCrypto, keccak::keccak_hash};
4use ethrex_rlp::encode::RLPEncode;
5use ethrex_trie::Trie;
6use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize};
7use serde::{Deserialize, Serialize};
8use std::{
9    collections::{BTreeMap, HashMap},
10    io::{BufReader, Error},
11    path::Path,
12};
13use tracing::warn;
14
15use super::{
16    AccountState, Block, BlockBody, BlockHeader, BlockNumber, INITIAL_BASE_FEE,
17    compute_receipts_root, compute_transactions_root, compute_withdrawals_root,
18};
19use crate::{
20    constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH, EMPTY_BLOCK_ACCESS_LIST_HASH},
21    rkyv_utils,
22};
23
24#[allow(unused)]
25#[derive(Debug, Deserialize, Serialize, Clone, Default)]
26#[serde(rename_all = "camelCase")]
27pub struct Genesis {
28    /// Chain configuration
29    pub config: ChainConfig,
30    /// The initial state of the accounts in the genesis block.
31    /// This is a BTreeMap because: https://github.com/lambdaclass/ethrex/issues/2070
32    pub alloc: BTreeMap<Address, GenesisAccount>,
33    /// Genesis header values
34    pub coinbase: Address,
35    pub difficulty: U256,
36    #[serde(default, with = "crate::serde_utils::bytes")]
37    pub extra_data: Bytes,
38    #[serde(with = "crate::serde_utils::u64::hex_str")]
39    pub gas_limit: u64,
40    #[serde(with = "crate::serde_utils::u64::hex_str")]
41    pub nonce: u64,
42    #[serde(alias = "mixHash", alias = "mixhash")]
43    pub mix_hash: H256,
44    #[serde(deserialize_with = "crate::serde_utils::u64::deser_hex_or_dec_str")]
45    #[serde(serialize_with = "crate::serde_utils::u256::serialize_number")]
46    pub timestamp: u64,
47    #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
48    pub base_fee_per_gas: Option<u64>,
49    #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
50    pub blob_gas_used: Option<u64>,
51    #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
52    pub excess_blob_gas: Option<u64>,
53    pub requests_hash: Option<H256>,
54    // Amsterdam fork fields (EIP-7928)
55    pub block_access_list_hash: Option<H256>,
56    #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
57    pub slot_number: Option<u64>,
58}
59
60#[derive(Debug, thiserror::Error)]
61pub enum GenesisError {
62    #[error("Failed to decode genesis file: {0}")]
63    Decode(#[from] serde_json::Error),
64    #[error("Fork not supported. Only post-merge networks are supported.")]
65    InvalidFork(),
66    #[error("Failed to open genesis file: {0}")]
67    File(#[from] Error),
68}
69
70impl TryFrom<&Path> for Genesis {
71    type Error = GenesisError;
72
73    fn try_from(genesis_file_path: &Path) -> Result<Self, Self::Error> {
74        let genesis_file = std::fs::File::open(genesis_file_path)?;
75        let genesis_reader = BufReader::new(genesis_file);
76        let genesis: Genesis = serde_json::from_reader(genesis_reader)?;
77
78        // ethrex only supports post-merge (PoS) networks. PoW execution is not planned.
79        if is_unsupported_pow_genesis(&genesis) {
80            warn!("Genesis has no merge configuration; ethrex only supports post-merge networks.");
81        }
82
83        if genesis.config.bpo3_time.is_some() && genesis.config.blob_schedule.bpo3.is_none()
84            || genesis.config.bpo4_time.is_some() && genesis.config.blob_schedule.bpo4.is_none()
85            || genesis.config.bpo5_time.is_some() && genesis.config.blob_schedule.bpo5.is_none()
86        {
87            warn!("BPO time set but no BPO BlobSchedule found in ChainConfig")
88        }
89
90        Ok(genesis)
91    }
92}
93
94/// Returns true for a genesis that describes a pre-merge PoW chain with no
95/// merge configured. A real post-merge genesis either configures the merge
96/// (terminal total difficulty or merge-netsplit block) or starts post-merge
97/// (genesis difficulty 0). The previous heuristic warned whenever the
98/// post-merge forks were scheduled at non-zero timestamps, which false-fired
99/// on every mainnet-style genesis.
100///
101/// Note: `terminal_total_difficulty = Some(0)` is the sentinel for "PoS active
102/// from genesis" and counts as merge-configured here, as does the
103/// `terminal_total_difficulty_passed` flag (the post-merge signal used by the
104/// sync manager).
105fn is_unsupported_pow_genesis(genesis: &Genesis) -> bool {
106    let merge_configured = genesis.config.terminal_total_difficulty.is_some()
107        || genesis.config.merge_netsplit_block.is_some()
108        || genesis.config.terminal_total_difficulty_passed;
109    let post_merge_at_genesis = genesis.difficulty.is_zero();
110    !merge_configured && !post_merge_at_genesis
111}
112
113#[allow(unused)]
114#[derive(
115    Clone,
116    Copy,
117    Debug,
118    Serialize,
119    Deserialize,
120    PartialEq,
121    RSerialize,
122    RDeserialize,
123    Archive,
124    Default,
125)]
126#[serde(rename_all = "camelCase")]
127pub struct ForkBlobSchedule {
128    pub base_fee_update_fraction: u64,
129    pub max: u32,
130    pub target: u32,
131}
132
133#[allow(unused)]
134#[derive(
135    Clone, Copy, Debug, Serialize, Deserialize, PartialEq, RSerialize, RDeserialize, Archive,
136)]
137#[serde(rename_all = "camelCase")]
138pub struct BlobSchedule {
139    #[serde(default = "default_cancun_schedule")]
140    pub cancun: ForkBlobSchedule,
141    #[serde(default = "default_prague_schedule")]
142    pub prague: ForkBlobSchedule,
143    #[serde(default = "default_osaka_schedule")]
144    pub osaka: ForkBlobSchedule,
145    #[serde(default = "default_bpo1_schedule")]
146    pub bpo1: ForkBlobSchedule,
147    #[serde(default = "default_bpo2_schedule")]
148    pub bpo2: ForkBlobSchedule,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub bpo3: Option<ForkBlobSchedule>,
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub bpo4: Option<ForkBlobSchedule>,
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub bpo5: Option<ForkBlobSchedule>,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub amsterdam: Option<ForkBlobSchedule>,
157}
158
159impl Default for BlobSchedule {
160    fn default() -> Self {
161        BlobSchedule {
162            cancun: default_cancun_schedule(),
163            prague: default_prague_schedule(),
164            osaka: default_osaka_schedule(),
165            bpo1: default_bpo1_schedule(),
166            bpo2: default_bpo2_schedule(),
167            bpo3: None,
168            bpo4: None,
169            bpo5: None,
170            amsterdam: None,
171        }
172    }
173}
174
175fn default_cancun_schedule() -> ForkBlobSchedule {
176    ForkBlobSchedule {
177        target: 3,
178        max: 6,
179        base_fee_update_fraction: 3338477,
180    }
181}
182
183fn default_prague_schedule() -> ForkBlobSchedule {
184    ForkBlobSchedule {
185        target: 6,
186        max: 9,
187        base_fee_update_fraction: 5007716,
188    }
189}
190
191fn default_osaka_schedule() -> ForkBlobSchedule {
192    ForkBlobSchedule {
193        target: 6,
194        max: 9,
195        base_fee_update_fraction: 5007716,
196    }
197}
198
199fn default_bpo1_schedule() -> ForkBlobSchedule {
200    ForkBlobSchedule {
201        target: 10,
202        max: 15,
203        base_fee_update_fraction: 8346193,
204    }
205}
206
207fn default_bpo2_schedule() -> ForkBlobSchedule {
208    ForkBlobSchedule {
209        target: 14,
210        max: 21,
211        base_fee_update_fraction: 11684671,
212    }
213}
214/// Blockchain settings defined per block
215#[allow(unused)]
216#[derive(
217    Clone,
218    Copy,
219    Debug,
220    Serialize,
221    Deserialize,
222    Default,
223    PartialEq,
224    RSerialize,
225    RDeserialize,
226    Archive,
227)]
228#[serde(rename_all = "camelCase")]
229pub struct ChainConfig {
230    /// Current chain identifier
231    pub chain_id: u64,
232
233    /// Block numbers for the block where each fork was activated
234    /// (None = no fork, 0 = fork is already active)
235    pub homestead_block: Option<u64>,
236
237    pub dao_fork_block: Option<u64>,
238    /// Whether the node supports or opposes the DAO hard-fork
239    #[serde(default)]
240    pub dao_fork_support: bool,
241
242    pub eip150_block: Option<u64>,
243    pub eip155_block: Option<u64>,
244    pub eip158_block: Option<u64>,
245
246    pub byzantium_block: Option<u64>,
247    pub constantinople_block: Option<u64>,
248    pub petersburg_block: Option<u64>,
249    pub istanbul_block: Option<u64>,
250    pub muir_glacier_block: Option<u64>,
251    pub berlin_block: Option<u64>,
252    pub london_block: Option<u64>,
253    pub arrow_glacier_block: Option<u64>,
254    pub gray_glacier_block: Option<u64>,
255    pub merge_netsplit_block: Option<u64>,
256
257    /// Timestamp at which each fork was activated
258    /// (None = no fork, 0 = fork is already active)
259    pub shanghai_time: Option<u64>,
260    pub cancun_time: Option<u64>,
261    pub prague_time: Option<u64>,
262    pub verkle_time: Option<u64>,
263    pub osaka_time: Option<u64>,
264
265    pub bpo1_time: Option<u64>,
266    pub bpo2_time: Option<u64>,
267    pub bpo3_time: Option<u64>,
268    pub bpo4_time: Option<u64>,
269    pub bpo5_time: Option<u64>,
270    pub amsterdam_time: Option<u64>,
271
272    /// Amount of total difficulty reached by the network that triggers the consensus upgrade.
273    #[serde(default, with = "crate::serde_utils::u128::hex_str_opt")]
274    pub terminal_total_difficulty: Option<u128>,
275    /// Network has already passed the terminal total difficult
276    #[serde(default)]
277    pub terminal_total_difficulty_passed: bool,
278    #[serde(default)]
279    pub blob_schedule: BlobSchedule,
280    #[rkyv(with = rkyv_utils::H160Wrapper)]
281    // Deposits system contract address
282    pub deposit_contract_address: Address,
283
284    #[serde(default)]
285    pub enable_verkle_at_genesis: bool,
286}
287
288lazy_static::lazy_static! {
289    pub static ref NETWORK_NAMES: HashMap<u64, &'static str> = {
290        HashMap::from([
291            (1, "mainnet"),
292            (11155111, "sepolia"),
293            (560048, "hoodi"),
294            (9, "L1 local devnet"),
295            (65536999, "L2 local devnet"),
296        ])
297    };
298}
299
300#[repr(u8)]
301#[derive(Debug, PartialEq, Eq, PartialOrd, Default, Hash, Clone, Copy, Serialize, Deserialize)]
302pub enum Fork {
303    Frontier = 0,
304    FrontierThawing = 1,
305    Homestead = 2,
306    DaoFork = 3,
307    Tangerine = 4,
308    SpuriousDragon = 5,
309    Byzantium = 6,
310    Constantinople = 7,
311    Petersburg = 8,
312    Istanbul = 9,
313    MuirGlacier = 10,
314    Berlin = 11,
315    London = 12,
316    ArrowGlacier = 13,
317    GrayGlacier = 14,
318    Paris = 15,
319    Shanghai = 16,
320    #[default]
321    Cancun = 17,
322    Prague = 18,
323    Osaka = 19,
324    BPO1 = 20,
325    BPO2 = 21,
326    BPO3 = 22,
327    BPO4 = 23,
328    BPO5 = 24,
329    Amsterdam = 25,
330}
331
332impl From<Fork> for &str {
333    fn from(fork: Fork) -> Self {
334        match fork {
335            Fork::Frontier => "Frontier",
336            Fork::FrontierThawing => "FrontierThawing",
337            Fork::Homestead => "Homestead",
338            Fork::DaoFork => "DaoFork",
339            Fork::Tangerine => "Tangerine",
340            Fork::SpuriousDragon => "SpuriousDragon",
341            Fork::Byzantium => "Byzantium",
342            Fork::Constantinople => "Constantinople",
343            Fork::Petersburg => "Petersburg",
344            Fork::Istanbul => "Istanbul",
345            Fork::MuirGlacier => "MuirGlacier",
346            Fork::Berlin => "Berlin",
347            Fork::London => "London",
348            Fork::ArrowGlacier => "ArrowGlacier",
349            Fork::GrayGlacier => "GrayGlacier",
350            Fork::Paris => "Paris",
351            Fork::Shanghai => "Shanghai",
352            Fork::Cancun => "Cancun",
353            Fork::Prague => "Prague",
354            Fork::Osaka => "Osaka",
355            Fork::BPO1 => "BPO1",
356            Fork::BPO2 => "BPO2",
357            Fork::BPO3 => "BPO3",
358            Fork::BPO4 => "BPO4",
359            Fork::BPO5 => "BPO5",
360            Fork::Amsterdam => "Amsterdam",
361        }
362    }
363}
364
365impl ChainConfig {
366    pub fn is_amsterdam_activated(&self, block_timestamp: u64) -> bool {
367        self.amsterdam_time
368            .is_some_and(|time| time <= block_timestamp)
369    }
370
371    pub fn is_bpo5_activated(&self, block_timestamp: u64) -> bool {
372        self.bpo5_time.is_some_and(|time| time <= block_timestamp)
373    }
374
375    pub fn is_bpo4_activated(&self, block_timestamp: u64) -> bool {
376        self.bpo4_time.is_some_and(|time| time <= block_timestamp)
377    }
378
379    pub fn is_bpo3_activated(&self, block_timestamp: u64) -> bool {
380        self.bpo3_time.is_some_and(|time| time <= block_timestamp)
381    }
382
383    pub fn is_bpo2_activated(&self, block_timestamp: u64) -> bool {
384        self.bpo2_time.is_some_and(|time| time <= block_timestamp)
385    }
386
387    pub fn is_bpo1_activated(&self, block_timestamp: u64) -> bool {
388        self.bpo1_time.is_some_and(|time| time <= block_timestamp)
389    }
390
391    pub fn is_osaka_activated(&self, block_timestamp: u64) -> bool {
392        self.osaka_time.is_some_and(|time| time <= block_timestamp)
393    }
394
395    pub fn is_prague_activated(&self, block_timestamp: u64) -> bool {
396        self.prague_time.is_some_and(|time| time <= block_timestamp)
397    }
398
399    pub fn is_shanghai_activated(&self, block_timestamp: u64) -> bool {
400        self.shanghai_time
401            .is_some_and(|time| time <= block_timestamp)
402    }
403
404    pub fn is_cancun_activated(&self, block_timestamp: u64) -> bool {
405        self.cancun_time.is_some_and(|time| time <= block_timestamp)
406    }
407
408    pub fn is_istanbul_activated(&self, block_number: BlockNumber) -> bool {
409        self.istanbul_block.is_some_and(|num| num <= block_number)
410    }
411
412    pub fn is_london_activated(&self, block_number: BlockNumber) -> bool {
413        self.london_block.is_some_and(|num| num <= block_number)
414    }
415
416    pub fn is_eip155_activated(&self, block_number: BlockNumber) -> bool {
417        self.eip155_block.is_some_and(|num| num <= block_number)
418    }
419
420    pub fn display_config(&self) -> String {
421        let network = NETWORK_NAMES.get(&self.chain_id).unwrap_or(&"unknown");
422        let mut output = format!("Chain ID: {} ({})\n\n", self.chain_id, network);
423
424        let post_merge_forks = [
425            ("Shanghai", self.shanghai_time),
426            ("Cancun", self.cancun_time),
427            ("Prague", self.prague_time),
428            ("Verkle", self.verkle_time),
429            ("Osaka", self.osaka_time),
430            ("Amsterdam", self.amsterdam_time),
431        ];
432
433        let active_forks: Vec<_> = post_merge_forks
434            .iter()
435            .filter_map(|(name, t)| t.map(|time| format!("- {}: @{:<10}", name, time)))
436            .collect();
437
438        if !active_forks.is_empty() {
439            output.push_str("Network is post-merge\n\n");
440            output.push_str("Post-Merge hard forks (timestamp based):\n");
441            output.push_str(&active_forks.join("\n"));
442        } else {
443            output.push_str("Network is at Paris\n\n");
444        }
445
446        output
447    }
448
449    pub fn get_fork(&self, block_timestamp: u64) -> Fork {
450        if self.is_amsterdam_activated(block_timestamp) {
451            Fork::Amsterdam
452        } else if self.is_bpo5_activated(block_timestamp) {
453            Fork::BPO5
454        } else if self.is_bpo4_activated(block_timestamp) {
455            Fork::BPO4
456        } else if self.is_bpo3_activated(block_timestamp) {
457            Fork::BPO3
458        } else if self.is_bpo2_activated(block_timestamp) {
459            Fork::BPO2
460        } else if self.is_bpo1_activated(block_timestamp) {
461            Fork::BPO1
462        } else if self.is_osaka_activated(block_timestamp) {
463            Fork::Osaka
464        } else if self.is_prague_activated(block_timestamp) {
465            Fork::Prague
466        } else if self.is_cancun_activated(block_timestamp) {
467            Fork::Cancun
468        } else if self.is_shanghai_activated(block_timestamp) {
469            Fork::Shanghai
470        } else {
471            Fork::Paris
472        }
473    }
474
475    pub fn get_fork_blob_schedule(&self, block_timestamp: u64) -> Option<ForkBlobSchedule> {
476        // Amsterdam (and BPO3-5) don't independently define blob params in Hive;
477        // they inherit from the highest activated BPO fork. If the fork-specific
478        // entry is None, fall through to find the right BPO schedule.
479        if self.is_amsterdam_activated(block_timestamp)
480            && let Some(schedule) = self.blob_schedule.amsterdam
481        {
482            return Some(schedule);
483        }
484        // Fall through to BPO chain
485        if self.is_bpo5_activated(block_timestamp)
486            && let Some(schedule) = self.blob_schedule.bpo5
487        {
488            return Some(schedule);
489        }
490        if self.is_bpo4_activated(block_timestamp)
491            && let Some(schedule) = self.blob_schedule.bpo4
492        {
493            return Some(schedule);
494        }
495        if self.is_bpo3_activated(block_timestamp)
496            && let Some(schedule) = self.blob_schedule.bpo3
497        {
498            return Some(schedule);
499        }
500        // Amsterdam implies BPO2 blob params when no explicit schedule is set.
501        if self.is_bpo2_activated(block_timestamp) || self.is_amsterdam_activated(block_timestamp) {
502            Some(self.blob_schedule.bpo2)
503        } else if self.is_bpo1_activated(block_timestamp) {
504            Some(self.blob_schedule.bpo1)
505        } else if self.is_osaka_activated(block_timestamp) {
506            Some(self.blob_schedule.osaka)
507        } else if self.is_prague_activated(block_timestamp) {
508            Some(self.blob_schedule.prague)
509        } else if self.is_cancun_activated(block_timestamp) {
510            Some(self.blob_schedule.cancun)
511        } else {
512            None
513        }
514    }
515
516    pub fn fork(&self, block_timestamp: u64) -> Fork {
517        self.get_fork(block_timestamp)
518    }
519
520    pub fn next_fork(&self, block_timestamp: u64) -> Option<Fork> {
521        // Pick the scheduled fork with the smallest activation timestamp strictly
522        // greater than `block_timestamp`. Iterating all timestamp-based forks avoids
523        // bugs when intermediate forks (e.g. BPOs) are skipped in a network's schedule.
524        //
525        // NOTE: every timestamp-based fork MUST appear here in chronological order.
526        // Omitting a fork will silently cause `next_fork` to skip it; ties are
527        // broken by array position, so the order also encodes the canonical
528        // schedule independent of the `Fork` enum's discriminants.
529        [
530            Fork::Shanghai,
531            Fork::Cancun,
532            Fork::Prague,
533            Fork::Osaka,
534            Fork::BPO1,
535            Fork::BPO2,
536            Fork::BPO3,
537            Fork::BPO4,
538            Fork::BPO5,
539            Fork::Amsterdam,
540        ]
541        .into_iter()
542        .enumerate()
543        .filter_map(|(pos, fork)| {
544            self.get_activation_timestamp_for_fork(fork)
545                .filter(|&t| t > block_timestamp)
546                .map(|t| (fork, t, pos))
547        })
548        .min_by(|a, b| a.1.cmp(&b.1).then_with(|| a.2.cmp(&b.2)))
549        .map(|(fork, _, _)| fork)
550    }
551
552    pub fn get_last_scheduled_fork(&self) -> Fork {
553        if self.amsterdam_time.is_some() {
554            Fork::Amsterdam
555        } else if self.bpo5_time.is_some() {
556            Fork::BPO5
557        } else if self.bpo4_time.is_some() {
558            Fork::BPO4
559        } else if self.bpo3_time.is_some() {
560            Fork::BPO3
561        } else if self.bpo2_time.is_some() {
562            Fork::BPO2
563        } else if self.bpo1_time.is_some() {
564            Fork::BPO1
565        } else if self.osaka_time.is_some() {
566            Fork::Osaka
567        } else if self.prague_time.is_some() {
568            Fork::Prague
569        } else if self.cancun_time.is_some() {
570            Fork::Cancun
571        } else {
572            Fork::Paris
573        }
574    }
575
576    pub fn get_activation_timestamp_for_fork(&self, fork: Fork) -> Option<u64> {
577        match fork {
578            Fork::Cancun => self.cancun_time,
579            Fork::Prague => self.prague_time,
580            Fork::Osaka => self.osaka_time,
581            Fork::BPO1 => self.bpo1_time,
582            Fork::BPO2 => self.bpo2_time,
583            Fork::BPO3 => self.bpo3_time,
584            Fork::BPO4 => self.bpo4_time,
585            Fork::BPO5 => self.bpo5_time,
586            Fork::Amsterdam => self.amsterdam_time,
587            Fork::Homestead => self.homestead_block,
588            Fork::DaoFork => self.dao_fork_block,
589            Fork::Byzantium => self.byzantium_block,
590            Fork::Constantinople => self.constantinople_block,
591            Fork::Petersburg => self.petersburg_block,
592            Fork::Istanbul => self.istanbul_block,
593            Fork::MuirGlacier => self.muir_glacier_block,
594            Fork::Berlin => self.berlin_block,
595            Fork::London => self.london_block,
596            Fork::ArrowGlacier => self.arrow_glacier_block,
597            Fork::GrayGlacier => self.gray_glacier_block,
598            Fork::Paris => self.merge_netsplit_block,
599            Fork::Shanghai => self.shanghai_time,
600            _ => None,
601        }
602    }
603
604    pub fn get_blob_schedule_for_fork(&self, fork: Fork) -> Option<ForkBlobSchedule> {
605        match fork {
606            Fork::Cancun => Some(self.blob_schedule.cancun),
607            Fork::Prague => Some(self.blob_schedule.prague),
608            Fork::Osaka => Some(self.blob_schedule.osaka),
609            Fork::BPO1 => Some(self.blob_schedule.bpo1),
610            Fork::BPO2 => Some(self.blob_schedule.bpo2),
611            Fork::BPO3 => self.blob_schedule.bpo3,
612            Fork::BPO4 => self.blob_schedule.bpo4,
613            Fork::BPO5 => self.blob_schedule.bpo5,
614            Fork::Amsterdam => self.blob_schedule.amsterdam,
615            _ => None,
616        }
617    }
618
619    pub fn gather_forks(&self, genesis_header: BlockHeader) -> (Vec<u64>, Vec<u64>) {
620        let mut block_number_based_forks: Vec<u64> = vec![
621            self.homestead_block,
622            if self.dao_fork_support {
623                self.dao_fork_block
624            } else {
625                None
626            },
627            self.eip150_block,
628            self.eip155_block,
629            self.eip158_block,
630            self.byzantium_block,
631            self.constantinople_block,
632            self.petersburg_block,
633            self.istanbul_block,
634            self.muir_glacier_block,
635            self.berlin_block,
636            self.london_block,
637            self.arrow_glacier_block,
638            self.gray_glacier_block,
639            self.merge_netsplit_block,
640        ]
641        .into_iter()
642        .flatten()
643        .collect();
644
645        // Remove repeated values
646        block_number_based_forks.sort();
647        block_number_based_forks.dedup();
648
649        let mut timestamp_based_forks: Vec<u64> = vec![
650            self.shanghai_time,
651            self.cancun_time,
652            self.prague_time,
653            self.osaka_time,
654            self.bpo1_time,
655            self.bpo2_time,
656            self.bpo3_time,
657            self.bpo4_time,
658            self.bpo5_time,
659            self.amsterdam_time,
660            self.verkle_time,
661        ]
662        .into_iter()
663        .flatten()
664        .collect();
665
666        // Remove repeated values
667        timestamp_based_forks.sort();
668        timestamp_based_forks.dedup();
669
670        // Filter forks before genesis
671        block_number_based_forks.retain(|block_number| *block_number != 0);
672        timestamp_based_forks.retain(|block_timestamp| *block_timestamp > genesis_header.timestamp);
673
674        (block_number_based_forks, timestamp_based_forks)
675    }
676}
677
678#[allow(unused)]
679#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
680pub struct GenesisAccount {
681    #[serde(default, with = "crate::serde_utils::bytes")]
682    pub code: Bytes,
683    #[serde(default)]
684    pub storage: BTreeMap<U256, U256>,
685    #[serde(deserialize_with = "crate::serde_utils::u256::deser_hex_or_dec_str")]
686    pub balance: U256,
687    #[serde(default, with = "crate::serde_utils::u64::hex_str")]
688    pub nonce: u64,
689}
690
691impl Genesis {
692    pub fn get_block(&self) -> Block {
693        Block::new(self.get_block_header(), self.get_block_body())
694    }
695
696    fn get_block_header(&self) -> BlockHeader {
697        let mut blob_gas_used: Option<u64> = None;
698        let mut excess_blob_gas: Option<u64> = None;
699
700        if let Some(cancun_time) = self.config.cancun_time
701            && cancun_time <= self.timestamp
702        {
703            blob_gas_used = Some(self.blob_gas_used.unwrap_or(0));
704            excess_blob_gas = Some(self.excess_blob_gas.unwrap_or(0));
705        }
706        let base_fee_per_gas = self.base_fee_per_gas.or_else(|| {
707            self.config
708                .is_london_activated(0)
709                .then_some(INITIAL_BASE_FEE)
710        });
711
712        let withdrawals_root = self
713            .config
714            .is_shanghai_activated(self.timestamp)
715            .then_some(compute_withdrawals_root(&[], &NativeCrypto));
716
717        let parent_beacon_block_root = self
718            .config
719            .is_cancun_activated(self.timestamp)
720            .then_some(H256::zero());
721
722        let requests_hash = self
723            .config
724            .is_prague_activated(self.timestamp)
725            .then_some(self.requests_hash.unwrap_or(*DEFAULT_REQUESTS_HASH));
726
727        let block_access_list_hash = self
728            .config
729            .is_amsterdam_activated(self.timestamp)
730            .then_some(
731                self.block_access_list_hash
732                    .unwrap_or(*EMPTY_BLOCK_ACCESS_LIST_HASH),
733            );
734
735        let slot_number = self
736            .config
737            .is_amsterdam_activated(self.timestamp)
738            .then_some(self.slot_number.unwrap_or(0));
739
740        BlockHeader {
741            parent_hash: H256::zero(),
742            ommers_hash: *DEFAULT_OMMERS_HASH,
743            coinbase: self.coinbase,
744            state_root: self.compute_state_root(),
745            transactions_root: compute_transactions_root(&[], &NativeCrypto),
746            receipts_root: compute_receipts_root(&[], &NativeCrypto),
747            logs_bloom: Bloom::zero(),
748            difficulty: self.difficulty,
749            number: 0,
750            gas_limit: self.gas_limit,
751            gas_used: 0,
752            timestamp: self.timestamp,
753            extra_data: self.extra_data.clone(),
754            prev_randao: self.mix_hash,
755            nonce: self.nonce,
756            base_fee_per_gas,
757            withdrawals_root,
758            blob_gas_used,
759            excess_blob_gas,
760            parent_beacon_block_root,
761            requests_hash,
762            block_access_list_hash,
763            slot_number,
764            ..Default::default()
765        }
766    }
767
768    fn get_block_body(&self) -> BlockBody {
769        BlockBody {
770            transactions: vec![],
771            ommers: vec![],
772            withdrawals: Some(vec![]),
773        }
774    }
775
776    pub fn compute_state_root(&self) -> H256 {
777        let iter = self.alloc.iter().map(|(addr, account)| {
778            (
779                keccak_hash(addr).to_vec(),
780                AccountState::from(account).encode_to_vec(),
781            )
782        });
783        Trie::compute_hash_from_unsorted_iter(iter, &NativeCrypto)
784    }
785}
786#[cfg(test)]
787mod tests {
788    use std::str::FromStr;
789    use std::{fs::File, io::BufReader};
790
791    use ethereum_types::H160;
792
793    use crate::types::INITIAL_BASE_FEE;
794
795    use super::*;
796
797    #[test]
798    fn terminal_total_difficulty_accepts_number_or_hex_string() {
799        // geth/reth-style genesis files encode terminalTotalDifficulty as a
800        // bare decimal number that exceeds u64::MAX; ethrex must accept it as
801        // well as the 0x-hex-string form.
802        let dca = r#""depositContractAddress":"0x00000000219ab540356cbb839cbe05303d7705fa""#;
803
804        let from_number: ChainConfig = serde_json::from_str(&format!(
805            r#"{{"chainId":1,"terminalTotalDifficulty":58750000000000000000000,{dca}}}"#
806        ))
807        .expect("number-encoded TTD should parse");
808        // f64 cast is lossy above u64::MAX; assert the known, stable
809        // approximation so a regression to Some(0) can't silently pass.
810        assert_eq!(
811            from_number.terminal_total_difficulty,
812            Some(58749999999999996329984u128)
813        );
814
815        let from_hex: ChainConfig = serde_json::from_str(&format!(
816            r#"{{"chainId":1,"terminalTotalDifficulty":"0xc70d808a128d7380000",{dca}}}"#
817        ))
818        .expect("hex-string TTD should parse");
819        assert_eq!(
820            from_hex.terminal_total_difficulty,
821            Some(58750000000000000000000u128)
822        );
823
824        let small: ChainConfig = serde_json::from_str(&format!(
825            r#"{{"chainId":1,"terminalTotalDifficulty":17000000000000000,{dca}}}"#
826        ))
827        .expect("small number TTD should parse");
828        assert_eq!(small.terminal_total_difficulty, Some(17000000000000000u128));
829
830        // A negative bare number must error, not silently saturate to Some(0).
831        let negative = serde_json::from_str::<ChainConfig>(&format!(
832            r#"{{"chainId":1,"terminalTotalDifficulty":-1,{dca}}}"#
833        ));
834        let err = negative.expect_err("negative TTD must be rejected");
835        assert!(
836            err.to_string().contains("finite, non-negative"),
837            "error should name the sign/finiteness cause, got: {err}"
838        );
839    }
840
841    #[test]
842    fn pow_genesis_detection() {
843        // Default genesis: difficulty 0 (post-merge at genesis) -> supported.
844        let mut g = Genesis::default();
845        assert!(!is_unsupported_pow_genesis(&g));
846
847        // PoW genesis: non-zero difficulty, no merge configured -> unsupported.
848        g.difficulty = U256::from(0x4000_0000u64);
849        assert!(is_unsupported_pow_genesis(&g));
850
851        // Mainnet-style: non-zero difficulty but TTD configured -> supported.
852        g.config.terminal_total_difficulty = Some(58750000000000000000000);
853        assert!(!is_unsupported_pow_genesis(&g));
854
855        // Merge-netsplit block configured (no TTD) -> supported.
856        g.config.terminal_total_difficulty = None;
857        g.config.merge_netsplit_block = Some(15537394);
858        assert!(!is_unsupported_pow_genesis(&g));
859
860        // TTD = Some(0) sentinel (PoS from genesis), non-zero difficulty -> supported.
861        g.config.merge_netsplit_block = None;
862        g.config.terminal_total_difficulty = Some(0);
863        assert!(!is_unsupported_pow_genesis(&g));
864
865        // terminal_total_difficulty_passed with no TTD value (post-merge snapshot
866        // that dropped the redundant field), non-zero difficulty -> supported.
867        g.config.terminal_total_difficulty = None;
868        g.config.terminal_total_difficulty_passed = true;
869        assert!(!is_unsupported_pow_genesis(&g));
870    }
871
872    #[test]
873    fn deserialize_genesis_file() {
874        // Deserialize genesis file
875        let file = File::open("../../fixtures/genesis/kurtosis.json")
876            .expect("Failed to open genesis file");
877        let reader = BufReader::new(file);
878        let genesis: Genesis =
879            serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
880        // Check Genesis fields
881        // Chain config
882        let expected_chain_config = ChainConfig {
883            chain_id: 3151908_u64,
884            homestead_block: Some(0),
885            eip150_block: Some(0),
886            eip155_block: Some(0),
887            eip158_block: Some(0),
888            byzantium_block: Some(0),
889            constantinople_block: Some(0),
890            petersburg_block: Some(0),
891            istanbul_block: Some(0),
892            berlin_block: Some(0),
893            london_block: Some(0),
894            merge_netsplit_block: Some(0),
895            shanghai_time: Some(0),
896            cancun_time: Some(0),
897            prague_time: Some(1718232101),
898            terminal_total_difficulty: Some(0),
899            terminal_total_difficulty_passed: true,
900            deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
901                .unwrap(),
902            // Note this BlobSchedule config is not the default
903            blob_schedule: BlobSchedule {
904                cancun: ForkBlobSchedule {
905                    target: 2,
906                    max: 3,
907                    base_fee_update_fraction: 6676954,
908                },
909                prague: ForkBlobSchedule {
910                    target: 3,
911                    max: 4,
912                    base_fee_update_fraction: 13353908,
913                },
914                ..Default::default()
915            },
916            ..Default::default()
917        };
918        assert_eq!(&genesis.config, &expected_chain_config);
919        // Genesis header fields
920        assert_eq!(genesis.coinbase, Address::from([0; 20]));
921        assert_eq!(genesis.difficulty, U256::from(1));
922        assert!(genesis.extra_data.is_empty());
923        assert_eq!(genesis.gas_limit, 0x17d7840);
924        assert_eq!(genesis.nonce, 0x1234);
925        assert_eq!(genesis.mix_hash, H256::from([0; 32]));
926        assert_eq!(genesis.timestamp, 1718040081);
927        // Check alloc field
928        // We will only check a couple of the hashmap's values as it is quite large
929        let addr_a = Address::from_str("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap();
930        assert!(genesis.alloc.contains_key(&addr_a));
931        let expected_account_a = GenesisAccount {
932        code: Bytes::from(hex::decode("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500").unwrap()),
933        balance: 0.into(),
934        nonce: 1,
935        storage: Default::default(),
936    };
937        assert_eq!(genesis.alloc[&addr_a], expected_account_a);
938        // Check some storage values from another account
939        let addr_b = Address::from_str("0x4242424242424242424242424242424242424242").unwrap();
940        assert!(genesis.alloc.contains_key(&addr_b));
941        let addr_b_storage = &genesis.alloc[&addr_b].storage;
942        assert_eq!(
943            addr_b_storage.get(
944                &U256::from_str(
945                    "0x0000000000000000000000000000000000000000000000000000000000000022"
946                )
947                .unwrap()
948            ),
949            Some(
950                &U256::from_str(
951                    "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
952                )
953                .unwrap()
954            )
955        );
956        assert_eq!(
957            addr_b_storage.get(
958                &U256::from_str(
959                    "0x0000000000000000000000000000000000000000000000000000000000000038"
960                )
961                .unwrap()
962            ),
963            Some(
964                &U256::from_str(
965                    "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7"
966                )
967                .unwrap()
968            )
969        );
970    }
971
972    #[test]
973    fn genesis_block() {
974        // Deserialize genesis file
975        let file = File::open("../../fixtures/genesis/kurtosis.json")
976            .expect("Failed to open genesis file");
977        let reader = BufReader::new(file);
978        let genesis: Genesis =
979            serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
980        let genesis_block = genesis.get_block();
981        let header = genesis_block.header;
982        let body = genesis_block.body;
983        assert_eq!(header.parent_hash, H256::from([0; 32]));
984        assert_eq!(header.ommers_hash, *DEFAULT_OMMERS_HASH);
985        assert_eq!(header.coinbase, Address::default());
986        assert_eq!(
987            header.state_root,
988            H256::from_str("0x2dab6a1d6d638955507777aecea699e6728825524facbd446bd4e86d44fa5ecd")
989                .unwrap()
990        );
991        assert_eq!(
992            header.transactions_root,
993            compute_transactions_root(&[], &NativeCrypto)
994        );
995        assert_eq!(
996            header.receipts_root,
997            compute_receipts_root(&[], &NativeCrypto)
998        );
999        assert_eq!(header.logs_bloom, Bloom::default());
1000        assert_eq!(header.difficulty, U256::from(1));
1001        assert_eq!(header.gas_limit, 25_000_000);
1002        assert_eq!(header.gas_used, 0);
1003        assert_eq!(header.timestamp, 1_718_040_081);
1004        assert_eq!(header.extra_data, Bytes::default());
1005        assert_eq!(header.prev_randao, H256::from([0; 32]));
1006        assert_eq!(header.nonce, 4660);
1007        assert_eq!(
1008            header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE),
1009            INITIAL_BASE_FEE
1010        );
1011        assert_eq!(
1012            header.withdrawals_root,
1013            Some(compute_withdrawals_root(&[], &NativeCrypto))
1014        );
1015        assert_eq!(header.blob_gas_used, Some(0));
1016        assert_eq!(header.excess_blob_gas, Some(0));
1017        assert_eq!(header.parent_beacon_block_root, Some(H256::zero()));
1018        assert!(body.transactions.is_empty());
1019        assert!(body.ommers.is_empty());
1020        assert!(body.withdrawals.is_some_and(|w| w.is_empty()));
1021    }
1022
1023    #[test]
1024    // Parses genesis received by kurtosis and checks that the hash matches the next block's parent hash
1025    fn read_and_compute_kurtosis_hash() {
1026        let file = File::open("../../fixtures/genesis/kurtosis.json")
1027            .expect("Failed to open genesis file");
1028        let reader = BufReader::new(file);
1029        let genesis: Genesis =
1030            serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1031        let genesis_block_hash = genesis.get_block().hash();
1032        assert_eq!(
1033            genesis_block_hash,
1034            H256::from_str("0xcb5306dd861d0f2c1f9952fbfbc75a46d0b6ce4f37bea370c3471fe8410bf40b")
1035                .unwrap()
1036        )
1037    }
1038
1039    #[test]
1040    fn parse_hive_genesis_file() {
1041        let file =
1042            File::open("../../fixtures/genesis/hive.json").expect("Failed to open genesis file");
1043        let reader = BufReader::new(file);
1044        let _genesis: Genesis =
1045            serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1046    }
1047
1048    #[test]
1049    fn read_and_compute_hive_hash() {
1050        let file =
1051            File::open("../../fixtures/genesis/hive.json").expect("Failed to open genesis file");
1052        let reader = BufReader::new(file);
1053        let genesis: Genesis =
1054            serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1055        let computed_block_hash = genesis.get_block().hash();
1056        let genesis_block_hash =
1057            H256::from_str("0x30f516e34fc173bb5fc4daddcc7532c4aca10b702c7228f3c806b4df2646fb7e")
1058                .unwrap();
1059        assert_eq!(genesis_block_hash, computed_block_hash)
1060    }
1061
1062    #[test]
1063    fn deserialize_chain_config_blob_schedule() {
1064        let json = r#"
1065
1066            {
1067                "chainId": 123,
1068                "blobSchedule": {
1069                  "cancun": {
1070                    "target": 1,
1071                    "max": 2,
1072                    "baseFeeUpdateFraction": 10000
1073                  },
1074                  "prague": {
1075                    "target": 3,
1076                    "max": 4,
1077                    "baseFeeUpdateFraction": 20000
1078                  }
1079                },
1080                "depositContractAddress": "0x4242424242424242424242424242424242424242"
1081            }
1082            "#;
1083
1084        let config: ChainConfig =
1085            serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1086        let expected_chain_config = ChainConfig {
1087            chain_id: 123,
1088            blob_schedule: BlobSchedule {
1089                cancun: ForkBlobSchedule {
1090                    target: 1,
1091                    max: 2,
1092                    base_fee_update_fraction: 10000,
1093                },
1094                prague: ForkBlobSchedule {
1095                    target: 3,
1096                    max: 4,
1097                    base_fee_update_fraction: 20000,
1098                },
1099                ..Default::default()
1100            },
1101            deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1102                .unwrap(),
1103            ..Default::default()
1104        };
1105        assert_eq!(&config, &expected_chain_config);
1106    }
1107
1108    #[test]
1109    fn deserialize_chain_config_missing_entire_blob_schedule() {
1110        let json = r#"
1111            {
1112                "chainId": 123,
1113                "depositContractAddress": "0x4242424242424242424242424242424242424242"
1114            }
1115            "#;
1116
1117        let config: ChainConfig =
1118            serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1119        let expected_chain_config = ChainConfig {
1120            chain_id: 123,
1121            blob_schedule: BlobSchedule {
1122                cancun: ForkBlobSchedule {
1123                    target: 3,
1124                    max: 6,
1125                    base_fee_update_fraction: 3338477,
1126                },
1127                prague: ForkBlobSchedule {
1128                    target: 6,
1129                    max: 9,
1130                    base_fee_update_fraction: 5007716,
1131                },
1132                ..Default::default()
1133            },
1134            deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1135                .unwrap(),
1136            ..Default::default()
1137        };
1138        assert_eq!(&config, &expected_chain_config);
1139    }
1140
1141    #[test]
1142    fn deserialize_chain_config_missing_cancun_blob_schedule() {
1143        let json = r#"
1144            {
1145                "chainId": 123,
1146                "blobSchedule": {
1147                    "prague": {
1148                      "target": 3,
1149                      "max": 4,
1150                      "baseFeeUpdateFraction": 20000
1151                    }
1152                },
1153                "depositContractAddress": "0x4242424242424242424242424242424242424242"
1154            }
1155            "#;
1156
1157        let config: ChainConfig =
1158            serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1159        let expected_chain_config = ChainConfig {
1160            chain_id: 123,
1161            blob_schedule: BlobSchedule {
1162                cancun: ForkBlobSchedule {
1163                    target: 3,
1164                    max: 6,
1165                    base_fee_update_fraction: 3338477,
1166                },
1167                prague: ForkBlobSchedule {
1168                    target: 3,
1169                    max: 4,
1170                    base_fee_update_fraction: 20000,
1171                },
1172                ..Default::default()
1173            },
1174            deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1175                .unwrap(),
1176            ..Default::default()
1177        };
1178        assert_eq!(&config, &expected_chain_config);
1179    }
1180
1181    #[test]
1182    fn deserialize_chain_config_missing_prague_blob_schedule() {
1183        let json = r#"
1184            {
1185                "chainId": 123,
1186                "blobSchedule": {
1187                  "cancun": {
1188                    "target": 1,
1189                    "max": 2,
1190                    "baseFeeUpdateFraction": 10000
1191                  }
1192                },
1193                "depositContractAddress": "0x4242424242424242424242424242424242424242"
1194            }
1195            "#;
1196
1197        let config: ChainConfig =
1198            serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1199        let expected_chain_config = ChainConfig {
1200            chain_id: 123,
1201            blob_schedule: BlobSchedule {
1202                cancun: ForkBlobSchedule {
1203                    target: 1,
1204                    max: 2,
1205                    base_fee_update_fraction: 10000,
1206                },
1207                prague: ForkBlobSchedule {
1208                    target: 6,
1209                    max: 9,
1210                    base_fee_update_fraction: 5007716,
1211                },
1212                ..Default::default()
1213            },
1214            deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1215                .unwrap(),
1216            ..Default::default()
1217        };
1218        assert_eq!(&config, &expected_chain_config);
1219    }
1220
1221    #[test]
1222    fn deserialize_chain_config_missing_deposit_contract_address() {
1223        let json = r#"
1224            {
1225                "chainId": 123
1226            }
1227            "#;
1228
1229        let result: Result<ChainConfig, _> = serde_json::from_str(json);
1230
1231        assert!(result.is_err());
1232
1233        let error_message = result.unwrap_err().to_string();
1234        assert!(error_message.contains("missing field `depositContractAddress`"),);
1235    }
1236
1237    #[test]
1238    fn next_fork_skips_unscheduled_intermediate_forks() {
1239        // bal-devnet-7 layout: every post-merge fork up to Osaka at t=0, Amsterdam
1240        // scheduled later, no BPOs scheduled. `next_fork` must return Amsterdam
1241        // even though the BPO chain between Osaka and Amsterdam is empty.
1242        let config = ChainConfig {
1243            shanghai_time: Some(0),
1244            cancun_time: Some(0),
1245            prague_time: Some(0),
1246            osaka_time: Some(0),
1247            amsterdam_time: Some(1_779_098_127),
1248            ..Default::default()
1249        };
1250        assert_eq!(config.next_fork(0), Some(Fork::Amsterdam));
1251        assert_eq!(config.next_fork(1_779_098_126), Some(Fork::Amsterdam));
1252        assert_eq!(config.next_fork(1_779_098_127), None);
1253    }
1254
1255    #[test]
1256    fn next_fork_picks_earliest_scheduled() {
1257        // Contiguous schedule: at Cancun, next should be Prague (not Osaka).
1258        let config = ChainConfig {
1259            shanghai_time: Some(0),
1260            cancun_time: Some(100),
1261            prague_time: Some(200),
1262            osaka_time: Some(300),
1263            ..Default::default()
1264        };
1265        assert_eq!(config.next_fork(150), Some(Fork::Prague));
1266        assert_eq!(config.next_fork(250), Some(Fork::Osaka));
1267        assert_eq!(config.next_fork(300), None);
1268    }
1269
1270    #[test]
1271    fn next_fork_returns_none_when_at_last_scheduled() {
1272        let config = ChainConfig {
1273            shanghai_time: Some(0),
1274            cancun_time: Some(0),
1275            ..Default::default()
1276        };
1277        assert_eq!(config.next_fork(0), None);
1278    }
1279}