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