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