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