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