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