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 pub config: ChainConfig,
30 pub alloc: BTreeMap<Address, GenesisAccount>,
33 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 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 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
94fn 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#[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 pub chain_id: u64,
232
233 pub homestead_block: Option<u64>,
236
237 pub dao_fork_block: Option<u64>,
238 #[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 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 #[serde(
278 alias = "hezeTime",
279 alias = "heze_time",
280 alias = "bogotaTime",
281 alias = "bogota_time"
282 )]
283 pub hegota_time: Option<u64>,
284 pub lstar_time: Option<u64>,
285
286 #[serde(default, with = "crate::serde_utils::u128::hex_str_opt")]
288 pub terminal_total_difficulty: Option<u128>,
289 #[serde(default)]
291 pub terminal_total_difficulty_passed: bool,
292 #[serde(default)]
293 pub blob_schedule: BlobSchedule,
294 #[rkyv(with = rkyv_utils::H160Wrapper)]
295 pub deposit_contract_address: Address,
297
298 #[serde(default)]
299 pub enable_verkle_at_genesis: bool,
300}
301
302lazy_static::lazy_static! {
303 pub static ref NETWORK_NAMES: HashMap<u64, &'static str> = {
304 HashMap::from([
305 (1, "mainnet"),
306 (11155111, "sepolia"),
307 (560048, "hoodi"),
308 (7091047534, "plataberget"),
309 (9, "L1 local devnet"),
310 (65536999, "L2 local devnet"),
311 ])
312 };
313}
314
315#[repr(u8)]
316#[derive(Debug, PartialEq, Eq, PartialOrd, Default, Hash, Clone, Copy, Serialize, Deserialize)]
317pub enum Fork {
318 Frontier = 0,
319 FrontierThawing = 1,
320 Homestead = 2,
321 DaoFork = 3,
322 Tangerine = 4,
323 SpuriousDragon = 5,
324 Byzantium = 6,
325 Constantinople = 7,
326 Petersburg = 8,
327 Istanbul = 9,
328 MuirGlacier = 10,
329 Berlin = 11,
330 London = 12,
331 ArrowGlacier = 13,
332 GrayGlacier = 14,
333 Paris = 15,
334 Shanghai = 16,
335 #[default]
336 Cancun = 17,
337 Prague = 18,
338 Osaka = 19,
339 BPO1 = 20,
340 BPO2 = 21,
341 BPO3 = 22,
342 BPO4 = 23,
343 BPO5 = 24,
344 Amsterdam = 25,
345 Hegota = 26,
346 LStar = 27,
347}
348
349impl From<Fork> for &str {
350 fn from(fork: Fork) -> Self {
351 match fork {
352 Fork::Frontier => "Frontier",
353 Fork::FrontierThawing => "FrontierThawing",
354 Fork::Homestead => "Homestead",
355 Fork::DaoFork => "DaoFork",
356 Fork::Tangerine => "Tangerine",
357 Fork::SpuriousDragon => "SpuriousDragon",
358 Fork::Byzantium => "Byzantium",
359 Fork::Constantinople => "Constantinople",
360 Fork::Petersburg => "Petersburg",
361 Fork::Istanbul => "Istanbul",
362 Fork::MuirGlacier => "MuirGlacier",
363 Fork::Berlin => "Berlin",
364 Fork::London => "London",
365 Fork::ArrowGlacier => "ArrowGlacier",
366 Fork::GrayGlacier => "GrayGlacier",
367 Fork::Paris => "Paris",
368 Fork::Shanghai => "Shanghai",
369 Fork::Cancun => "Cancun",
370 Fork::Prague => "Prague",
371 Fork::Osaka => "Osaka",
372 Fork::BPO1 => "BPO1",
373 Fork::BPO2 => "BPO2",
374 Fork::BPO3 => "BPO3",
375 Fork::BPO4 => "BPO4",
376 Fork::BPO5 => "BPO5",
377 Fork::Amsterdam => "Amsterdam",
378 Fork::Hegota => "Hegota",
379 Fork::LStar => "LStar",
380 }
381 }
382}
383
384impl ChainConfig {
385 pub fn is_hegota_activated(&self, block_timestamp: u64) -> bool {
386 self.hegota_time.is_some_and(|time| time <= block_timestamp)
387 }
388
389 pub fn is_lstar_activated(&self, block_timestamp: u64) -> bool {
390 self.lstar_time.is_some_and(|time| time <= block_timestamp)
391 }
392
393 pub fn is_amsterdam_activated(&self, block_timestamp: u64) -> bool {
394 self.amsterdam_time
395 .is_some_and(|time| time <= block_timestamp)
396 }
397
398 pub fn is_bpo5_activated(&self, block_timestamp: u64) -> bool {
399 self.bpo5_time.is_some_and(|time| time <= block_timestamp)
400 }
401
402 pub fn is_bpo4_activated(&self, block_timestamp: u64) -> bool {
403 self.bpo4_time.is_some_and(|time| time <= block_timestamp)
404 }
405
406 pub fn is_bpo3_activated(&self, block_timestamp: u64) -> bool {
407 self.bpo3_time.is_some_and(|time| time <= block_timestamp)
408 }
409
410 pub fn is_bpo2_activated(&self, block_timestamp: u64) -> bool {
411 self.bpo2_time.is_some_and(|time| time <= block_timestamp)
412 }
413
414 pub fn is_bpo1_activated(&self, block_timestamp: u64) -> bool {
415 self.bpo1_time.is_some_and(|time| time <= block_timestamp)
416 }
417
418 pub fn is_osaka_activated(&self, block_timestamp: u64) -> bool {
419 self.osaka_time.is_some_and(|time| time <= block_timestamp)
420 }
421
422 pub fn is_prague_activated(&self, block_timestamp: u64) -> bool {
423 self.prague_time.is_some_and(|time| time <= block_timestamp)
424 }
425
426 pub fn is_shanghai_activated(&self, block_timestamp: u64) -> bool {
427 self.shanghai_time
428 .is_some_and(|time| time <= block_timestamp)
429 }
430
431 pub fn is_cancun_activated(&self, block_timestamp: u64) -> bool {
432 self.cancun_time.is_some_and(|time| time <= block_timestamp)
433 }
434
435 pub fn is_istanbul_activated(&self, block_number: BlockNumber) -> bool {
436 self.istanbul_block.is_some_and(|num| num <= block_number)
437 }
438
439 pub fn is_london_activated(&self, block_number: BlockNumber) -> bool {
440 self.london_block.is_some_and(|num| num <= block_number)
441 }
442
443 pub fn is_eip155_activated(&self, block_number: BlockNumber) -> bool {
444 self.eip155_block.is_some_and(|num| num <= block_number)
445 }
446
447 pub fn display_config(&self) -> String {
448 let network = NETWORK_NAMES.get(&self.chain_id).unwrap_or(&"unknown");
449 let mut output = format!("Chain ID: {} ({})\n\n", self.chain_id, network);
450
451 let post_merge_forks = [
452 ("Shanghai", self.shanghai_time),
453 ("Cancun", self.cancun_time),
454 ("Prague", self.prague_time),
455 ("Verkle", self.verkle_time),
456 ("Osaka", self.osaka_time),
457 ("Amsterdam", self.amsterdam_time),
458 ("Hegota", self.hegota_time),
459 ];
460
461 let active_forks: Vec<_> = post_merge_forks
462 .iter()
463 .filter_map(|(name, t)| t.map(|time| format!("- {}: @{:<10}", name, time)))
464 .collect();
465
466 if !active_forks.is_empty() {
467 output.push_str("Network is post-merge\n\n");
468 output.push_str("Post-Merge hard forks (timestamp based):\n");
469 output.push_str(&active_forks.join("\n"));
470 } else {
471 output.push_str("Network is at Paris\n\n");
472 }
473
474 output
475 }
476
477 pub fn get_fork(&self, block_timestamp: u64) -> Fork {
478 if self.is_lstar_activated(block_timestamp) {
479 Fork::LStar
480 } else if self.is_hegota_activated(block_timestamp) {
481 Fork::Hegota
482 } else if self.is_amsterdam_activated(block_timestamp) {
483 Fork::Amsterdam
484 } else if self.is_bpo5_activated(block_timestamp) {
485 Fork::BPO5
486 } else if self.is_bpo4_activated(block_timestamp) {
487 Fork::BPO4
488 } else if self.is_bpo3_activated(block_timestamp) {
489 Fork::BPO3
490 } else if self.is_bpo2_activated(block_timestamp) {
491 Fork::BPO2
492 } else if self.is_bpo1_activated(block_timestamp) {
493 Fork::BPO1
494 } else if self.is_osaka_activated(block_timestamp) {
495 Fork::Osaka
496 } else if self.is_prague_activated(block_timestamp) {
497 Fork::Prague
498 } else if self.is_cancun_activated(block_timestamp) {
499 Fork::Cancun
500 } else if self.is_shanghai_activated(block_timestamp) {
501 Fork::Shanghai
502 } else {
503 Fork::Paris
504 }
505 }
506
507 pub fn get_fork_blob_schedule(&self, block_timestamp: u64) -> Option<ForkBlobSchedule> {
508 if (self.is_lstar_activated(block_timestamp)
513 || self.is_hegota_activated(block_timestamp)
514 || self.is_amsterdam_activated(block_timestamp))
515 && let Some(schedule) = self.blob_schedule.amsterdam
516 {
517 return Some(schedule);
518 }
519 if self.is_bpo5_activated(block_timestamp)
520 && let Some(schedule) = self.blob_schedule.bpo5
521 {
522 return Some(schedule);
523 }
524 if self.is_bpo4_activated(block_timestamp)
525 && let Some(schedule) = self.blob_schedule.bpo4
526 {
527 return Some(schedule);
528 }
529 if self.is_bpo3_activated(block_timestamp)
530 && let Some(schedule) = self.blob_schedule.bpo3
531 {
532 return Some(schedule);
533 }
534 if self.is_bpo2_activated(block_timestamp) {
535 Some(self.blob_schedule.bpo2)
536 } else if self.is_bpo1_activated(block_timestamp) {
537 Some(self.blob_schedule.bpo1)
538 } else if self.is_osaka_activated(block_timestamp) {
539 Some(self.blob_schedule.osaka)
540 } else if self.is_prague_activated(block_timestamp) {
541 Some(self.blob_schedule.prague)
542 } else if self.is_cancun_activated(block_timestamp) {
543 Some(self.blob_schedule.cancun)
544 } else {
545 None
546 }
547 }
548
549 pub fn fork(&self, block_timestamp: u64) -> Fork {
550 self.get_fork(block_timestamp)
551 }
552
553 pub fn next_fork(&self, block_timestamp: u64) -> Option<Fork> {
554 [
563 Fork::Shanghai,
564 Fork::Cancun,
565 Fork::Prague,
566 Fork::Osaka,
567 Fork::BPO1,
568 Fork::BPO2,
569 Fork::BPO3,
570 Fork::BPO4,
571 Fork::BPO5,
572 Fork::Amsterdam,
573 Fork::Hegota,
574 Fork::LStar,
575 ]
576 .into_iter()
577 .enumerate()
578 .filter_map(|(pos, fork)| {
579 self.get_activation_timestamp_for_fork(fork)
580 .filter(|&t| t > block_timestamp)
581 .map(|t| (fork, t, pos))
582 })
583 .min_by(|a, b| a.1.cmp(&b.1).then_with(|| a.2.cmp(&b.2)))
584 .map(|(fork, _, _)| fork)
585 }
586
587 pub fn get_last_scheduled_fork(&self) -> Fork {
588 if self.lstar_time.is_some() {
589 Fork::LStar
590 } else if self.hegota_time.is_some() {
591 Fork::Hegota
592 } else if self.amsterdam_time.is_some() {
593 Fork::Amsterdam
594 } else if self.bpo5_time.is_some() {
595 Fork::BPO5
596 } else if self.bpo4_time.is_some() {
597 Fork::BPO4
598 } else if self.bpo3_time.is_some() {
599 Fork::BPO3
600 } else if self.bpo2_time.is_some() {
601 Fork::BPO2
602 } else if self.bpo1_time.is_some() {
603 Fork::BPO1
604 } else if self.osaka_time.is_some() {
605 Fork::Osaka
606 } else if self.prague_time.is_some() {
607 Fork::Prague
608 } else if self.cancun_time.is_some() {
609 Fork::Cancun
610 } else {
611 Fork::Paris
612 }
613 }
614
615 pub fn get_activation_timestamp_for_fork(&self, fork: Fork) -> Option<u64> {
616 match fork {
617 Fork::Cancun => self.cancun_time,
618 Fork::Prague => self.prague_time,
619 Fork::Osaka => self.osaka_time,
620 Fork::BPO1 => self.bpo1_time,
621 Fork::BPO2 => self.bpo2_time,
622 Fork::BPO3 => self.bpo3_time,
623 Fork::BPO4 => self.bpo4_time,
624 Fork::BPO5 => self.bpo5_time,
625 Fork::Amsterdam => self.amsterdam_time,
626 Fork::Hegota => self.hegota_time,
627 Fork::LStar => self.lstar_time,
628 Fork::Homestead => self.homestead_block,
629 Fork::DaoFork => self.dao_fork_block,
630 Fork::Byzantium => self.byzantium_block,
631 Fork::Constantinople => self.constantinople_block,
632 Fork::Petersburg => self.petersburg_block,
633 Fork::Istanbul => self.istanbul_block,
634 Fork::MuirGlacier => self.muir_glacier_block,
635 Fork::Berlin => self.berlin_block,
636 Fork::London => self.london_block,
637 Fork::ArrowGlacier => self.arrow_glacier_block,
638 Fork::GrayGlacier => self.gray_glacier_block,
639 Fork::Paris => self.merge_netsplit_block,
640 Fork::Shanghai => self.shanghai_time,
641 _ => None,
642 }
643 }
644
645 pub fn get_blob_schedule_for_fork(&self, fork: Fork) -> Option<ForkBlobSchedule> {
646 if fork < Fork::Cancun {
652 return None;
653 }
654 self.get_activation_timestamp_for_fork(fork)
655 .and_then(|timestamp| self.get_fork_blob_schedule(timestamp))
656 }
657
658 pub fn gather_forks(&self, genesis_header: BlockHeader) -> (Vec<u64>, Vec<u64>) {
659 let mut block_number_based_forks: Vec<u64> = vec![
660 self.homestead_block,
661 if self.dao_fork_support {
662 self.dao_fork_block
663 } else {
664 None
665 },
666 self.eip150_block,
667 self.eip155_block,
668 self.eip158_block,
669 self.byzantium_block,
670 self.constantinople_block,
671 self.petersburg_block,
672 self.istanbul_block,
673 self.muir_glacier_block,
674 self.berlin_block,
675 self.london_block,
676 self.arrow_glacier_block,
677 self.gray_glacier_block,
678 self.merge_netsplit_block,
679 ]
680 .into_iter()
681 .flatten()
682 .collect();
683
684 block_number_based_forks.sort();
686 block_number_based_forks.dedup();
687
688 let mut timestamp_based_forks: Vec<u64> = vec![
689 self.shanghai_time,
690 self.cancun_time,
691 self.prague_time,
692 self.osaka_time,
693 self.bpo1_time,
694 self.bpo2_time,
695 self.bpo3_time,
696 self.bpo4_time,
697 self.bpo5_time,
698 self.amsterdam_time,
699 self.hegota_time,
700 self.verkle_time,
701 ]
702 .into_iter()
703 .flatten()
704 .collect();
705
706 timestamp_based_forks.sort();
708 timestamp_based_forks.dedup();
709
710 block_number_based_forks.retain(|block_number| *block_number != 0);
712 timestamp_based_forks.retain(|block_timestamp| *block_timestamp > genesis_header.timestamp);
713
714 (block_number_based_forks, timestamp_based_forks)
715 }
716}
717
718#[allow(unused)]
719#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
720pub struct GenesisAccount {
721 #[serde(default, with = "crate::serde_utils::bytes")]
722 pub code: Bytes,
723 #[serde(default)]
724 pub storage: BTreeMap<U256, U256>,
725 #[serde(deserialize_with = "crate::serde_utils::u256::deser_hex_or_dec_str")]
726 pub balance: U256,
727 #[serde(default, with = "crate::serde_utils::u64::hex_str")]
728 pub nonce: u64,
729}
730
731impl Genesis {
732 pub fn get_block(&self) -> Block {
733 Block::new(self.get_block_header(), self.get_block_body())
734 }
735
736 fn get_block_header(&self) -> BlockHeader {
737 let mut blob_gas_used: Option<u64> = None;
738 let mut excess_blob_gas: Option<u64> = None;
739
740 if let Some(cancun_time) = self.config.cancun_time
741 && cancun_time <= self.timestamp
742 {
743 blob_gas_used = Some(self.blob_gas_used.unwrap_or(0));
744 excess_blob_gas = Some(self.excess_blob_gas.unwrap_or(0));
745 }
746 let base_fee_per_gas = self.base_fee_per_gas.or_else(|| {
747 self.config
748 .is_london_activated(0)
749 .then_some(INITIAL_BASE_FEE)
750 });
751
752 let withdrawals_root = self
753 .config
754 .is_shanghai_activated(self.timestamp)
755 .then_some(compute_withdrawals_root(&[], &NativeCrypto));
756
757 let parent_beacon_block_root = self
758 .config
759 .is_cancun_activated(self.timestamp)
760 .then_some(H256::zero());
761
762 let requests_hash = self
763 .config
764 .is_prague_activated(self.timestamp)
765 .then_some(self.requests_hash.unwrap_or(*DEFAULT_REQUESTS_HASH));
766
767 let block_access_list_hash = self
768 .config
769 .is_amsterdam_activated(self.timestamp)
770 .then_some(
771 self.block_access_list_hash
772 .unwrap_or(*EMPTY_BLOCK_ACCESS_LIST_HASH),
773 );
774
775 let slot_number = self
776 .config
777 .is_amsterdam_activated(self.timestamp)
778 .then_some(self.slot_number.unwrap_or(0));
779
780 BlockHeader {
781 parent_hash: H256::zero(),
782 ommers_hash: *DEFAULT_OMMERS_HASH,
783 coinbase: self.coinbase,
784 state_root: self.compute_state_root(),
785 transactions_root: compute_transactions_root(&[], &NativeCrypto),
786 receipts_root: compute_receipts_root(&[], &NativeCrypto),
787 logs_bloom: Bloom::zero(),
788 difficulty: self.difficulty,
789 number: 0,
790 gas_limit: self.gas_limit,
791 gas_used: 0,
792 timestamp: self.timestamp,
793 extra_data: self.extra_data.clone(),
794 prev_randao: self.mix_hash,
795 nonce: self.nonce,
796 base_fee_per_gas,
797 withdrawals_root,
798 blob_gas_used,
799 excess_blob_gas,
800 parent_beacon_block_root,
801 requests_hash,
802 block_access_list_hash,
803 slot_number,
804 ..Default::default()
805 }
806 }
807
808 fn get_block_body(&self) -> BlockBody {
809 BlockBody {
810 transactions: vec![],
811 ommers: vec![],
812 withdrawals: Some(vec![]),
813 }
814 }
815
816 pub fn compute_state_root(&self) -> H256 {
817 let iter = self.alloc.iter().map(|(addr, account)| {
818 (
819 keccak_hash(addr).to_vec(),
820 AccountState::from(account).encode_to_vec(),
821 )
822 });
823 Trie::compute_hash_from_unsorted_iter(iter, &NativeCrypto)
824 }
825}
826#[cfg(test)]
827mod tests {
828 use std::str::FromStr;
829 use std::{fs::File, io::BufReader};
830
831 use ethereum_types::H160;
832
833 use crate::types::INITIAL_BASE_FEE;
834
835 use super::*;
836
837 #[test]
838 fn terminal_total_difficulty_accepts_number_or_hex_string() {
839 let dca = r#""depositContractAddress":"0x00000000219ab540356cbb839cbe05303d7705fa""#;
843
844 let from_number: ChainConfig = serde_json::from_str(&format!(
845 r#"{{"chainId":1,"terminalTotalDifficulty":58750000000000000000000,{dca}}}"#
846 ))
847 .expect("number-encoded TTD should parse");
848 assert_eq!(
851 from_number.terminal_total_difficulty,
852 Some(58749999999999996329984u128)
853 );
854
855 let from_hex: ChainConfig = serde_json::from_str(&format!(
856 r#"{{"chainId":1,"terminalTotalDifficulty":"0xc70d808a128d7380000",{dca}}}"#
857 ))
858 .expect("hex-string TTD should parse");
859 assert_eq!(
860 from_hex.terminal_total_difficulty,
861 Some(58750000000000000000000u128)
862 );
863
864 let small: ChainConfig = serde_json::from_str(&format!(
865 r#"{{"chainId":1,"terminalTotalDifficulty":17000000000000000,{dca}}}"#
866 ))
867 .expect("small number TTD should parse");
868 assert_eq!(small.terminal_total_difficulty, Some(17000000000000000u128));
869
870 let negative = serde_json::from_str::<ChainConfig>(&format!(
872 r#"{{"chainId":1,"terminalTotalDifficulty":-1,{dca}}}"#
873 ));
874 let err = negative.expect_err("negative TTD must be rejected");
875 assert!(
876 err.to_string().contains("finite, non-negative"),
877 "error should name the sign/finiteness cause, got: {err}"
878 );
879 }
880
881 #[test]
882 fn pow_genesis_detection() {
883 let mut g = Genesis::default();
885 assert!(!is_unsupported_pow_genesis(&g));
886
887 g.difficulty = U256::from(0x4000_0000u64);
889 assert!(is_unsupported_pow_genesis(&g));
890
891 g.config.terminal_total_difficulty = Some(58750000000000000000000);
893 assert!(!is_unsupported_pow_genesis(&g));
894
895 g.config.terminal_total_difficulty = None;
897 g.config.merge_netsplit_block = Some(15537394);
898 assert!(!is_unsupported_pow_genesis(&g));
899
900 g.config.merge_netsplit_block = None;
902 g.config.terminal_total_difficulty = Some(0);
903 assert!(!is_unsupported_pow_genesis(&g));
904
905 g.config.terminal_total_difficulty = None;
908 g.config.terminal_total_difficulty_passed = true;
909 assert!(!is_unsupported_pow_genesis(&g));
910 }
911
912 #[test]
913 fn deserialize_genesis_file() {
914 let file = File::open("../../fixtures/genesis/kurtosis.json")
916 .expect("Failed to open genesis file");
917 let reader = BufReader::new(file);
918 let genesis: Genesis =
919 serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
920 let expected_chain_config = ChainConfig {
923 chain_id: 3151908_u64,
924 homestead_block: Some(0),
925 eip150_block: Some(0),
926 eip155_block: Some(0),
927 eip158_block: Some(0),
928 byzantium_block: Some(0),
929 constantinople_block: Some(0),
930 petersburg_block: Some(0),
931 istanbul_block: Some(0),
932 berlin_block: Some(0),
933 london_block: Some(0),
934 merge_netsplit_block: Some(0),
935 shanghai_time: Some(0),
936 cancun_time: Some(0),
937 prague_time: Some(1718232101),
938 terminal_total_difficulty: Some(0),
939 terminal_total_difficulty_passed: true,
940 deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
941 .unwrap(),
942 blob_schedule: BlobSchedule {
944 cancun: ForkBlobSchedule {
945 target: 2,
946 max: 3,
947 base_fee_update_fraction: 6676954,
948 },
949 prague: ForkBlobSchedule {
950 target: 3,
951 max: 4,
952 base_fee_update_fraction: 13353908,
953 },
954 ..Default::default()
955 },
956 ..Default::default()
957 };
958 assert_eq!(&genesis.config, &expected_chain_config);
959 assert_eq!(genesis.coinbase, Address::from([0; 20]));
961 assert_eq!(genesis.difficulty, U256::from(1));
962 assert!(genesis.extra_data.is_empty());
963 assert_eq!(genesis.gas_limit, 0x17d7840);
964 assert_eq!(genesis.nonce, 0x1234);
965 assert_eq!(genesis.mix_hash, H256::from([0; 32]));
966 assert_eq!(genesis.timestamp, 1718040081);
967 let addr_a = Address::from_str("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap();
970 assert!(genesis.alloc.contains_key(&addr_a));
971 let expected_account_a = GenesisAccount {
972 code: Bytes::from(hex::decode("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500").unwrap()),
973 balance: 0.into(),
974 nonce: 1,
975 storage: Default::default(),
976 };
977 assert_eq!(genesis.alloc[&addr_a], expected_account_a);
978 let addr_b = Address::from_str("0x4242424242424242424242424242424242424242").unwrap();
980 assert!(genesis.alloc.contains_key(&addr_b));
981 let addr_b_storage = &genesis.alloc[&addr_b].storage;
982 assert_eq!(
983 addr_b_storage.get(
984 &U256::from_str(
985 "0x0000000000000000000000000000000000000000000000000000000000000022"
986 )
987 .unwrap()
988 ),
989 Some(
990 &U256::from_str(
991 "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
992 )
993 .unwrap()
994 )
995 );
996 assert_eq!(
997 addr_b_storage.get(
998 &U256::from_str(
999 "0x0000000000000000000000000000000000000000000000000000000000000038"
1000 )
1001 .unwrap()
1002 ),
1003 Some(
1004 &U256::from_str(
1005 "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7"
1006 )
1007 .unwrap()
1008 )
1009 );
1010 }
1011
1012 #[test]
1013 fn genesis_block() {
1014 let file = File::open("../../fixtures/genesis/kurtosis.json")
1016 .expect("Failed to open genesis file");
1017 let reader = BufReader::new(file);
1018 let genesis: Genesis =
1019 serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1020 let genesis_block = genesis.get_block();
1021 let header = genesis_block.header;
1022 let body = genesis_block.body;
1023 assert_eq!(header.parent_hash, H256::from([0; 32]));
1024 assert_eq!(header.ommers_hash, *DEFAULT_OMMERS_HASH);
1025 assert_eq!(header.coinbase, Address::default());
1026 assert_eq!(
1027 header.state_root,
1028 H256::from_str("0x2dab6a1d6d638955507777aecea699e6728825524facbd446bd4e86d44fa5ecd")
1029 .unwrap()
1030 );
1031 assert_eq!(
1032 header.transactions_root,
1033 compute_transactions_root(&[], &NativeCrypto)
1034 );
1035 assert_eq!(
1036 header.receipts_root,
1037 compute_receipts_root(&[], &NativeCrypto)
1038 );
1039 assert_eq!(header.logs_bloom, Bloom::default());
1040 assert_eq!(header.difficulty, U256::from(1));
1041 assert_eq!(header.gas_limit, 25_000_000);
1042 assert_eq!(header.gas_used, 0);
1043 assert_eq!(header.timestamp, 1_718_040_081);
1044 assert_eq!(header.extra_data, Bytes::default());
1045 assert_eq!(header.prev_randao, H256::from([0; 32]));
1046 assert_eq!(header.nonce, 4660);
1047 assert_eq!(
1048 header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE),
1049 INITIAL_BASE_FEE
1050 );
1051 assert_eq!(
1052 header.withdrawals_root,
1053 Some(compute_withdrawals_root(&[], &NativeCrypto))
1054 );
1055 assert_eq!(header.blob_gas_used, Some(0));
1056 assert_eq!(header.excess_blob_gas, Some(0));
1057 assert_eq!(header.parent_beacon_block_root, Some(H256::zero()));
1058 assert!(body.transactions.is_empty());
1059 assert!(body.ommers.is_empty());
1060 assert!(body.withdrawals.is_some_and(|w| w.is_empty()));
1061 }
1062
1063 #[test]
1064 fn read_and_compute_kurtosis_hash() {
1066 let file = File::open("../../fixtures/genesis/kurtosis.json")
1067 .expect("Failed to open genesis file");
1068 let reader = BufReader::new(file);
1069 let genesis: Genesis =
1070 serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1071 let genesis_block_hash = genesis.get_block().hash();
1072 assert_eq!(
1073 genesis_block_hash,
1074 H256::from_str("0xcb5306dd861d0f2c1f9952fbfbc75a46d0b6ce4f37bea370c3471fe8410bf40b")
1075 .unwrap()
1076 )
1077 }
1078
1079 #[test]
1080 fn parse_hive_genesis_file() {
1081 let file =
1082 File::open("../../fixtures/genesis/hive.json").expect("Failed to open genesis file");
1083 let reader = BufReader::new(file);
1084 let _genesis: Genesis =
1085 serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1086 }
1087
1088 #[test]
1089 fn read_and_compute_hive_hash() {
1090 let file =
1091 File::open("../../fixtures/genesis/hive.json").expect("Failed to open genesis file");
1092 let reader = BufReader::new(file);
1093 let genesis: Genesis =
1094 serde_json::from_reader(reader).expect("Failed to deserialize genesis file");
1095 let computed_block_hash = genesis.get_block().hash();
1096 let genesis_block_hash =
1097 H256::from_str("0x30f516e34fc173bb5fc4daddcc7532c4aca10b702c7228f3c806b4df2646fb7e")
1098 .unwrap();
1099 assert_eq!(genesis_block_hash, computed_block_hash)
1100 }
1101
1102 #[test]
1103 fn deserialize_chain_config_blob_schedule() {
1104 let json = r#"
1105
1106 {
1107 "chainId": 123,
1108 "blobSchedule": {
1109 "cancun": {
1110 "target": 1,
1111 "max": 2,
1112 "baseFeeUpdateFraction": 10000
1113 },
1114 "prague": {
1115 "target": 3,
1116 "max": 4,
1117 "baseFeeUpdateFraction": 20000
1118 }
1119 },
1120 "depositContractAddress": "0x4242424242424242424242424242424242424242"
1121 }
1122 "#;
1123
1124 let config: ChainConfig =
1125 serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1126 let expected_chain_config = ChainConfig {
1127 chain_id: 123,
1128 blob_schedule: BlobSchedule {
1129 cancun: ForkBlobSchedule {
1130 target: 1,
1131 max: 2,
1132 base_fee_update_fraction: 10000,
1133 },
1134 prague: ForkBlobSchedule {
1135 target: 3,
1136 max: 4,
1137 base_fee_update_fraction: 20000,
1138 },
1139 ..Default::default()
1140 },
1141 deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1142 .unwrap(),
1143 ..Default::default()
1144 };
1145 assert_eq!(&config, &expected_chain_config);
1146 }
1147
1148 #[test]
1149 fn deserialize_chain_config_missing_entire_blob_schedule() {
1150 let json = r#"
1151 {
1152 "chainId": 123,
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: 6,
1169 max: 9,
1170 base_fee_update_fraction: 5007716,
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_cancun_blob_schedule() {
1183 let json = r#"
1184 {
1185 "chainId": 123,
1186 "blobSchedule": {
1187 "prague": {
1188 "target": 3,
1189 "max": 4,
1190 "baseFeeUpdateFraction": 20000
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: 3,
1204 max: 6,
1205 base_fee_update_fraction: 3338477,
1206 },
1207 prague: ForkBlobSchedule {
1208 target: 3,
1209 max: 4,
1210 base_fee_update_fraction: 20000,
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_prague_blob_schedule() {
1223 let json = r#"
1224 {
1225 "chainId": 123,
1226 "blobSchedule": {
1227 "cancun": {
1228 "target": 1,
1229 "max": 2,
1230 "baseFeeUpdateFraction": 10000
1231 }
1232 },
1233 "depositContractAddress": "0x4242424242424242424242424242424242424242"
1234 }
1235 "#;
1236
1237 let config: ChainConfig =
1238 serde_json::from_str(json).expect("Failed to deserialize ChainConfig");
1239 let expected_chain_config = ChainConfig {
1240 chain_id: 123,
1241 blob_schedule: BlobSchedule {
1242 cancun: ForkBlobSchedule {
1243 target: 1,
1244 max: 2,
1245 base_fee_update_fraction: 10000,
1246 },
1247 prague: ForkBlobSchedule {
1248 target: 6,
1249 max: 9,
1250 base_fee_update_fraction: 5007716,
1251 },
1252 ..Default::default()
1253 },
1254 deposit_contract_address: H160::from_str("0x4242424242424242424242424242424242424242")
1255 .unwrap(),
1256 ..Default::default()
1257 };
1258 assert_eq!(&config, &expected_chain_config);
1259 }
1260
1261 #[test]
1262 fn deserialize_chain_config_missing_deposit_contract_address() {
1263 let json = r#"
1264 {
1265 "chainId": 123
1266 }
1267 "#;
1268
1269 let result: Result<ChainConfig, _> = serde_json::from_str(json);
1270
1271 assert!(result.is_err());
1272
1273 let error_message = result.unwrap_err().to_string();
1274 assert!(error_message.contains("missing field `depositContractAddress`"),);
1275 }
1276
1277 #[test]
1278 fn lstar_fork_ordering_and_activation() {
1279 assert!(Fork::LStar > Fork::Amsterdam);
1281 assert_eq!(<&str>::from(Fork::LStar), "LStar");
1282
1283 let mut cfg = ChainConfig {
1284 lstar_time: Some(100),
1285 ..Default::default()
1286 };
1287 assert!(!cfg.is_lstar_activated(99));
1288 assert!(cfg.is_lstar_activated(100));
1289 assert!(cfg.is_lstar_activated(101));
1290
1291 cfg.lstar_time = None;
1292 assert!(!cfg.is_lstar_activated(u64::MAX));
1293 }
1294
1295 #[test]
1296 fn native_l2_genesis_activates_amsterdam() {
1297 let file = File::open("../../fixtures/genesis/native_l2.json")
1298 .expect("Failed to open native_l2.json");
1299 let reader = BufReader::new(file);
1300 let genesis: Genesis =
1301 serde_json::from_reader(reader).expect("Failed to deserialize native_l2.json");
1302 assert_eq!(
1303 genesis.config.get_fork(0),
1304 Fork::Amsterdam,
1305 "native_l2.json genesis should activate Amsterdam at timestamp 0"
1306 );
1307 let bs = genesis
1309 .config
1310 .get_fork_blob_schedule(0)
1311 .expect("Amsterdam implies bpo2 blob schedule");
1312 assert_eq!(bs.max, 21, "Amsterdam should inherit bpo2 max=21");
1313 }
1314
1315 #[test]
1316 fn lstar_fork_resolution() {
1317 let cfg = ChainConfig {
1319 cancun_time: Some(0),
1320 prague_time: Some(0),
1321 amsterdam_time: Some(0),
1322 lstar_time: Some(1000),
1323 ..Default::default()
1324 };
1325
1326 assert_eq!(cfg.get_fork(999), Fork::Amsterdam);
1328 assert_eq!(cfg.get_fork(1000), Fork::LStar);
1330
1331 assert_eq!(cfg.next_fork(999), Some(Fork::LStar));
1333 assert_eq!(cfg.next_fork(1000), None);
1334
1335 assert_eq!(cfg.get_last_scheduled_fork(), Fork::LStar);
1337
1338 assert_eq!(
1340 cfg.get_activation_timestamp_for_fork(Fork::LStar),
1341 Some(1000)
1342 );
1343
1344 let sched_lstar = cfg
1346 .get_fork_blob_schedule(1000)
1347 .expect("lstar blob schedule");
1348 let sched_amsterdam = cfg
1349 .get_fork_blob_schedule(999)
1350 .expect("amsterdam blob schedule");
1351 assert_eq!(sched_lstar.max, sched_amsterdam.max);
1352 }
1353
1354 #[test]
1355 fn test_hegota_after_amsterdam() {
1356 assert!(Fork::Hegota > Fork::Amsterdam);
1358 assert_eq!(Fork::Hegota as u8, 26);
1359 assert_eq!(Fork::Amsterdam as u8, 25);
1360
1361 let hegota_str: &str = Fork::Hegota.into();
1363 assert_eq!(hegota_str, "Hegota");
1364
1365 let mut config = ChainConfig {
1367 chain_id: 1,
1368 deposit_contract_address: Address::default(),
1369 ..Default::default()
1370 };
1371 config.hegota_time = Some(1000);
1372 assert!(!config.is_hegota_activated(999));
1373 assert!(config.is_hegota_activated(1000));
1374 assert!(config.is_hegota_activated(1001));
1375
1376 config.hegota_time = None;
1378 assert!(!config.is_hegota_activated(0));
1379 assert!(!config.is_hegota_activated(u64::MAX));
1380
1381 let mut config = ChainConfig {
1383 chain_id: 1,
1384 deposit_contract_address: Address::default(),
1385 ..Default::default()
1386 };
1387 config.amsterdam_time = Some(500);
1388 config.hegota_time = Some(1000);
1389 assert_eq!(config.get_fork(499), Fork::Paris);
1390 assert_eq!(config.get_fork(500), Fork::Amsterdam);
1391 assert_eq!(config.get_fork(999), Fork::Amsterdam);
1392 assert_eq!(config.get_fork(1000), Fork::Hegota);
1393 assert_eq!(config.get_fork(2000), Fork::Hegota);
1394
1395 assert_eq!(config.next_fork(500), Some(Fork::Hegota));
1397 assert_eq!(config.next_fork(1000), None);
1398
1399 assert_eq!(config.get_last_scheduled_fork(), Fork::Hegota);
1401
1402 assert_eq!(
1404 config.get_activation_timestamp_for_fork(Fork::Hegota),
1405 Some(1000)
1406 );
1407 }
1408
1409 #[test]
1410 fn chain_config_accepts_heze_and_bogota_aliases_for_hegota_time() {
1411 for key in [
1412 "hegotaTime",
1413 "hezeTime",
1414 "heze_time",
1415 "bogotaTime",
1416 "bogota_time",
1417 ] {
1418 let json = format!(
1419 r#"{{
1420 "chainId": 1,
1421 "depositContractAddress": "0x0000000000000000000000000000000000000000",
1422 "{key}": 1700000000
1423 }}"#
1424 );
1425 let cfg: ChainConfig = serde_json::from_str(&json)
1426 .unwrap_or_else(|e| panic!("alias {key} must deserialize: {e}"));
1427 assert_eq!(cfg.hegota_time, Some(1_700_000_000), "alias {key}");
1428 }
1429 }
1430
1431 #[test]
1432 fn next_fork_skips_unscheduled_intermediate_forks() {
1433 let config = ChainConfig {
1437 shanghai_time: Some(0),
1438 cancun_time: Some(0),
1439 prague_time: Some(0),
1440 osaka_time: Some(0),
1441 amsterdam_time: Some(1_779_098_127),
1442 ..Default::default()
1443 };
1444 assert_eq!(config.next_fork(0), Some(Fork::Amsterdam));
1445 assert_eq!(config.next_fork(1_779_098_126), Some(Fork::Amsterdam));
1446 assert_eq!(config.next_fork(1_779_098_127), None);
1447 }
1448
1449 #[test]
1450 fn next_fork_picks_earliest_scheduled() {
1451 let config = ChainConfig {
1453 shanghai_time: Some(0),
1454 cancun_time: Some(100),
1455 prague_time: Some(200),
1456 osaka_time: Some(300),
1457 ..Default::default()
1458 };
1459 assert_eq!(config.next_fork(150), Some(Fork::Prague));
1460 assert_eq!(config.next_fork(250), Some(Fork::Osaka));
1461 assert_eq!(config.next_fork(300), None);
1462 }
1463
1464 #[test]
1465 fn next_fork_returns_none_when_at_last_scheduled() {
1466 let config = ChainConfig {
1467 shanghai_time: Some(0),
1468 cancun_time: Some(0),
1469 ..Default::default()
1470 };
1471 assert_eq!(config.next_fork(0), None);
1472 }
1473
1474 #[test]
1475 fn amsterdam_inherits_highest_activated_bpo_schedule() {
1476 let config: ChainConfig = serde_json::from_str(
1480 r#"{
1481 "chainId": 1,
1482 "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa",
1483 "osakaTime": 0,
1484 "bpo1Time": 0,
1485 "amsterdamTime": 100,
1486 "blobSchedule": {
1487 "cancun": {"target": 3, "max": 6, "baseFeeUpdateFraction": 3338477},
1488 "prague": {"target": 6, "max": 9, "baseFeeUpdateFraction": 5007716},
1489 "bpo1": {"target": 10, "max": 15, "baseFeeUpdateFraction": 8346193}
1490 }
1491 }"#,
1492 )
1493 .expect("genesis should parse");
1494
1495 let bpo1 = ForkBlobSchedule {
1496 target: 10,
1497 max: 15,
1498 base_fee_update_fraction: 8346193,
1499 };
1500 assert_eq!(config.get_fork_blob_schedule(99), Some(bpo1));
1501 assert_eq!(config.get_fork_blob_schedule(100), Some(bpo1));
1502 }
1503
1504 #[test]
1505 fn blob_schedule_for_fork_reports_the_inherited_entry() {
1506 let config = ChainConfig {
1509 osaka_time: Some(0),
1510 bpo1_time: Some(0),
1511 amsterdam_time: Some(100),
1512 ..Default::default()
1513 };
1514
1515 let schedule = config
1516 .get_blob_schedule_for_fork(Fork::Amsterdam)
1517 .expect("Amsterdam must report a blob schedule");
1518 assert_eq!((schedule.target, schedule.max), (10, 15));
1519
1520 assert_eq!(config.get_blob_schedule_for_fork(Fork::BPO2), None);
1523 assert_eq!(config.get_blob_schedule_for_fork(Fork::London), None);
1524 }
1525
1526 #[test]
1527 fn amsterdam_prefers_explicitly_pinned_schedule() {
1528 let mut config = ChainConfig {
1529 osaka_time: Some(0),
1530 bpo1_time: Some(0),
1531 bpo2_time: Some(50),
1532 amsterdam_time: Some(100),
1533 ..Default::default()
1534 };
1535 assert_eq!(
1536 config.get_fork_blob_schedule(100),
1537 Some(default_bpo2_schedule())
1538 );
1539
1540 let pinned = ForkBlobSchedule {
1541 target: 16,
1542 max: 24,
1543 base_fee_update_fraction: 13353910,
1544 };
1545 config.blob_schedule.amsterdam = Some(pinned);
1546 assert_eq!(config.get_fork_blob_schedule(100), Some(pinned));
1547 }
1548}