1pub use alloy_eips::eip1559::BaseFeeParams;
2use alloy_evm::eth::spec::EthExecutorSpec;
3
4use crate::{
5 constants::{MAINNET_DEPOSIT_CONTRACT, MAINNET_PRUNE_DELETE_LIMIT},
6 ethereum::SEPOLIA_PARIS_TTD,
7 holesky, hoodi, mainnet,
8 mainnet::{MAINNET_PARIS_BLOCK, MAINNET_PARIS_TTD},
9 sepolia,
10 sepolia::SEPOLIA_PARIS_BLOCK,
11 EthChainSpec,
12};
13use alloc::{
14 boxed::Box,
15 collections::BTreeMap,
16 format,
17 string::{String, ToString},
18 sync::Arc,
19 vec::Vec,
20};
21use alloy_chains::{Chain, NamedChain};
22use alloy_consensus::{
23 constants::{
24 EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, HOODI_GENESIS_HASH, MAINNET_GENESIS_HASH,
25 SEPOLIA_GENESIS_HASH,
26 },
27 Header,
28};
29use alloy_eips::{
30 eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
31 eip7892::BlobScheduleBlobParams, eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
32};
33use alloy_genesis::{ChainConfig, Genesis};
34use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
35use alloy_trie::root::state_root_ref_unhashed;
36use core::fmt::Debug;
37use derive_more::From;
38use reth_ethereum_forks::{
39 ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition,
40 ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Hardforks, Head, DEV_HARDFORKS,
41};
42use reth_network_peers::{holesky_nodes, hoodi_nodes, mainnet_nodes, sepolia_nodes, NodeRecord};
43use reth_primitives_traits::{sync::LazyLock, BlockHeader, SealedHeader};
44
45pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
47 let base_fee_per_gas = hardforks
49 .fork(EthereumHardfork::London)
50 .active_at_block(0)
51 .then(|| genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(INITIAL_BASE_FEE));
52
53 let withdrawals_root = hardforks
56 .fork(EthereumHardfork::Shanghai)
57 .active_at_timestamp(genesis.timestamp)
58 .then_some(EMPTY_WITHDRAWALS);
59
60 let (parent_beacon_block_root, blob_gas_used, excess_blob_gas) =
65 if hardforks.fork(EthereumHardfork::Cancun).active_at_timestamp(genesis.timestamp) {
66 let blob_gas_used = genesis.blob_gas_used.unwrap_or(0);
67 let excess_blob_gas = genesis.excess_blob_gas.unwrap_or(0);
68 (Some(B256::ZERO), Some(blob_gas_used), Some(excess_blob_gas))
69 } else {
70 (None, None, None)
71 };
72
73 let requests_hash = hardforks
75 .fork(EthereumHardfork::Prague)
76 .active_at_timestamp(genesis.timestamp)
77 .then_some(EMPTY_REQUESTS_HASH);
78
79 let block_access_list_hash = hardforks
81 .fork(EthereumHardfork::Amsterdam)
82 .active_at_timestamp(genesis.timestamp)
83 .then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
84
85 let slot_number = hardforks
87 .fork(EthereumHardfork::Amsterdam)
88 .active_at_timestamp(genesis.timestamp)
89 .then_some(genesis.slot_number.unwrap_or(0));
90
91 Header {
92 number: genesis.number.unwrap_or_default(),
93 parent_hash: genesis.parent_hash.unwrap_or_default(),
94 gas_limit: genesis.gas_limit,
95 difficulty: genesis.difficulty,
96 nonce: genesis.nonce.into(),
97 extra_data: genesis.extra_data.clone(),
98 state_root: state_root_ref_unhashed(&genesis.alloc),
99 timestamp: genesis.timestamp,
100 mix_hash: genesis.mix_hash,
101 beneficiary: genesis.coinbase,
102 base_fee_per_gas,
103 withdrawals_root,
104 parent_beacon_block_root,
105 blob_gas_used,
106 excess_blob_gas,
107 requests_hash,
108 block_access_list_hash,
109 slot_number,
110 ..Default::default()
111 }
112}
113
114pub static MAINNET: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
116 let genesis = serde_json::from_str(include_str!("../res/genesis/mainnet.json"))
117 .expect("Can't deserialize Mainnet genesis json");
118 let hardforks = EthereumHardfork::mainnet().into();
119 let mut spec = ChainSpec {
120 chain: Chain::mainnet(),
121 genesis_header: SealedHeader::new(
122 make_genesis_header(&genesis, &hardforks),
123 MAINNET_GENESIS_HASH,
124 ),
125 genesis,
126 paris_block_and_final_difficulty: Some((
128 MAINNET_PARIS_BLOCK,
129 U256::from(58_750_003_716_598_352_816_469u128),
130 )),
131 hardforks,
132 deposit_contract: Some(MAINNET_DEPOSIT_CONTRACT),
134 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
135 prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
136 blob_params: BlobScheduleBlobParams::default().with_scheduled([
137 (mainnet::MAINNET_BPO1_TIMESTAMP, BlobParams::bpo1()),
138 (mainnet::MAINNET_BPO2_TIMESTAMP, BlobParams::bpo2()),
139 ]),
140 };
141 spec.genesis.config.dao_fork_support = true;
142 spec.into()
143});
144
145pub static SEPOLIA: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
147 let genesis = serde_json::from_str(include_str!("../res/genesis/sepolia.json"))
148 .expect("Can't deserialize Sepolia genesis json");
149 let hardforks = EthereumHardfork::sepolia().into();
150 let mut spec = ChainSpec {
151 chain: Chain::sepolia(),
152 genesis_header: SealedHeader::new(
153 make_genesis_header(&genesis, &hardforks),
154 SEPOLIA_GENESIS_HASH,
155 ),
156 genesis,
157 paris_block_and_final_difficulty: Some((
159 SEPOLIA_PARIS_BLOCK,
160 U256::from(17_000_018_015_853_232u128),
161 )),
162 hardforks,
163 deposit_contract: Some(DepositContract::new(
165 address!("0x7f02c3e3c98b133055b8b348b2ac625669ed295d"),
166 1273020,
167 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
168 )),
169 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
170 prune_delete_limit: 10000,
171 blob_params: BlobScheduleBlobParams::default().with_scheduled([
172 (sepolia::SEPOLIA_BPO1_TIMESTAMP, BlobParams::bpo1()),
173 (sepolia::SEPOLIA_BPO2_TIMESTAMP, BlobParams::bpo2()),
174 ]),
175 };
176 spec.genesis.config.dao_fork_support = true;
177 spec.into()
178});
179
180pub static HOLESKY: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
182 let genesis = serde_json::from_str(include_str!("../res/genesis/holesky.json"))
183 .expect("Can't deserialize Holesky genesis json");
184 let hardforks = EthereumHardfork::holesky().into();
185 let mut spec = ChainSpec {
186 chain: Chain::holesky(),
187 genesis_header: SealedHeader::new(
188 make_genesis_header(&genesis, &hardforks),
189 HOLESKY_GENESIS_HASH,
190 ),
191 genesis,
192 paris_block_and_final_difficulty: Some((0, U256::from(1))),
193 hardforks,
194 deposit_contract: Some(DepositContract::new(
195 address!("0x4242424242424242424242424242424242424242"),
196 0,
197 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
198 )),
199 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
200 prune_delete_limit: 10000,
201 blob_params: BlobScheduleBlobParams::default().with_scheduled([
202 (holesky::HOLESKY_BPO1_TIMESTAMP, BlobParams::bpo1()),
203 (holesky::HOLESKY_BPO2_TIMESTAMP, BlobParams::bpo2()),
204 ]),
205 };
206 spec.genesis.config.dao_fork_support = true;
207 spec.into()
208});
209
210pub static HOODI: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
214 let genesis = serde_json::from_str(include_str!("../res/genesis/hoodi.json"))
215 .expect("Can't deserialize Hoodi genesis json");
216 let hardforks = EthereumHardfork::hoodi().into();
217 let mut spec = ChainSpec {
218 chain: Chain::hoodi(),
219 genesis_header: SealedHeader::new(
220 make_genesis_header(&genesis, &hardforks),
221 HOODI_GENESIS_HASH,
222 ),
223 genesis,
224 paris_block_and_final_difficulty: Some((0, U256::from(0))),
225 hardforks,
226 deposit_contract: Some(DepositContract::new(
227 address!("0x00000000219ab540356cBB839Cbe05303d7705Fa"),
228 0,
229 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
230 )),
231 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
232 prune_delete_limit: 10000,
233 blob_params: BlobScheduleBlobParams::default().with_scheduled([
234 (hoodi::HOODI_BPO1_TIMESTAMP, BlobParams::bpo1()),
235 (hoodi::HOODI_BPO2_TIMESTAMP, BlobParams::bpo2()),
236 ]),
237 };
238 spec.genesis.config.dao_fork_support = true;
239 spec.into()
240});
241
242pub static DEV: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
247 let genesis = serde_json::from_str(include_str!("../res/genesis/dev.json"))
248 .expect("Can't deserialize Dev testnet genesis json");
249 let hardforks = DEV_HARDFORKS.clone();
250 ChainSpec {
251 chain: Chain::dev(),
252 genesis_header: SealedHeader::seal_slow(make_genesis_header(&genesis, &hardforks)),
253 genesis,
254 paris_block_and_final_difficulty: Some((0, U256::from(0))),
255 hardforks,
256 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
257 deposit_contract: None, ..Default::default()
259 }
260 .into()
261});
262
263pub fn create_chain_config(
266 chain: Option<Chain>,
267 hardforks: &ChainHardforks,
268 deposit_contract_address: Option<Address>,
269 blob_schedule: BTreeMap<String, BlobParams>,
270) -> ChainConfig {
271 let block_num = |fork: EthereumHardfork| hardforks.fork(fork).block_number();
273
274 let timestamp = |fork: EthereumHardfork| -> Option<u64> {
276 match hardforks.fork(fork) {
277 ForkCondition::Timestamp(t) => Some(t),
278 _ => None,
279 }
280 };
281
282 let (terminal_total_difficulty, terminal_total_difficulty_passed) =
284 match hardforks.fork(EthereumHardfork::Paris) {
285 ForkCondition::TTD { total_difficulty, .. } => (Some(total_difficulty), true),
286 _ => (None, false),
287 };
288
289 let dao_fork_support = hardforks.fork(EthereumHardfork::Dao) != ForkCondition::Never;
291
292 ChainConfig {
293 chain_id: chain.map(|c| c.id()).unwrap_or(0),
294 homestead_block: block_num(EthereumHardfork::Homestead),
295 dao_fork_block: block_num(EthereumHardfork::Dao),
296 dao_fork_support,
297 eip150_block: block_num(EthereumHardfork::Tangerine),
298 eip155_block: block_num(EthereumHardfork::SpuriousDragon),
299 eip158_block: block_num(EthereumHardfork::SpuriousDragon),
300 byzantium_block: block_num(EthereumHardfork::Byzantium),
301 constantinople_block: block_num(EthereumHardfork::Constantinople),
302 petersburg_block: block_num(EthereumHardfork::Petersburg),
303 istanbul_block: block_num(EthereumHardfork::Istanbul),
304 muir_glacier_block: block_num(EthereumHardfork::MuirGlacier),
305 berlin_block: block_num(EthereumHardfork::Berlin),
306 london_block: block_num(EthereumHardfork::London),
307 arrow_glacier_block: block_num(EthereumHardfork::ArrowGlacier),
308 gray_glacier_block: block_num(EthereumHardfork::GrayGlacier),
309 merge_netsplit_block: None,
310 shanghai_time: timestamp(EthereumHardfork::Shanghai),
311 cancun_time: timestamp(EthereumHardfork::Cancun),
312 prague_time: timestamp(EthereumHardfork::Prague),
313 osaka_time: timestamp(EthereumHardfork::Osaka),
314 amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
315 bogota_time: timestamp(EthereumHardfork::Bogota),
316 bpo1_time: timestamp(EthereumHardfork::Bpo1),
317 bpo2_time: timestamp(EthereumHardfork::Bpo2),
318 bpo3_time: timestamp(EthereumHardfork::Bpo3),
319 bpo4_time: timestamp(EthereumHardfork::Bpo4),
320 bpo5_time: timestamp(EthereumHardfork::Bpo5),
321 terminal_total_difficulty,
322 terminal_total_difficulty_passed,
323 deposit_contract_address,
324 blob_schedule,
325 ..Default::default()
326 }
327}
328
329pub fn mainnet_chain_config() -> ChainConfig {
331 let hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
332 let blob_schedule = blob_params_to_schedule(&MAINNET.blob_params, &hardforks);
333 create_chain_config(
334 Some(Chain::mainnet()),
335 &hardforks,
336 Some(MAINNET_DEPOSIT_CONTRACT.address),
337 blob_schedule,
338 )
339}
340
341pub fn blob_params_to_schedule(
343 params: &BlobScheduleBlobParams,
344 hardforks: &ChainHardforks,
345) -> BTreeMap<String, BlobParams> {
346 let mut schedule = BTreeMap::new();
347 schedule.insert("cancun".to_string(), params.cancun);
348 schedule.insert("prague".to_string(), params.prague);
349 schedule.insert("osaka".to_string(), params.osaka);
350
351 let bpo_forks = EthereumHardfork::bpo_variants();
353 for (timestamp, blob_params) in ¶ms.scheduled {
354 for bpo_fork in bpo_forks {
355 if let ForkCondition::Timestamp(fork_ts) = hardforks.fork(bpo_fork) &&
356 fork_ts == *timestamp
357 {
358 schedule.insert(bpo_fork.name().to_lowercase(), *blob_params);
359 break;
360 }
361 }
362 }
363
364 schedule
365}
366
367#[derive(Clone, Debug, PartialEq, Eq)]
370pub enum BaseFeeParamsKind {
371 Constant(BaseFeeParams),
373 Variable(ForkBaseFeeParams),
376}
377
378impl Default for BaseFeeParamsKind {
379 fn default() -> Self {
380 BaseFeeParams::ethereum().into()
381 }
382}
383
384impl From<BaseFeeParams> for BaseFeeParamsKind {
385 fn from(params: BaseFeeParams) -> Self {
386 Self::Constant(params)
387 }
388}
389
390impl From<ForkBaseFeeParams> for BaseFeeParamsKind {
391 fn from(params: ForkBaseFeeParams) -> Self {
392 Self::Variable(params)
393 }
394}
395
396#[derive(Clone, Debug, PartialEq, Eq, From)]
399pub struct ForkBaseFeeParams(Vec<(Box<dyn Hardfork>, BaseFeeParams)>);
400
401impl<H: BlockHeader> core::ops::Deref for ChainSpec<H> {
402 type Target = ChainHardforks;
403
404 fn deref(&self) -> &Self::Target {
405 &self.hardforks
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
417pub struct ChainSpec<H: BlockHeader = Header> {
418 pub chain: Chain,
420
421 pub genesis: Genesis,
423
424 pub genesis_header: SealedHeader<H>,
426
427 pub paris_block_and_final_difficulty: Option<(u64, U256)>,
430
431 pub hardforks: ChainHardforks,
433
434 pub deposit_contract: Option<DepositContract>,
436
437 pub base_fee_params: BaseFeeParamsKind,
439
440 pub prune_delete_limit: usize,
442
443 pub blob_params: BlobScheduleBlobParams,
445}
446
447impl<H: BlockHeader> Default for ChainSpec<H> {
448 fn default() -> Self {
449 Self {
450 chain: Default::default(),
451 genesis: Default::default(),
452 genesis_header: Default::default(),
453 paris_block_and_final_difficulty: Default::default(),
454 hardforks: Default::default(),
455 deposit_contract: Default::default(),
456 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
457 prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
458 blob_params: Default::default(),
459 }
460 }
461}
462
463impl ChainSpec {
464 pub fn from_genesis(genesis: Genesis) -> Self {
466 genesis.into()
467 }
468
469 pub fn builder() -> ChainSpecBuilder {
471 ChainSpecBuilder::default()
472 }
473
474 pub fn from_chain_id(chain_id: u64) -> Option<Arc<Self>> {
476 match NamedChain::try_from(chain_id).ok()? {
477 NamedChain::Mainnet => Some(MAINNET.clone()),
478 NamedChain::Sepolia => Some(SEPOLIA.clone()),
479 NamedChain::Holesky => Some(HOLESKY.clone()),
480 NamedChain::Hoodi => Some(HOODI.clone()),
481 NamedChain::Dev => Some(DEV.clone()),
482 _ => None,
483 }
484 }
485}
486
487impl<H: BlockHeader> ChainSpec<H> {
488 pub const fn chain(&self) -> Chain {
490 self.chain
491 }
492
493 #[inline]
495 pub const fn is_ethereum(&self) -> bool {
496 self.chain.is_ethereum()
497 }
498
499 #[inline]
501 pub fn is_optimism_mainnet(&self) -> bool {
502 self.chain == Chain::optimism_mainnet()
503 }
504
505 #[inline]
507 pub fn paris_block(&self) -> Option<u64> {
508 self.paris_block_and_final_difficulty.map(|(block, _)| block)
509 }
510
511 pub const fn genesis(&self) -> &Genesis {
515 &self.genesis
516 }
517
518 pub fn genesis_header(&self) -> &H {
520 &self.genesis_header
521 }
522
523 pub fn sealed_genesis_header(&self) -> SealedHeader<H> {
525 SealedHeader::new(self.genesis_header().clone(), self.genesis_hash())
526 }
527
528 pub fn initial_base_fee(&self) -> Option<u64> {
530 let genesis_base_fee =
532 self.genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(INITIAL_BASE_FEE);
533
534 self.hardforks.fork(EthereumHardfork::London).active_at_block(0).then_some(genesis_base_fee)
536 }
537
538 pub fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
540 match self.base_fee_params {
541 BaseFeeParamsKind::Constant(bf_params) => bf_params,
542 BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => {
543 for (fork, params) in bf_params.iter().rev() {
547 if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) {
548 return *params
549 }
550 }
551
552 bf_params.first().map(|(_, params)| *params).unwrap_or_else(BaseFeeParams::ethereum)
553 }
554 }
555 }
556
557 pub fn genesis_hash(&self) -> B256 {
559 self.genesis_header.hash()
560 }
561
562 pub const fn genesis_timestamp(&self) -> u64 {
564 self.genesis.timestamp
565 }
566
567 pub fn get_final_paris_total_difficulty(&self) -> Option<U256> {
569 self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty)
570 }
571
572 pub fn hardfork_fork_filter<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkFilter> {
574 match self.hardforks.fork(fork.clone()) {
575 ForkCondition::Never => None,
576 _ => Some(self.fork_filter(self.satisfy(self.hardforks.fork(fork)))),
577 }
578 }
579
580 pub fn display_hardforks(&self) -> DisplayHardforks {
582 let hardforks_with_meta = self.hardforks.forks_iter().map(|(fork, condition)| {
584 let metadata = match condition {
586 ForkCondition::Timestamp(timestamp) => {
587 EthChainSpec::blob_params_at_timestamp(self, timestamp).map(|params| {
590 format!(
591 "blob: (target: {}, max: {}, fraction: {})",
592 params.target_blob_count, params.max_blob_count, params.update_fraction
593 )
594 })
595 }
596 _ => None,
597 };
598 (fork, condition, metadata)
599 });
600
601 DisplayHardforks::with_meta(hardforks_with_meta)
602 }
603
604 #[inline]
606 pub fn hardfork_fork_id<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkId> {
607 let condition = self.hardforks.fork(fork);
608 match condition {
609 ForkCondition::Never => None,
610 _ => Some(self.fork_id(&self.satisfy(condition))),
611 }
612 }
613
614 #[inline]
617 pub fn shanghai_fork_id(&self) -> Option<ForkId> {
618 self.hardfork_fork_id(EthereumHardfork::Shanghai)
619 }
620
621 #[inline]
624 pub fn cancun_fork_id(&self) -> Option<ForkId> {
625 self.hardfork_fork_id(EthereumHardfork::Cancun)
626 }
627
628 #[inline]
631 pub fn latest_fork_id(&self) -> ForkId {
632 self.hardfork_fork_id(self.hardforks.last().unwrap().0).unwrap()
633 }
634
635 pub fn fork_filter(&self, head: Head) -> ForkFilter {
637 let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| {
638 Some(match condition {
641 ForkCondition::Block(block) |
642 ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block),
643 ForkCondition::Timestamp(time) => ForkFilterKey::Time(time),
644 _ => return None,
645 })
646 });
647
648 ForkFilter::new(head, self.genesis_hash(), self.genesis_timestamp(), forks)
649 }
650
651 pub fn fork_id(&self, head: &Head) -> ForkId {
663 let mut forkhash = ForkHash::from(self.genesis_hash());
664
665 let mut current_applied = 0;
671
672 for (_, cond) in self.hardforks.forks_iter() {
674 if let ForkCondition::Block(block) |
677 ForkCondition::TTD { fork_block: Some(block), .. } = cond
678 {
679 if head.number >= block {
680 if block != current_applied {
682 forkhash += block;
683 current_applied = block;
684 }
685 } else {
686 return ForkId { hash: forkhash, next: block }
689 }
690 }
691 }
692
693 for timestamp in self.hardforks.forks_iter().filter_map(|(_, cond)| {
697 cond.as_timestamp().filter(|time| time > &self.genesis.timestamp)
699 }) {
700 if head.timestamp >= timestamp {
701 if timestamp != current_applied {
703 forkhash += timestamp;
704 current_applied = timestamp;
705 }
706 } else {
707 return ForkId { hash: forkhash, next: timestamp }
711 }
712 }
713
714 ForkId { hash: forkhash, next: 0 }
715 }
716
717 pub(crate) fn satisfy(&self, cond: ForkCondition) -> Head {
723 match cond {
724 ForkCondition::Block(number) => Head { number, ..Default::default() },
725 ForkCondition::Timestamp(timestamp) => {
726 Head {
729 timestamp,
730 number: self.last_block_fork_before_merge_or_timestamp().unwrap_or_default(),
731 ..Default::default()
732 }
733 }
734 ForkCondition::TTD { total_difficulty, fork_block, .. } => Head {
735 total_difficulty,
736 number: fork_block.unwrap_or_default(),
737 ..Default::default()
738 },
739 ForkCondition::Never => unreachable!(),
740 }
741 }
742
743 pub(crate) fn last_block_fork_before_merge_or_timestamp(&self) -> Option<u64> {
755 let mut hardforks_iter = self.hardforks.forks_iter().peekable();
756 while let Some((_, curr_cond)) = hardforks_iter.next() {
757 if let Some((_, next_cond)) = hardforks_iter.peek() {
758 match next_cond {
762 ForkCondition::TTD { fork_block: Some(block), .. } => return Some(*block),
765
766 ForkCondition::TTD { .. } | ForkCondition::Timestamp(_) => {
769 if let ForkCondition::Block(block_num) = curr_cond {
772 return Some(block_num);
773 }
774 }
775 ForkCondition::Block(_) | ForkCondition::Never => {}
776 }
777 }
778 }
779 None
780 }
781
782 pub fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
784 use NamedChain as C;
785
786 match self.chain.try_into().ok()? {
787 C::Mainnet => Some(mainnet_nodes()),
788 C::Sepolia => Some(sepolia_nodes()),
789 C::Holesky => Some(holesky_nodes()),
790 C::Hoodi => Some(hoodi_nodes()),
791 _ => None,
792 }
793 }
794
795 pub fn map_header<NewH: BlockHeader>(self, f: impl FnOnce(H) -> NewH) -> ChainSpec<NewH> {
797 let Self {
798 chain,
799 genesis,
800 genesis_header,
801 paris_block_and_final_difficulty,
802 hardforks,
803 deposit_contract,
804 base_fee_params,
805 prune_delete_limit,
806 blob_params,
807 } = self;
808 ChainSpec {
809 chain,
810 genesis,
811 genesis_header: SealedHeader::new_unhashed(f(genesis_header.into_header())),
812 paris_block_and_final_difficulty,
813 hardforks,
814 deposit_contract,
815 base_fee_params,
816 prune_delete_limit,
817 blob_params,
818 }
819 }
820}
821
822impl From<Genesis> for ChainSpec {
823 fn from(genesis: Genesis) -> Self {
824 let hardfork_opts = [
826 (EthereumHardfork::Frontier.boxed(), Some(0)),
827 (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
828 (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block),
829 (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
830 (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
831 (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
832 (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
833 (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
834 (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
835 (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
836 (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
837 (EthereumHardfork::London.boxed(), genesis.config.london_block),
838 (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
839 (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
840 ];
841 let mut hardforks = hardfork_opts
842 .into_iter()
843 .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
844 .collect::<Vec<_>>();
845
846 let paris_block_and_final_difficulty = if let Some(ttd) =
850 genesis.config.terminal_total_difficulty
851 {
852 hardforks.push((
853 EthereumHardfork::Paris.boxed(),
854 ForkCondition::TTD {
855 activation_block_number: genesis
858 .config
859 .merge_netsplit_block
860 .or_else(|| {
861 match genesis.config.chain_id {
869 1 if ttd == MAINNET_PARIS_TTD => return Some(MAINNET_PARIS_BLOCK),
870 11155111 if ttd == SEPOLIA_PARIS_TTD => {
871 return Some(SEPOLIA_PARIS_BLOCK)
872 }
873 _ => {}
874 };
875 None
876 })
877 .unwrap_or_default(),
878 total_difficulty: ttd,
879 fork_block: genesis.config.merge_netsplit_block,
880 },
881 ));
882
883 genesis.config.merge_netsplit_block.map(|block| (block, ttd))
884 } else {
885 None
886 };
887
888 let time_hardfork_opts = [
890 (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time),
891 (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
892 (EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
893 (EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time),
894 (EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time),
895 (EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time),
896 (EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
897 (EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time),
898 (EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time),
899 (EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
900 (EthereumHardfork::Bogota.boxed(), genesis.config.bogota_time),
901 ];
902
903 let mut time_hardforks = time_hardfork_opts
904 .into_iter()
905 .filter_map(|(hardfork, opt)| {
906 opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
907 })
908 .collect::<Vec<_>>();
909
910 hardforks.append(&mut time_hardforks);
911
912 let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
914 let mainnet_order = mainnet_hardforks.forks_iter();
915
916 let mut ordered_hardforks = Vec::with_capacity(hardforks.len());
917 for (hardfork, _) in mainnet_order {
918 if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) {
919 ordered_hardforks.push(hardforks.remove(pos));
920 }
921 }
922
923 ordered_hardforks.append(&mut hardforks);
925
926 let blob_params = genesis.config.blob_schedule_blob_params();
928
929 let deposit_contract = genesis.config.deposit_contract_address.map(|address| {
934 DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic }
935 });
936
937 let hardforks = ChainHardforks::new(ordered_hardforks);
938
939 Self {
940 chain: genesis.config.chain_id.into(),
941 genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis, &hardforks)),
942 genesis,
943 hardforks,
944 paris_block_and_final_difficulty,
945 deposit_contract,
946 blob_params,
947 ..Default::default()
948 }
949 }
950}
951
952impl<H: BlockHeader> Hardforks for ChainSpec<H> {
953 fn fork<HF: Hardfork>(&self, fork: HF) -> ForkCondition {
954 self.hardforks.fork(fork)
955 }
956
957 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
958 self.hardforks.forks_iter()
959 }
960
961 fn fork_id(&self, head: &Head) -> ForkId {
962 self.fork_id(head)
963 }
964
965 fn latest_fork_id(&self) -> ForkId {
966 self.latest_fork_id()
967 }
968
969 fn fork_filter(&self, head: Head) -> ForkFilter {
970 self.fork_filter(head)
971 }
972}
973
974impl<H: BlockHeader> EthereumHardforks for ChainSpec<H> {
975 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
976 self.fork(fork)
977 }
978}
979
980#[auto_impl::auto_impl(&, Arc)]
982pub trait ChainSpecProvider: Debug + Send {
983 type ChainSpec: EthChainSpec + 'static;
985
986 fn chain_spec(&self) -> Arc<Self::ChainSpec>;
988}
989
990#[derive(Debug, Default, Clone)]
992pub struct ChainSpecBuilder {
993 chain: Option<Chain>,
994 genesis: Option<Genesis>,
995 hardforks: ChainHardforks,
996}
997
998impl ChainSpecBuilder {
999 pub fn mainnet() -> Self {
1001 Self {
1002 chain: Some(MAINNET.chain),
1003 genesis: Some(MAINNET.genesis.clone()),
1004 hardforks: MAINNET.hardforks.clone(),
1005 }
1006 }
1007}
1008
1009impl ChainSpecBuilder {
1010 pub const fn chain(mut self, chain: Chain) -> Self {
1012 self.chain = Some(chain);
1013 self
1014 }
1015
1016 pub fn reset(mut self) -> Self {
1018 self.hardforks = ChainHardforks::default();
1019 self
1020 }
1021
1022 pub fn genesis(mut self, genesis: Genesis) -> Self {
1024 self.genesis = Some(genesis);
1025 self
1026 }
1027
1028 pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self {
1030 self.hardforks.insert(fork, condition);
1031 self
1032 }
1033
1034 pub fn with_forks(mut self, forks: ChainHardforks) -> Self {
1036 self.hardforks = forks;
1037 self
1038 }
1039
1040 pub fn without_fork<H: Hardfork>(mut self, fork: H) -> Self {
1042 self.hardforks.remove(&fork);
1043 self
1044 }
1045
1046 pub fn paris_at_ttd(self, ttd: U256, activation_block_number: BlockNumber) -> Self {
1050 self.with_fork(
1051 EthereumHardfork::Paris,
1052 ForkCondition::TTD { activation_block_number, total_difficulty: ttd, fork_block: None },
1053 )
1054 }
1055
1056 pub fn frontier_activated(mut self) -> Self {
1058 self.hardforks.insert(EthereumHardfork::Frontier, ForkCondition::Block(0));
1059 self
1060 }
1061
1062 pub fn dao_activated(mut self) -> Self {
1064 self = self.frontier_activated();
1065 self.hardforks.insert(EthereumHardfork::Dao, ForkCondition::Block(0));
1066 self
1067 }
1068
1069 pub fn homestead_activated(mut self) -> Self {
1071 self = self.dao_activated();
1072 self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0));
1073 self
1074 }
1075
1076 pub fn tangerine_whistle_activated(mut self) -> Self {
1078 self = self.homestead_activated();
1079 self.hardforks.insert(EthereumHardfork::Tangerine, ForkCondition::Block(0));
1080 self
1081 }
1082
1083 pub fn spurious_dragon_activated(mut self) -> Self {
1085 self = self.tangerine_whistle_activated();
1086 self.hardforks.insert(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0));
1087 self
1088 }
1089
1090 pub fn byzantium_activated(mut self) -> Self {
1092 self = self.spurious_dragon_activated();
1093 self.hardforks.insert(EthereumHardfork::Byzantium, ForkCondition::Block(0));
1094 self
1095 }
1096
1097 pub fn constantinople_activated(mut self) -> Self {
1099 self = self.byzantium_activated();
1100 self.hardforks.insert(EthereumHardfork::Constantinople, ForkCondition::Block(0));
1101 self
1102 }
1103
1104 pub fn petersburg_activated(mut self) -> Self {
1106 self = self.constantinople_activated();
1107 self.hardforks.insert(EthereumHardfork::Petersburg, ForkCondition::Block(0));
1108 self
1109 }
1110
1111 pub fn istanbul_activated(mut self) -> Self {
1113 self = self.petersburg_activated();
1114 self.hardforks.insert(EthereumHardfork::Istanbul, ForkCondition::Block(0));
1115 self
1116 }
1117
1118 pub fn muirglacier_activated(mut self) -> Self {
1120 self = self.istanbul_activated();
1121 self.hardforks.insert(EthereumHardfork::MuirGlacier, ForkCondition::Block(0));
1122 self
1123 }
1124
1125 pub fn berlin_activated(mut self) -> Self {
1127 self = self.muirglacier_activated();
1128 self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0));
1129 self
1130 }
1131
1132 pub fn london_activated(mut self) -> Self {
1134 self = self.berlin_activated();
1135 self.hardforks.insert(EthereumHardfork::London, ForkCondition::Block(0));
1136 self
1137 }
1138
1139 pub fn arrowglacier_activated(mut self) -> Self {
1141 self = self.london_activated();
1142 self.hardforks.insert(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0));
1143 self
1144 }
1145
1146 pub fn grayglacier_activated(mut self) -> Self {
1148 self = self.arrowglacier_activated();
1149 self.hardforks.insert(EthereumHardfork::GrayGlacier, ForkCondition::Block(0));
1150 self
1151 }
1152
1153 pub fn paris_activated(mut self) -> Self {
1155 self = self.grayglacier_activated();
1156 self.hardforks.insert(
1157 EthereumHardfork::Paris,
1158 ForkCondition::TTD {
1159 activation_block_number: 0,
1160 total_difficulty: U256::ZERO,
1161 fork_block: None,
1162 },
1163 );
1164 self
1165 }
1166
1167 pub fn shanghai_activated(mut self) -> Self {
1169 self = self.paris_activated();
1170 self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
1171 self
1172 }
1173
1174 pub fn cancun_activated(mut self) -> Self {
1176 self = self.shanghai_activated();
1177 self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
1178 self
1179 }
1180
1181 pub fn prague_activated(mut self) -> Self {
1183 self = self.cancun_activated();
1184 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(0));
1185 self
1186 }
1187
1188 pub fn with_prague_at(mut self, timestamp: u64) -> Self {
1190 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(timestamp));
1191 self
1192 }
1193
1194 pub fn osaka_activated(mut self) -> Self {
1196 self = self.prague_activated();
1197 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(0));
1198 self
1199 }
1200
1201 pub fn with_osaka_at(mut self, timestamp: u64) -> Self {
1203 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(timestamp));
1204 self
1205 }
1206
1207 pub fn amsterdam_activated(mut self) -> Self {
1209 self = self.osaka_activated();
1210 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
1211 self
1212 }
1213
1214 pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
1216 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
1217 self
1218 }
1219
1220 pub fn bogota_activated(mut self) -> Self {
1222 self = self.amsterdam_activated();
1223 self.hardforks.insert(EthereumHardfork::Bogota, ForkCondition::Timestamp(0));
1224 self
1225 }
1226
1227 pub fn with_bogota_at(mut self, timestamp: u64) -> Self {
1229 self.hardforks.insert(EthereumHardfork::Bogota, ForkCondition::Timestamp(timestamp));
1230 self
1231 }
1232
1233 pub fn build(self) -> ChainSpec {
1240 let paris_block_and_final_difficulty = {
1241 self.hardforks.get(EthereumHardfork::Paris).and_then(|cond| {
1242 if let ForkCondition::TTD { total_difficulty, activation_block_number, .. } = cond {
1243 Some((activation_block_number, total_difficulty))
1244 } else {
1245 None
1246 }
1247 })
1248 };
1249 let genesis = self.genesis.expect("The genesis is required");
1250 ChainSpec {
1251 chain: self.chain.expect("The chain is required"),
1252 genesis_header: SealedHeader::new_unhashed(make_genesis_header(
1253 &genesis,
1254 &self.hardforks,
1255 )),
1256 genesis,
1257 hardforks: self.hardforks,
1258 paris_block_and_final_difficulty,
1259 deposit_contract: None,
1260 ..Default::default()
1261 }
1262 }
1263}
1264
1265impl From<&Arc<ChainSpec>> for ChainSpecBuilder {
1266 fn from(value: &Arc<ChainSpec>) -> Self {
1267 Self {
1268 chain: Some(value.chain),
1269 genesis: Some(value.genesis.clone()),
1270 hardforks: value.hardforks.clone(),
1271 }
1272 }
1273}
1274
1275impl<H: BlockHeader> EthExecutorSpec for ChainSpec<H> {
1276 fn deposit_contract_address(&self) -> Option<Address> {
1277 self.deposit_contract.map(|deposit_contract| deposit_contract.address)
1278 }
1279}
1280
1281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1283pub struct DepositContract {
1284 pub address: Address,
1286 pub block: BlockNumber,
1288 pub topic: B256,
1290}
1291
1292impl DepositContract {
1293 pub const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
1295 Self { address, block, topic }
1296 }
1297}
1298
1299#[cfg(any(test, feature = "test-utils"))]
1301pub fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
1302 for (block, expected_id) in cases {
1303 let computed_id = spec.fork_id(block);
1304 assert_eq!(
1305 expected_id, &computed_id,
1306 "Expected fork ID {:?}, computed fork ID {:?} at block {}",
1307 expected_id, computed_id, block.number
1308 );
1309 }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314 use super::*;
1315 use alloy_chains::Chain;
1316 use alloy_consensus::constants::ETH_TO_WEI;
1317 use alloy_eips::{eip4844::BLOB_TX_MIN_BLOB_GASPRICE, eip7840::BlobParams};
1318 use alloy_evm::block::calc::{base_block_reward, block_reward};
1319 use alloy_genesis::{ChainConfig, GenesisAccount};
1320 use alloy_primitives::{b256, hex};
1321 use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH};
1322 use core::ops::Deref;
1323 use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head};
1324 use std::{collections::HashMap, str::FromStr};
1325
1326 fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) {
1327 for (hardfork, expected_id) in cases {
1328 if let Some(computed_id) = spec.hardfork_fork_id(*hardfork) {
1329 assert_eq!(
1330 expected_id, &computed_id,
1331 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for hardfork {hardfork}"
1332 );
1333 if matches!(hardfork, EthereumHardfork::Shanghai) {
1334 if let Some(shanghai_id) = spec.shanghai_fork_id() {
1335 assert_eq!(
1336 expected_id, &shanghai_id,
1337 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for Shanghai hardfork"
1338 );
1339 } else {
1340 panic!("Expected ForkCondition to return Some for Hardfork::Shanghai");
1341 }
1342 }
1343 }
1344 }
1345 }
1346
1347 #[test]
1348 fn test_hardfork_list_display_mainnet() {
1349 assert_eq!(
1350 MAINNET.display_hardforks().to_string(),
1351 "Pre-merge hard forks (block based):
1352- Frontier @0
1353- Homestead @1150000
1354- Dao @1920000
1355- Tangerine @2463000
1356- SpuriousDragon @2675000
1357- Byzantium @4370000
1358- Constantinople @7280000
1359- Petersburg @7280000
1360- Istanbul @9069000
1361- MuirGlacier @9200000
1362- Berlin @12244000
1363- London @12965000
1364- ArrowGlacier @13773000
1365- GrayGlacier @15050000
1366Merge hard forks:
1367- Paris @58750000000000000000000 (network is known to be merged)
1368Post-merge hard forks (timestamp based):
1369- Shanghai @1681338455
1370- Cancun @1710338135 blob: (target: 3, max: 6, fraction: 3338477)
1371- Prague @1746612311 blob: (target: 6, max: 9, fraction: 5007716)
1372- Osaka @1764798551 blob: (target: 6, max: 9, fraction: 5007716)
1373- Bpo1 @1765290071 blob: (target: 10, max: 15, fraction: 8346193)
1374- Bpo2 @1767747671 blob: (target: 14, max: 21, fraction: 11684671)"
1375 );
1376 }
1377
1378 #[test]
1379 fn test_hardfork_list_ignores_disabled_forks() {
1380 let spec = ChainSpec::builder()
1381 .chain(Chain::mainnet())
1382 .genesis(Genesis::default())
1383 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1384 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Never)
1385 .build();
1386 assert_eq!(
1387 spec.display_hardforks().to_string(),
1388 "Pre-merge hard forks (block based):
1389- Frontier @0"
1390 );
1391 }
1392
1393 #[test]
1395 fn ignores_genesis_fork_blocks() {
1396 let spec = ChainSpec::builder()
1397 .chain(Chain::mainnet())
1398 .genesis(Genesis::default())
1399 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1400 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(0))
1401 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(0))
1402 .with_fork(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0))
1403 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(0))
1404 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(0))
1405 .with_fork(EthereumHardfork::Istanbul, ForkCondition::Block(0))
1406 .with_fork(EthereumHardfork::MuirGlacier, ForkCondition::Block(0))
1407 .with_fork(EthereumHardfork::Berlin, ForkCondition::Block(0))
1408 .with_fork(EthereumHardfork::London, ForkCondition::Block(0))
1409 .with_fork(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0))
1410 .with_fork(EthereumHardfork::GrayGlacier, ForkCondition::Block(0))
1411 .build();
1412
1413 assert_eq!(spec.deref().len(), 12, "12 forks should be active.");
1414 assert_eq!(
1415 spec.fork_id(&Head { number: 1, ..Default::default() }),
1416 ForkId { hash: ForkHash::from(spec.genesis_hash()), next: 0 },
1417 "the fork ID should be the genesis hash; forks at genesis are ignored for fork filters"
1418 );
1419 }
1420
1421 #[test]
1422 fn ignores_duplicate_fork_blocks() {
1423 let empty_genesis = Genesis::default();
1424 let unique_spec = ChainSpec::builder()
1425 .chain(Chain::mainnet())
1426 .genesis(empty_genesis.clone())
1427 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1428 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1429 .build();
1430
1431 let duplicate_spec = ChainSpec::builder()
1432 .chain(Chain::mainnet())
1433 .genesis(empty_genesis)
1434 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1435 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1436 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(1))
1437 .build();
1438
1439 assert_eq!(
1440 unique_spec.fork_id(&Head { number: 2, ..Default::default() }),
1441 duplicate_spec.fork_id(&Head { number: 2, ..Default::default() }),
1442 "duplicate fork blocks should be deduplicated for fork filters"
1443 );
1444 }
1445
1446 #[test]
1447 fn test_chainspec_satisfy() {
1448 let empty_genesis = Genesis::default();
1449 let happy_path_case = ChainSpec::builder()
1451 .chain(Chain::mainnet())
1452 .genesis(empty_genesis.clone())
1453 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1454 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1455 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1456 .build();
1457 let happy_path_head = happy_path_case.satisfy(ForkCondition::Timestamp(11313123));
1458 let happy_path_expected = Head { number: 73, timestamp: 11313123, ..Default::default() };
1459 assert_eq!(
1460 happy_path_head, happy_path_expected,
1461 "expected satisfy() to return {happy_path_expected:#?}, but got {happy_path_head:#?} "
1462 );
1463 let multiple_timestamp_fork_case = ChainSpec::builder()
1465 .chain(Chain::mainnet())
1466 .genesis(empty_genesis.clone())
1467 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1468 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1469 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1470 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(11313398))
1471 .build();
1472 let multi_timestamp_head =
1473 multiple_timestamp_fork_case.satisfy(ForkCondition::Timestamp(11313398));
1474 let mult_timestamp_expected =
1475 Head { number: 73, timestamp: 11313398, ..Default::default() };
1476 assert_eq!(
1477 multi_timestamp_head, mult_timestamp_expected,
1478 "expected satisfy() to return {mult_timestamp_expected:#?}, but got {multi_timestamp_head:#?} "
1479 );
1480 let no_block_fork_case = ChainSpec::builder()
1482 .chain(Chain::mainnet())
1483 .genesis(empty_genesis.clone())
1484 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1485 .build();
1486 let no_block_fork_head = no_block_fork_case.satisfy(ForkCondition::Timestamp(11313123));
1487 let no_block_fork_expected = Head { number: 0, timestamp: 11313123, ..Default::default() };
1488 assert_eq!(
1489 no_block_fork_head, no_block_fork_expected,
1490 "expected satisfy() to return {no_block_fork_expected:#?}, but got {no_block_fork_head:#?} ",
1491 );
1492 let fork_cond_ttd_blocknum_case = ChainSpec::builder()
1494 .chain(Chain::mainnet())
1495 .genesis(empty_genesis.clone())
1496 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1497 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1498 .with_fork(
1499 EthereumHardfork::Paris,
1500 ForkCondition::TTD {
1501 activation_block_number: 101,
1502 fork_block: Some(101),
1503 total_difficulty: U256::from(10_790_000),
1504 },
1505 )
1506 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1507 .build();
1508 let fork_cond_ttd_blocknum_head =
1509 fork_cond_ttd_blocknum_case.satisfy(ForkCondition::Timestamp(11313123));
1510 let fork_cond_ttd_blocknum_expected =
1511 Head { number: 101, timestamp: 11313123, ..Default::default() };
1512 assert_eq!(
1513 fork_cond_ttd_blocknum_head, fork_cond_ttd_blocknum_expected,
1514 "expected satisfy() to return {fork_cond_ttd_blocknum_expected:#?}, but got {fork_cond_ttd_blocknum_head:#?} ",
1515 );
1516
1517 let fork_cond_block_only_case = ChainSpec::builder()
1521 .chain(Chain::mainnet())
1522 .genesis(empty_genesis)
1523 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1524 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1525 .build();
1526 let fork_cond_block_only_head = fork_cond_block_only_case.satisfy(ForkCondition::Block(73));
1527 let fork_cond_block_only_expected = Head { number: 73, ..Default::default() };
1528 assert_eq!(
1529 fork_cond_block_only_head, fork_cond_block_only_expected,
1530 "expected satisfy() to return {fork_cond_block_only_expected:#?}, but got {fork_cond_block_only_head:#?} ",
1531 );
1532 let fork_cond_ttd_no_new_spec = fork_cond_block_only_case.satisfy(ForkCondition::TTD {
1535 activation_block_number: 101,
1536 fork_block: None,
1537 total_difficulty: U256::from(10_790_000),
1538 });
1539 let fork_cond_ttd_no_new_spec_expected =
1540 Head { total_difficulty: U256::from(10_790_000), ..Default::default() };
1541 assert_eq!(
1542 fork_cond_ttd_no_new_spec, fork_cond_ttd_no_new_spec_expected,
1543 "expected satisfy() to return {fork_cond_ttd_no_new_spec_expected:#?}, but got {fork_cond_ttd_no_new_spec:#?} ",
1544 );
1545 }
1546
1547 #[test]
1548 fn mainnet_hardfork_fork_ids() {
1549 test_hardfork_fork_ids(
1550 &MAINNET,
1551 &[
1552 (
1553 EthereumHardfork::Frontier,
1554 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1555 ),
1556 (
1557 EthereumHardfork::Homestead,
1558 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1559 ),
1560 (
1561 EthereumHardfork::Dao,
1562 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1563 ),
1564 (
1565 EthereumHardfork::Tangerine,
1566 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1567 ),
1568 (
1569 EthereumHardfork::SpuriousDragon,
1570 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1571 ),
1572 (
1573 EthereumHardfork::Byzantium,
1574 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1575 ),
1576 (
1577 EthereumHardfork::Constantinople,
1578 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1579 ),
1580 (
1581 EthereumHardfork::Petersburg,
1582 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1583 ),
1584 (
1585 EthereumHardfork::Istanbul,
1586 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1587 ),
1588 (
1589 EthereumHardfork::MuirGlacier,
1590 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1591 ),
1592 (
1593 EthereumHardfork::Berlin,
1594 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1595 ),
1596 (
1597 EthereumHardfork::London,
1598 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1599 ),
1600 (
1601 EthereumHardfork::ArrowGlacier,
1602 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1603 ),
1604 (
1605 EthereumHardfork::GrayGlacier,
1606 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1607 ),
1608 (
1609 EthereumHardfork::Shanghai,
1610 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1611 ),
1612 (
1613 EthereumHardfork::Cancun,
1614 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1615 ),
1616 (
1617 EthereumHardfork::Prague,
1618 ForkId {
1619 hash: ForkHash(hex!("0xc376cf8b")),
1620 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1621 },
1622 ),
1623 ],
1624 );
1625 }
1626
1627 #[test]
1628 fn sepolia_hardfork_fork_ids() {
1629 test_hardfork_fork_ids(
1630 &SEPOLIA,
1631 &[
1632 (
1633 EthereumHardfork::Frontier,
1634 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1635 ),
1636 (
1637 EthereumHardfork::Homestead,
1638 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1639 ),
1640 (
1641 EthereumHardfork::Tangerine,
1642 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1643 ),
1644 (
1645 EthereumHardfork::SpuriousDragon,
1646 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1647 ),
1648 (
1649 EthereumHardfork::Byzantium,
1650 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1651 ),
1652 (
1653 EthereumHardfork::Constantinople,
1654 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1655 ),
1656 (
1657 EthereumHardfork::Petersburg,
1658 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1659 ),
1660 (
1661 EthereumHardfork::Istanbul,
1662 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1663 ),
1664 (
1665 EthereumHardfork::Berlin,
1666 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1667 ),
1668 (
1669 EthereumHardfork::London,
1670 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1671 ),
1672 (
1673 EthereumHardfork::Paris,
1674 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1675 ),
1676 (
1677 EthereumHardfork::Shanghai,
1678 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1679 ),
1680 (
1681 EthereumHardfork::Cancun,
1682 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1683 ),
1684 (
1685 EthereumHardfork::Prague,
1686 ForkId {
1687 hash: ForkHash(hex!("0xed88b5fd")),
1688 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1689 },
1690 ),
1691 ],
1692 );
1693 }
1694
1695 #[test]
1696 fn mainnet_fork_ids() {
1697 test_fork_ids(
1698 &MAINNET,
1699 &[
1700 (
1701 Head { number: 0, ..Default::default() },
1702 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1703 ),
1704 (
1705 Head { number: 1150000, ..Default::default() },
1706 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1707 ),
1708 (
1709 Head { number: 1920000, ..Default::default() },
1710 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1711 ),
1712 (
1713 Head { number: 2463000, ..Default::default() },
1714 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1715 ),
1716 (
1717 Head { number: 2675000, ..Default::default() },
1718 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1719 ),
1720 (
1721 Head { number: 4370000, ..Default::default() },
1722 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1723 ),
1724 (
1725 Head { number: 7280000, ..Default::default() },
1726 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1727 ),
1728 (
1729 Head { number: 9069000, ..Default::default() },
1730 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1731 ),
1732 (
1733 Head { number: 9200000, ..Default::default() },
1734 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1735 ),
1736 (
1737 Head { number: 12244000, ..Default::default() },
1738 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1739 ),
1740 (
1741 Head { number: 12965000, ..Default::default() },
1742 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1743 ),
1744 (
1745 Head { number: 13773000, ..Default::default() },
1746 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1747 ),
1748 (
1749 Head { number: 15050000, ..Default::default() },
1750 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1751 ),
1752 (
1754 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
1755 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1756 ),
1757 (
1759 Head { number: 20000001, timestamp: 1710338135, ..Default::default() },
1760 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1761 ),
1762 (
1764 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
1765 ForkId {
1766 hash: ForkHash(hex!("0xc376cf8b")),
1767 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1768 },
1769 ),
1770 (
1772 Head {
1773 number: 20000004,
1774 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
1775 ..Default::default()
1776 },
1777 ForkId {
1778 hash: ForkHash(hex!("0x5167e2a6")),
1779 next: mainnet::MAINNET_BPO1_TIMESTAMP,
1780 },
1781 ),
1782 ],
1783 );
1784 }
1785
1786 #[test]
1787 fn hoodi_fork_ids() {
1788 test_fork_ids(
1789 &HOODI,
1790 &[
1791 (
1792 Head { number: 0, ..Default::default() },
1793 ForkId { hash: ForkHash(hex!("0xbef71d30")), next: 1742999832 },
1794 ),
1795 (
1797 Head { number: 0, timestamp: 1742999833, ..Default::default() },
1798 ForkId {
1799 hash: ForkHash(hex!("0x0929e24e")),
1800 next: hoodi::HOODI_OSAKA_TIMESTAMP,
1801 },
1802 ),
1803 (
1805 Head {
1806 number: 0,
1807 timestamp: hoodi::HOODI_OSAKA_TIMESTAMP,
1808 ..Default::default()
1809 },
1810 ForkId {
1811 hash: ForkHash(hex!("0xe7e0e7ff")),
1812 next: hoodi::HOODI_BPO1_TIMESTAMP,
1813 },
1814 ),
1815 ],
1816 )
1817 }
1818
1819 #[test]
1820 fn holesky_fork_ids() {
1821 test_fork_ids(
1822 &HOLESKY,
1823 &[
1824 (
1825 Head { number: 0, ..Default::default() },
1826 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1827 ),
1828 (
1830 Head { number: 123, ..Default::default() },
1831 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1832 ),
1833 (
1835 Head { number: 123, timestamp: 1696000703, ..Default::default() },
1836 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1837 ),
1838 (
1840 Head { number: 123, timestamp: 1696000704, ..Default::default() },
1841 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1842 ),
1843 (
1845 Head { number: 123, timestamp: 1707305663, ..Default::default() },
1846 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1847 ),
1848 (
1850 Head { number: 123, timestamp: 1707305664, ..Default::default() },
1851 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1852 ),
1853 (
1855 Head { number: 123, timestamp: 1740434111, ..Default::default() },
1856 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1857 ),
1858 (
1860 Head { number: 123, timestamp: 1740434112, ..Default::default() },
1861 ForkId {
1862 hash: ForkHash(hex!("0xdfbd9bed")),
1863 next: holesky::HOLESKY_OSAKA_TIMESTAMP,
1864 },
1865 ),
1866 (
1868 Head {
1869 number: 123,
1870 timestamp: holesky::HOLESKY_OSAKA_TIMESTAMP,
1871 ..Default::default()
1872 },
1873 ForkId {
1874 hash: ForkHash(hex!("0x783def52")),
1875 next: holesky::HOLESKY_BPO1_TIMESTAMP,
1876 },
1877 ),
1878 ],
1879 )
1880 }
1881
1882 #[test]
1883 fn sepolia_fork_ids() {
1884 test_fork_ids(
1885 &SEPOLIA,
1886 &[
1887 (
1888 Head { number: 0, ..Default::default() },
1889 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1890 ),
1891 (
1892 Head { number: 1735370, ..Default::default() },
1893 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1894 ),
1895 (
1896 Head { number: 1735371, ..Default::default() },
1897 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1898 ),
1899 (
1900 Head { number: 1735372, timestamp: 1677557087, ..Default::default() },
1901 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1902 ),
1903 (
1905 Head { number: 1735373, timestamp: 1677557088, ..Default::default() },
1906 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1907 ),
1908 (
1910 Head { number: 1735374, timestamp: 1706655071, ..Default::default() },
1911 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1912 ),
1913 (
1915 Head { number: 1735375, timestamp: 1706655072, ..Default::default() },
1916 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1917 ),
1918 (
1920 Head { number: 1735376, timestamp: 1741159775, ..Default::default() },
1921 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1922 ),
1923 (
1925 Head { number: 1735377, timestamp: 1741159776, ..Default::default() },
1926 ForkId {
1927 hash: ForkHash(hex!("0xed88b5fd")),
1928 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1929 },
1930 ),
1931 (
1933 Head {
1934 number: 1735377,
1935 timestamp: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1936 ..Default::default()
1937 },
1938 ForkId {
1939 hash: ForkHash(hex!("0xe2ae4999")),
1940 next: sepolia::SEPOLIA_BPO1_TIMESTAMP,
1941 },
1942 ),
1943 ],
1944 );
1945 }
1946
1947 #[test]
1948 fn dev_fork_ids() {
1949 test_fork_ids(
1950 &DEV,
1951 &[(
1952 Head { number: 0, ..Default::default() },
1953 ForkId { hash: ForkHash(hex!("0x0b1a4ef7")), next: 0 },
1954 )],
1955 )
1956 }
1957
1958 #[test]
1962 fn timestamped_forks() {
1963 let mainnet_with_timestamps = ChainSpecBuilder::mainnet().build();
1964 test_fork_ids(
1965 &mainnet_with_timestamps,
1966 &[
1967 (
1968 Head { number: 0, timestamp: 0, ..Default::default() },
1969 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1970 ), (
1972 Head { number: 1149999, timestamp: 0, ..Default::default() },
1973 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1974 ), (
1976 Head { number: 1150000, timestamp: 0, ..Default::default() },
1977 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1978 ), (
1980 Head { number: 1919999, timestamp: 0, ..Default::default() },
1981 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1982 ), (
1984 Head { number: 1920000, timestamp: 0, ..Default::default() },
1985 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1986 ), (
1988 Head { number: 2462999, timestamp: 0, ..Default::default() },
1989 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1990 ), (
1992 Head { number: 2463000, timestamp: 0, ..Default::default() },
1993 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1994 ), (
1996 Head { number: 2674999, timestamp: 0, ..Default::default() },
1997 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1998 ), (
2000 Head { number: 2675000, timestamp: 0, ..Default::default() },
2001 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
2002 ), (
2004 Head { number: 4369999, timestamp: 0, ..Default::default() },
2005 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
2006 ), (
2008 Head { number: 4370000, timestamp: 0, ..Default::default() },
2009 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
2010 ), (
2012 Head { number: 7279999, timestamp: 0, ..Default::default() },
2013 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
2014 ), (
2016 Head { number: 7280000, timestamp: 0, ..Default::default() },
2017 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2018 ), (
2020 Head { number: 9068999, timestamp: 0, ..Default::default() },
2021 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2022 ), (
2024 Head { number: 9069000, timestamp: 0, ..Default::default() },
2025 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2026 ), (
2028 Head { number: 9199999, timestamp: 0, ..Default::default() },
2029 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2030 ), (
2032 Head { number: 9200000, timestamp: 0, ..Default::default() },
2033 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2034 ), (
2036 Head { number: 12243999, timestamp: 0, ..Default::default() },
2037 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2038 ), (
2040 Head { number: 12244000, timestamp: 0, ..Default::default() },
2041 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2042 ), (
2044 Head { number: 12964999, timestamp: 0, ..Default::default() },
2045 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2046 ), (
2048 Head { number: 12965000, timestamp: 0, ..Default::default() },
2049 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2050 ), (
2052 Head { number: 13772999, timestamp: 0, ..Default::default() },
2053 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2054 ), (
2056 Head { number: 13773000, timestamp: 0, ..Default::default() },
2057 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2058 ), (
2060 Head { number: 15049999, timestamp: 0, ..Default::default() },
2061 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2062 ), (
2064 Head { number: 15050000, timestamp: 0, ..Default::default() },
2065 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2066 ), (
2068 Head { number: 19999999, timestamp: 1667999999, ..Default::default() },
2069 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2070 ), (
2072 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
2073 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2074 ), (
2076 Head { number: 20000001, timestamp: 1710338134, ..Default::default() },
2077 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2078 ), (
2080 Head { number: 20000002, timestamp: 1710338135, ..Default::default() },
2081 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2082 ), (
2084 Head { number: 20000003, timestamp: 1746612310, ..Default::default() },
2085 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2086 ), (
2088 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
2089 ForkId {
2090 hash: ForkHash(hex!("0xc376cf8b")),
2091 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
2092 },
2093 ),
2094 (
2096 Head {
2097 number: 20000004,
2098 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
2099 ..Default::default()
2100 },
2101 ForkId {
2102 hash: ForkHash(hex!("0x5167e2a6")),
2103 next: mainnet::MAINNET_BPO1_TIMESTAMP,
2104 },
2105 ),
2106 ],
2107 );
2108 }
2109
2110 fn construct_chainspec(
2113 builder: ChainSpecBuilder,
2114 shanghai_time: u64,
2115 cancun_time: u64,
2116 ) -> ChainSpec {
2117 builder
2118 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(shanghai_time))
2119 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(cancun_time))
2120 .build()
2121 }
2122
2123 #[test]
2128 fn test_timestamp_fork_in_genesis() {
2129 let timestamp = 1690475657u64;
2130 let default_spec_builder = ChainSpecBuilder::default()
2131 .chain(Chain::from_id(1337))
2132 .genesis(Genesis::default().with_timestamp(timestamp))
2133 .paris_activated();
2134
2135 let tests = [
2138 (
2139 construct_chainspec(default_spec_builder.clone(), timestamp - 1, timestamp + 1),
2140 timestamp + 1,
2141 ),
2142 (
2143 construct_chainspec(default_spec_builder.clone(), timestamp, timestamp + 1),
2144 timestamp + 1,
2145 ),
2146 (
2147 construct_chainspec(default_spec_builder, timestamp + 1, timestamp + 2),
2148 timestamp + 1,
2149 ),
2150 ];
2151
2152 for (spec, expected_timestamp) in tests {
2153 let got_forkid = spec.fork_id(&Head { number: 0, timestamp: 0, ..Default::default() });
2154 let genesis_hash = spec.genesis_hash();
2159 let expected_forkid =
2160 ForkId { hash: ForkHash::from(genesis_hash), next: expected_timestamp };
2161 assert_eq!(got_forkid, expected_forkid);
2162 }
2163 }
2164
2165 #[test]
2167 fn check_terminal_ttd() {
2168 let chainspec = ChainSpecBuilder::mainnet().build();
2169
2170 let terminal_block_ttd = U256::from(58750003716598352816469_u128);
2172 let terminal_block_difficulty = U256::from(11055787484078698_u128);
2173 assert!(!chainspec
2174 .fork(EthereumHardfork::Paris)
2175 .active_at_ttd(terminal_block_ttd, terminal_block_difficulty));
2176
2177 let first_pos_block_ttd = U256::from(58750003716598352816469_u128);
2179 let first_pos_difficulty = U256::ZERO;
2180 assert!(chainspec
2181 .fork(EthereumHardfork::Paris)
2182 .active_at_ttd(first_pos_block_ttd, first_pos_difficulty));
2183 }
2184
2185 #[test]
2186 fn geth_genesis_with_shanghai() {
2187 let geth_genesis = r#"
2188 {
2189 "config": {
2190 "chainId": 1337,
2191 "homesteadBlock": 0,
2192 "eip150Block": 0,
2193 "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2194 "eip155Block": 0,
2195 "eip158Block": 0,
2196 "byzantiumBlock": 0,
2197 "constantinopleBlock": 0,
2198 "petersburgBlock": 0,
2199 "istanbulBlock": 0,
2200 "muirGlacierBlock": 0,
2201 "berlinBlock": 0,
2202 "londonBlock": 0,
2203 "arrowGlacierBlock": 0,
2204 "grayGlacierBlock": 0,
2205 "shanghaiTime": 0,
2206 "cancunTime": 1,
2207 "terminalTotalDifficulty": 0,
2208 "terminalTotalDifficultyPassed": true,
2209 "ethash": {}
2210 },
2211 "nonce": "0x0",
2212 "timestamp": "0x0",
2213 "extraData": "0x",
2214 "gasLimit": "0x4c4b40",
2215 "difficulty": "0x1",
2216 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2217 "coinbase": "0x0000000000000000000000000000000000000000",
2218 "alloc": {
2219 "658bdf435d810c91414ec09147daa6db62406379": {
2220 "balance": "0x487a9a304539440000"
2221 },
2222 "aa00000000000000000000000000000000000000": {
2223 "code": "0x6042",
2224 "storage": {
2225 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2226 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2227 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2228 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2229 },
2230 "balance": "0x1",
2231 "nonce": "0x1"
2232 },
2233 "bb00000000000000000000000000000000000000": {
2234 "code": "0x600154600354",
2235 "storage": {
2236 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2237 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2238 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2239 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2240 },
2241 "balance": "0x2",
2242 "nonce": "0x1"
2243 }
2244 },
2245 "number": "0x0",
2246 "gasUsed": "0x0",
2247 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2248 "baseFeePerGas": "0x3b9aca00"
2249 }
2250 "#;
2251
2252 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
2253 let chainspec = ChainSpec::from(genesis);
2254
2255 assert_eq!(
2257 chainspec.hardforks.get(EthereumHardfork::Homestead).unwrap(),
2258 ForkCondition::Block(0)
2259 );
2260 assert_eq!(
2261 chainspec.hardforks.get(EthereumHardfork::Tangerine).unwrap(),
2262 ForkCondition::Block(0)
2263 );
2264 assert_eq!(
2265 chainspec.hardforks.get(EthereumHardfork::SpuriousDragon).unwrap(),
2266 ForkCondition::Block(0)
2267 );
2268 assert_eq!(
2269 chainspec.hardforks.get(EthereumHardfork::Byzantium).unwrap(),
2270 ForkCondition::Block(0)
2271 );
2272 assert_eq!(
2273 chainspec.hardforks.get(EthereumHardfork::Constantinople).unwrap(),
2274 ForkCondition::Block(0)
2275 );
2276 assert_eq!(
2277 chainspec.hardforks.get(EthereumHardfork::Petersburg).unwrap(),
2278 ForkCondition::Block(0)
2279 );
2280 assert_eq!(
2281 chainspec.hardforks.get(EthereumHardfork::Istanbul).unwrap(),
2282 ForkCondition::Block(0)
2283 );
2284 assert_eq!(
2285 chainspec.hardforks.get(EthereumHardfork::MuirGlacier).unwrap(),
2286 ForkCondition::Block(0)
2287 );
2288 assert_eq!(
2289 chainspec.hardforks.get(EthereumHardfork::Berlin).unwrap(),
2290 ForkCondition::Block(0)
2291 );
2292 assert_eq!(
2293 chainspec.hardforks.get(EthereumHardfork::London).unwrap(),
2294 ForkCondition::Block(0)
2295 );
2296 assert_eq!(
2297 chainspec.hardforks.get(EthereumHardfork::ArrowGlacier).unwrap(),
2298 ForkCondition::Block(0)
2299 );
2300 assert_eq!(
2301 chainspec.hardforks.get(EthereumHardfork::GrayGlacier).unwrap(),
2302 ForkCondition::Block(0)
2303 );
2304
2305 assert_eq!(
2307 chainspec.hardforks.get(EthereumHardfork::Shanghai).unwrap(),
2308 ForkCondition::Timestamp(0)
2309 );
2310
2311 assert_eq!(
2313 chainspec.hardforks.get(EthereumHardfork::Cancun).unwrap(),
2314 ForkCondition::Timestamp(1)
2315 );
2316
2317 let key_rlp = vec![
2319 (
2320 hex!("0x658bdf435d810c91414ec09147daa6db62406379"),
2321 &hex!(
2322 "0xf84d8089487a9a304539440000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
2323 )[..],
2324 ),
2325 (
2326 hex!("0xaa00000000000000000000000000000000000000"),
2327 &hex!(
2328 "0xf8440101a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0ce92c756baff35fa740c3557c1a971fd24d2d35b7c8e067880d50cd86bb0bc99"
2329 )[..],
2330 ),
2331 (
2332 hex!("0xbb00000000000000000000000000000000000000"),
2333 &hex!(
2334 "0xf8440102a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0e25a53cbb501cec2976b393719c63d832423dd70a458731a0b64e4847bbca7d2"
2335 )[..],
2336 ),
2337 ];
2338
2339 for (key, expected_rlp) in key_rlp {
2340 let account = chainspec.genesis.alloc.get(&key).expect("account should exist");
2341 assert_eq!(&alloy_rlp::encode(TrieAccount::from(account.clone())), expected_rlp);
2342 }
2343
2344 let expected_state_root: B256 =
2345 hex!("0x078dc6061b1d8eaa8493384b59c9c65ceb917201221d08b80c4de6770b6ec7e7").into();
2346 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2347
2348 assert_eq!(chainspec.genesis_header().withdrawals_root, Some(EMPTY_ROOT_HASH));
2349
2350 let expected_hash: B256 =
2351 hex!("0x1fc027d65f820d3eef441ebeec139ebe09e471cf98516dce7b5643ccb27f418c").into();
2352 let hash = chainspec.genesis_hash();
2353 assert_eq!(hash, expected_hash);
2354 }
2355
2356 #[test]
2357 fn hive_geth_json() {
2358 let hive_json = r#"
2359 {
2360 "nonce": "0x0000000000000042",
2361 "difficulty": "0x2123456",
2362 "mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
2363 "coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
2364 "timestamp": "0x123456",
2365 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2366 "extraData": "0xfafbfcfd",
2367 "gasLimit": "0x2fefd8",
2368 "alloc": {
2369 "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
2370 "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
2371 },
2372 "e6716f9544a56c530d868e4bfbacb172315bdead": {
2373 "balance": "0x11",
2374 "code": "0x12"
2375 },
2376 "b9c015918bdaba24b4ff057a92a3873d6eb201be": {
2377 "balance": "0x21",
2378 "storage": {
2379 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
2380 }
2381 },
2382 "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
2383 "balance": "0x31",
2384 "nonce": "0x32"
2385 },
2386 "0000000000000000000000000000000000000001": {
2387 "balance": "0x41"
2388 },
2389 "0000000000000000000000000000000000000002": {
2390 "balance": "0x51"
2391 },
2392 "0000000000000000000000000000000000000003": {
2393 "balance": "0x61"
2394 },
2395 "0000000000000000000000000000000000000004": {
2396 "balance": "0x71"
2397 }
2398 },
2399 "config": {
2400 "ethash": {},
2401 "chainId": 10,
2402 "homesteadBlock": 0,
2403 "eip150Block": 0,
2404 "eip155Block": 0,
2405 "eip158Block": 0,
2406 "byzantiumBlock": 0,
2407 "constantinopleBlock": 0,
2408 "petersburgBlock": 0,
2409 "istanbulBlock": 0
2410 }
2411 }
2412 "#;
2413
2414 let genesis = serde_json::from_str::<Genesis>(hive_json).unwrap();
2415 let chainspec: ChainSpec = genesis.into();
2416 assert_eq!(chainspec.chain, Chain::from_named(NamedChain::Optimism));
2417 let expected_state_root: B256 =
2418 hex!("0x9a6049ac535e3dc7436c189eaa81c73f35abd7f282ab67c32944ff0301d63360").into();
2419 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2420 let hard_forks = vec![
2421 EthereumHardfork::Byzantium,
2422 EthereumHardfork::Homestead,
2423 EthereumHardfork::Istanbul,
2424 EthereumHardfork::Petersburg,
2425 EthereumHardfork::Constantinople,
2426 ];
2427 for fork in hard_forks {
2428 assert_eq!(chainspec.hardforks.get(fork).unwrap(), ForkCondition::Block(0));
2429 }
2430
2431 let expected_hash: B256 =
2432 hex!("0x5ae31c6522bd5856129f66be3d582b842e4e9faaa87f21cce547128339a9db3c").into();
2433 let hash = chainspec.genesis_header().hash_slow();
2434 assert_eq!(hash, expected_hash);
2435 }
2436
2437 #[test]
2438 fn test_hive_paris_block_genesis_json() {
2439 let hive_paris = r#"
2442 {
2443 "config": {
2444 "ethash": {},
2445 "chainId": 3503995874084926,
2446 "homesteadBlock": 0,
2447 "eip150Block": 6,
2448 "eip155Block": 12,
2449 "eip158Block": 12,
2450 "byzantiumBlock": 18,
2451 "constantinopleBlock": 24,
2452 "petersburgBlock": 30,
2453 "istanbulBlock": 36,
2454 "muirGlacierBlock": 42,
2455 "berlinBlock": 48,
2456 "londonBlock": 54,
2457 "arrowGlacierBlock": 60,
2458 "grayGlacierBlock": 66,
2459 "mergeNetsplitBlock": 72,
2460 "terminalTotalDifficulty": 9454784,
2461 "shanghaiTime": 780,
2462 "cancunTime": 840
2463 },
2464 "nonce": "0x0",
2465 "timestamp": "0x0",
2466 "extraData": "0x68697665636861696e",
2467 "gasLimit": "0x23f3e20",
2468 "difficulty": "0x20000",
2469 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2470 "coinbase": "0x0000000000000000000000000000000000000000",
2471 "alloc": {
2472 "000f3df6d732807ef1319fb7b8bb8522d0beac02": {
2473 "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
2474 "balance": "0x2a"
2475 },
2476 "0c2c51a0990aee1d73c1228de158688341557508": {
2477 "balance": "0xc097ce7bc90715b34b9f1000000000"
2478 },
2479 "14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
2480 "balance": "0xc097ce7bc90715b34b9f1000000000"
2481 },
2482 "16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
2483 "balance": "0xc097ce7bc90715b34b9f1000000000"
2484 },
2485 "1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
2486 "balance": "0xc097ce7bc90715b34b9f1000000000"
2487 },
2488 "1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
2489 "balance": "0xc097ce7bc90715b34b9f1000000000"
2490 },
2491 "2d389075be5be9f2246ad654ce152cf05990b209": {
2492 "balance": "0xc097ce7bc90715b34b9f1000000000"
2493 },
2494 "3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
2495 "balance": "0xc097ce7bc90715b34b9f1000000000"
2496 },
2497 "4340ee1b812acb40a1eb561c019c327b243b92df": {
2498 "balance": "0xc097ce7bc90715b34b9f1000000000"
2499 },
2500 "4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
2501 "balance": "0xc097ce7bc90715b34b9f1000000000"
2502 },
2503 "4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
2504 "balance": "0xc097ce7bc90715b34b9f1000000000"
2505 },
2506 "5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
2507 "balance": "0xc097ce7bc90715b34b9f1000000000"
2508 },
2509 "654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
2510 "balance": "0xc097ce7bc90715b34b9f1000000000"
2511 },
2512 "717f8aa2b982bee0e29f573d31df288663e1ce16": {
2513 "balance": "0xc097ce7bc90715b34b9f1000000000"
2514 },
2515 "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
2516 "balance": "0xc097ce7bc90715b34b9f1000000000"
2517 },
2518 "83c7e323d189f18725ac510004fdc2941f8c4a78": {
2519 "balance": "0xc097ce7bc90715b34b9f1000000000"
2520 },
2521 "84e75c28348fb86acea1a93a39426d7d60f4cc46": {
2522 "balance": "0xc097ce7bc90715b34b9f1000000000"
2523 },
2524 "8bebc8ba651aee624937e7d897853ac30c95a067": {
2525 "storage": {
2526 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
2527 "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
2528 "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
2529 },
2530 "balance": "0x1",
2531 "nonce": "0x1"
2532 },
2533 "c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
2534 "balance": "0xc097ce7bc90715b34b9f1000000000"
2535 },
2536 "d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
2537 "balance": "0xc097ce7bc90715b34b9f1000000000"
2538 },
2539 "e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
2540 "balance": "0xc097ce7bc90715b34b9f1000000000"
2541 },
2542 "eda8645ba6948855e3b3cd596bbb07596d59c603": {
2543 "balance": "0xc097ce7bc90715b34b9f1000000000"
2544 }
2545 },
2546 "number": "0x0",
2547 "gasUsed": "0x0",
2548 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2549 "baseFeePerGas": null,
2550 "excessBlobGas": null,
2551 "blobGasUsed": null
2552 }
2553 "#;
2554
2555 let genesis: Genesis = serde_json::from_str(hive_paris).unwrap();
2557 let chainspec = ChainSpec::from(genesis);
2558
2559 let expected_forkid = ForkId { hash: ForkHash(hex!("0xbc0c2605")), next: 0 };
2561 let got_forkid =
2562 chainspec.fork_id(&Head { number: 73, timestamp: 840, ..Default::default() });
2563
2564 assert_eq!(got_forkid, expected_forkid);
2566 assert_eq!(chainspec.paris_block_and_final_difficulty, Some((72, U256::from(9454784))));
2568 }
2569
2570 #[test]
2571 fn test_parse_genesis_json() {
2572 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x1337"}"#;
2573 let genesis: Genesis = serde_json::from_str(s).unwrap();
2574 let acc = genesis
2575 .alloc
2576 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2577 .unwrap();
2578 assert_eq!(acc.balance, U256::from(1));
2579 assert_eq!(genesis.base_fee_per_gas, Some(0x1337));
2580 }
2581
2582 #[test]
2583 fn test_parse_cancun_genesis_json() {
2584 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2585 let genesis: Genesis = serde_json::from_str(s).unwrap();
2586 let acc = genesis
2587 .alloc
2588 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2589 .unwrap();
2590 assert_eq!(acc.balance, U256::from(1));
2591 assert_eq!(genesis.config.cancun_time, Some(4661));
2593 }
2594
2595 #[test]
2596 fn test_parse_prague_genesis_all_formats() {
2597 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661, "pragueTime": 4662},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2598 let genesis: Genesis = serde_json::from_str(s).unwrap();
2599
2600 let acc = genesis
2602 .alloc
2603 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2604 .unwrap();
2605 assert_eq!(acc.balance, U256::from(1));
2606 assert_eq!(genesis.config.cancun_time, Some(4661));
2608 assert_eq!(genesis.config.prague_time, Some(4662));
2610 }
2611
2612 #[test]
2613 fn test_parse_cancun_genesis_all_formats() {
2614 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2615 let genesis: Genesis = serde_json::from_str(s).unwrap();
2616
2617 let acc = genesis
2619 .alloc
2620 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2621 .unwrap();
2622 assert_eq!(acc.balance, U256::from(1));
2623 assert_eq!(genesis.config.cancun_time, Some(4661));
2625 }
2626
2627 #[test]
2628 fn test_paris_block_and_total_difficulty() {
2629 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2630 let paris_chainspec = ChainSpecBuilder::default()
2631 .chain(Chain::from_id(1337))
2632 .genesis(genesis)
2633 .paris_activated()
2634 .build();
2635 assert_eq!(paris_chainspec.paris_block_and_final_difficulty, Some((0, U256::ZERO)));
2636 }
2637
2638 #[test]
2639 fn test_default_cancun_header_forkhash() {
2640 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2642 let default_chainspec = ChainSpecBuilder::default()
2643 .chain(Chain::from_id(1337))
2644 .genesis(genesis)
2645 .cancun_activated()
2646 .build();
2647 let mut header = default_chainspec.genesis_header().clone();
2648
2649 header.state_root =
2651 B256::from_str("0x62e2595e017f0ca23e08d17221010721a71c3ae932f4ea3cb12117786bb392d4")
2652 .unwrap();
2653
2654 assert_eq!(header.withdrawals_root, Some(EMPTY_WITHDRAWALS));
2656
2657 assert_eq!(header.parent_beacon_block_root, Some(B256::ZERO));
2660 assert_eq!(header.blob_gas_used, Some(0));
2661 assert_eq!(header.excess_blob_gas, Some(0));
2662
2663 let genesis_hash = header.hash_slow();
2665 let expected_hash =
2666 b256!("0x16bb7c59613a5bad3f7c04a852fd056545ade2483968d9a25a1abb05af0c4d37");
2667 assert_eq!(genesis_hash, expected_hash);
2668
2669 let expected_forkhash = ForkHash(hex!("0x8062457a"));
2671 assert_eq!(ForkHash::from(genesis_hash), expected_forkhash);
2672 }
2673
2674 #[test]
2675 fn test_amsterdam_genesis_slot_number() {
2676 let genesis =
2678 Genesis { gas_limit: 0x2fefd8u64, ..Default::default() }.with_slot_number(Some(999));
2679 let chainspec = ChainSpecBuilder::default()
2680 .chain(Chain::from_id(1337))
2681 .genesis(genesis)
2682 .amsterdam_activated()
2683 .build();
2684 assert_eq!(chainspec.genesis_header().slot_number, Some(999));
2685
2686 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2688 let chainspec = ChainSpecBuilder::default()
2689 .chain(Chain::from_id(1337))
2690 .genesis(genesis)
2691 .amsterdam_activated()
2692 .build();
2693 assert_eq!(chainspec.genesis_header().slot_number, Some(0));
2694 }
2695
2696 #[test]
2697 fn holesky_paris_activated_at_genesis() {
2698 assert!(HOLESKY
2699 .fork(EthereumHardfork::Paris)
2700 .active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty));
2701 }
2702
2703 #[test]
2704 fn test_genesis_format_deserialization() {
2705 let config = ChainConfig {
2707 chain_id: 2600,
2708 homestead_block: Some(0),
2709 eip150_block: Some(0),
2710 eip155_block: Some(0),
2711 eip158_block: Some(0),
2712 byzantium_block: Some(0),
2713 constantinople_block: Some(0),
2714 petersburg_block: Some(0),
2715 istanbul_block: Some(0),
2716 berlin_block: Some(0),
2717 london_block: Some(0),
2718 shanghai_time: Some(0),
2719 terminal_total_difficulty: Some(U256::ZERO),
2720 terminal_total_difficulty_passed: true,
2721 ..Default::default()
2722 };
2723 let genesis = Genesis {
2725 config,
2726 nonce: 0,
2727 timestamp: 1698688670,
2728 gas_limit: 5000,
2729 difficulty: U256::ZERO,
2730 mix_hash: B256::ZERO,
2731 coinbase: Address::ZERO,
2732 ..Default::default()
2733 };
2734
2735 let address = hex!("0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").into();
2737 let account = GenesisAccount::default().with_balance(U256::from(33));
2738 let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));
2739
2740 let serialized_genesis = serde_json::to_string(&genesis).unwrap();
2742 let deserialized_genesis: Genesis = serde_json::from_str(&serialized_genesis).unwrap();
2743
2744 assert_eq!(genesis, deserialized_genesis);
2745 }
2746
2747 #[test]
2748 fn check_fork_id_chainspec_with_fork_condition_never() {
2749 let spec: ChainSpec = ChainSpec {
2750 chain: Chain::mainnet(),
2751 genesis: Genesis::default(),
2752 hardforks: ChainHardforks::new(vec![(
2753 EthereumHardfork::Frontier.boxed(),
2754 ForkCondition::Never,
2755 )]),
2756 paris_block_and_final_difficulty: None,
2757 deposit_contract: None,
2758 ..Default::default()
2759 };
2760
2761 assert_eq!(spec.hardfork_fork_id(EthereumHardfork::Frontier), None);
2762 }
2763
2764 #[test]
2765 fn check_fork_filter_chainspec_with_fork_condition_never() {
2766 let spec: ChainSpec = ChainSpec {
2767 chain: Chain::mainnet(),
2768 genesis: Genesis::default(),
2769 hardforks: ChainHardforks::new(vec![(
2770 EthereumHardfork::Shanghai.boxed(),
2771 ForkCondition::Never,
2772 )]),
2773 paris_block_and_final_difficulty: None,
2774 deposit_contract: None,
2775 ..Default::default()
2776 };
2777
2778 assert_eq!(spec.hardfork_fork_filter(EthereumHardfork::Shanghai), None);
2779 }
2780
2781 #[test]
2782 fn latest_eth_mainnet_fork_id() {
2783 assert_eq!(ForkId { hash: ForkHash(hex!("0x07c9462e")), next: 0 }, MAINNET.latest_fork_id())
2785 }
2786
2787 #[test]
2788 fn latest_hoodi_mainnet_fork_id() {
2789 assert_eq!(ForkId { hash: ForkHash(hex!("0x23aa1351")), next: 0 }, HOODI.latest_fork_id())
2791 }
2792
2793 #[test]
2794 fn latest_holesky_mainnet_fork_id() {
2795 assert_eq!(ForkId { hash: ForkHash(hex!("0x9bc6cb31")), next: 0 }, HOLESKY.latest_fork_id())
2797 }
2798
2799 #[test]
2800 fn latest_sepolia_mainnet_fork_id() {
2801 assert_eq!(ForkId { hash: ForkHash(hex!("0x268956b6")), next: 0 }, SEPOLIA.latest_fork_id())
2803 }
2804
2805 #[test]
2806 fn test_fork_order_ethereum_mainnet() {
2807 let genesis = Genesis {
2808 config: ChainConfig {
2809 chain_id: 0,
2810 homestead_block: Some(0),
2811 dao_fork_block: Some(0),
2812 dao_fork_support: false,
2813 eip150_block: Some(0),
2814 eip155_block: Some(0),
2815 eip158_block: Some(0),
2816 byzantium_block: Some(0),
2817 constantinople_block: Some(0),
2818 petersburg_block: Some(0),
2819 istanbul_block: Some(0),
2820 muir_glacier_block: Some(0),
2821 berlin_block: Some(0),
2822 london_block: Some(0),
2823 arrow_glacier_block: Some(0),
2824 gray_glacier_block: Some(0),
2825 merge_netsplit_block: Some(0),
2826 shanghai_time: Some(0),
2827 cancun_time: Some(0),
2828 terminal_total_difficulty: Some(U256::ZERO),
2829 ..Default::default()
2830 },
2831 ..Default::default()
2832 };
2833
2834 let chain_spec: ChainSpec = genesis.into();
2835
2836 let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
2837 let expected_hardforks = vec![
2838 EthereumHardfork::Frontier.boxed(),
2839 EthereumHardfork::Homestead.boxed(),
2840 EthereumHardfork::Dao.boxed(),
2841 EthereumHardfork::Tangerine.boxed(),
2842 EthereumHardfork::SpuriousDragon.boxed(),
2843 EthereumHardfork::Byzantium.boxed(),
2844 EthereumHardfork::Constantinople.boxed(),
2845 EthereumHardfork::Petersburg.boxed(),
2846 EthereumHardfork::Istanbul.boxed(),
2847 EthereumHardfork::MuirGlacier.boxed(),
2848 EthereumHardfork::Berlin.boxed(),
2849 EthereumHardfork::London.boxed(),
2850 EthereumHardfork::ArrowGlacier.boxed(),
2851 EthereumHardfork::GrayGlacier.boxed(),
2852 EthereumHardfork::Paris.boxed(),
2853 EthereumHardfork::Shanghai.boxed(),
2854 EthereumHardfork::Cancun.boxed(),
2855 ];
2856
2857 assert!(expected_hardforks
2858 .iter()
2859 .zip(hardforks.iter())
2860 .all(|(expected, actual)| &**expected == *actual));
2861 assert_eq!(expected_hardforks.len(), hardforks.len());
2862 }
2863
2864 #[test]
2865 fn test_calc_base_block_reward() {
2866 let cases = [
2868 ((0, U256::ZERO), Some(ETH_TO_WEI * 5)),
2870 ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)),
2872 ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)),
2874 ((15537394, U256::from(58_750_000_000_000_000_000_000_u128)), None),
2876 ];
2877
2878 for ((block_number, _td), expected_reward) in cases {
2879 assert_eq!(base_block_reward(&*MAINNET, block_number), expected_reward);
2880 }
2881 }
2882
2883 #[test]
2884 fn test_calc_full_block_reward() {
2885 let base_reward = ETH_TO_WEI;
2886 let one_thirty_twoth_reward = base_reward >> 5;
2887
2888 let cases = [
2890 (0, base_reward),
2891 (1, base_reward + one_thirty_twoth_reward),
2892 (2, base_reward + one_thirty_twoth_reward * 2),
2893 ];
2894
2895 for (num_ommers, expected_reward) in cases {
2896 assert_eq!(block_reward(base_reward, num_ommers), expected_reward);
2897 }
2898 }
2899
2900 #[test]
2901 fn blob_params_from_genesis() {
2902 let s = r#"{
2903 "blobSchedule": {
2904 "cancun":{
2905 "baseFeeUpdateFraction":3338477,
2906 "max":6,
2907 "target":3
2908 },
2909 "prague":{
2910 "baseFeeUpdateFraction":3338477,
2911 "max":6,
2912 "target":3
2913 }
2914 }
2915 }"#;
2916 let config: ChainConfig = serde_json::from_str(s).unwrap();
2917 let hardfork_params = config.blob_schedule_blob_params();
2918 let expected = BlobScheduleBlobParams {
2919 cancun: BlobParams {
2920 target_blob_count: 3,
2921 max_blob_count: 6,
2922 update_fraction: 3338477,
2923 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2924 max_blobs_per_tx: 6,
2925 blob_base_cost: 0,
2926 },
2927 prague: BlobParams {
2928 target_blob_count: 3,
2929 max_blob_count: 6,
2930 update_fraction: 3338477,
2931 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2932 max_blobs_per_tx: 6,
2933 blob_base_cost: 0,
2934 },
2935 ..Default::default()
2936 };
2937 assert_eq!(hardfork_params, expected);
2938 }
2939
2940 #[test]
2941 fn parse_perf_net_genesis() {
2942 let s = r#"{
2943 "config": {
2944 "chainId": 1,
2945 "homesteadBlock": 1150000,
2946 "daoForkBlock": 1920000,
2947 "daoForkSupport": true,
2948 "eip150Block": 2463000,
2949 "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
2950 "eip155Block": 2675000,
2951 "eip158Block": 2675000,
2952 "byzantiumBlock": 4370000,
2953 "constantinopleBlock": 7280000,
2954 "petersburgBlock": 7280000,
2955 "istanbulBlock": 9069000,
2956 "muirGlacierBlock": 9200000,
2957 "berlinBlock": 12244000,
2958 "londonBlock": 12965000,
2959 "arrowGlacierBlock": 13773000,
2960 "grayGlacierBlock": 15050000,
2961 "terminalTotalDifficulty": 58750000000000000000000,
2962 "terminalTotalDifficultyPassed": true,
2963 "shanghaiTime": 1681338455,
2964 "cancunTime": 1710338135,
2965 "pragueTime": 1746612311,
2966 "ethash": {},
2967 "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa",
2968 "blobSchedule": {
2969 "cancun": {
2970 "target": 3,
2971 "max": 6,
2972 "baseFeeUpdateFraction": 3338477
2973 },
2974 "prague": {
2975 "target": 6,
2976 "max": 9,
2977 "baseFeeUpdateFraction": 5007716
2978 }
2979 }
2980 },
2981 "nonce": "0x42",
2982 "timestamp": "0x0",
2983 "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
2984 "gasLimit": "0x1388",
2985 "difficulty": "0x400000000",
2986 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2987 "coinbase": "0x0000000000000000000000000000000000000000",
2988 "number": "0x0",
2989 "gasUsed": "0x0",
2990 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2991 "baseFeePerGas": null
2992}"#;
2993
2994 let genesis = serde_json::from_str::<Genesis>(s).unwrap();
2995 let chainspec = ChainSpec::from_genesis(genesis);
2996 let activation = chainspec.hardforks.fork(EthereumHardfork::Paris);
2997 assert_eq!(
2998 activation,
2999 ForkCondition::TTD {
3000 activation_block_number: MAINNET_PARIS_BLOCK,
3001 total_difficulty: MAINNET_PARIS_TTD,
3002 fork_block: None,
3003 }
3004 )
3005 }
3006}