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