1use crate::EthVersion;
2use alloy_chains::{Chain, NamedChain};
3use alloy_hardforks::{EthereumHardfork, ForkId, Head};
4use alloy_primitives::{hex, B256, U256};
5use alloy_rlp::{BufMut, Encodable, RlpDecodable, RlpEncodable};
6use core::fmt::{Debug, Display};
7use reth_chainspec::{EthChainSpec, Hardforks, MAINNET};
8use reth_codecs_derive::add_arbitrary_tests;
9
10#[derive(Clone, Debug, PartialEq, Eq, Copy)]
15pub struct UnifiedStatus {
16 pub version: EthVersion,
18 pub chain: Chain,
20 pub genesis: B256,
22 pub forkid: ForkId,
24 pub blockhash: B256,
26 pub total_difficulty: Option<U256>,
28 pub earliest_block: Option<u64>,
30 pub latest_block: Option<u64>,
32}
33
34impl Default for UnifiedStatus {
35 fn default() -> Self {
36 let mainnet_genesis = MAINNET.genesis_hash();
37 Self {
38 version: EthVersion::Eth68,
39 chain: Chain::from_named(NamedChain::Mainnet),
40 genesis: mainnet_genesis,
41 forkid: MAINNET
42 .hardfork_fork_id(EthereumHardfork::Frontier)
43 .expect("Frontier must exist"),
44 blockhash: mainnet_genesis,
45 total_difficulty: Some(U256::from(17_179_869_184u64)),
46 earliest_block: Some(0),
47 latest_block: Some(0),
48 }
49 }
50}
51
52impl UnifiedStatus {
53 pub fn builder() -> StatusBuilder {
55 Default::default()
56 }
57
58 pub fn spec_builder<Spec>(spec: &Spec, head: &Head) -> Self
60 where
61 Spec: EthChainSpec + Hardforks,
62 {
63 Self::builder()
64 .chain(spec.chain())
65 .genesis(spec.genesis_hash())
66 .forkid(spec.fork_id(head))
67 .blockhash(head.hash)
68 .total_difficulty(Some(head.total_difficulty))
69 .earliest_block(Some(0))
70 .latest_block(Some(head.number))
71 .build()
72 }
73
74 pub const fn set_history_range(&mut self, earliest: u64, latest: u64) {
77 self.earliest_block = Some(earliest);
78 self.latest_block = Some(latest);
79 }
80
81 pub const fn set_eth_version(&mut self, v: EthVersion) {
83 self.version = v;
84 }
85
86 pub fn into_legacy(self) -> Status {
89 Status {
90 version: self.version,
91 chain: self.chain,
92 genesis: self.genesis,
93 forkid: self.forkid,
94 blockhash: self.blockhash,
95 total_difficulty: self.total_difficulty.unwrap_or(U256::ZERO),
96 }
97 }
98
99 pub fn into_eth69(self) -> StatusEth69 {
101 StatusEth69 {
102 version: self.version,
103 chain: self.chain,
104 genesis: self.genesis,
105 forkid: self.forkid,
106 earliest: self.earliest_block.unwrap_or(0),
107 latest: self.latest_block.unwrap_or(0),
108 blockhash: self.blockhash,
109 }
110 }
111
112 pub fn into_message(self) -> StatusMessage {
114 if self.version >= EthVersion::Eth69 {
115 StatusMessage::Eth69(self.into_eth69())
116 } else {
117 StatusMessage::Legacy(self.into_legacy())
118 }
119 }
120
121 pub const fn from_message(msg: StatusMessage) -> Self {
123 match msg {
124 StatusMessage::Legacy(s) => Self {
125 version: s.version,
126 chain: s.chain,
127 genesis: s.genesis,
128 forkid: s.forkid,
129 blockhash: s.blockhash,
130 total_difficulty: Some(s.total_difficulty),
131 earliest_block: None,
132 latest_block: None,
133 },
134 StatusMessage::Eth69(e) => Self {
135 version: e.version,
136 chain: e.chain,
137 genesis: e.genesis,
138 forkid: e.forkid,
139 blockhash: e.blockhash,
140 total_difficulty: None,
141 earliest_block: Some(e.earliest),
142 latest_block: Some(e.latest),
143 },
144 }
145 }
146}
147
148#[derive(Debug, Default)]
150pub struct StatusBuilder {
151 status: UnifiedStatus,
152}
153
154impl StatusBuilder {
155 pub const fn build(self) -> UnifiedStatus {
157 self.status
158 }
159
160 pub const fn version(mut self, version: EthVersion) -> Self {
162 self.status.version = version;
163 self
164 }
165
166 pub const fn chain(mut self, chain: Chain) -> Self {
168 self.status.chain = chain;
169 self
170 }
171
172 pub const fn genesis(mut self, genesis: B256) -> Self {
174 self.status.genesis = genesis;
175 self
176 }
177
178 pub const fn forkid(mut self, forkid: ForkId) -> Self {
180 self.status.forkid = forkid;
181 self
182 }
183
184 pub const fn blockhash(mut self, blockhash: B256) -> Self {
186 self.status.blockhash = blockhash;
187 self
188 }
189
190 pub const fn total_difficulty(mut self, td: Option<U256>) -> Self {
192 self.status.total_difficulty = td;
193 self
194 }
195
196 pub const fn earliest_block(mut self, earliest: Option<u64>) -> Self {
198 self.status.earliest_block = earliest;
199 self
200 }
201
202 pub const fn latest_block(mut self, latest: Option<u64>) -> Self {
204 self.status.latest_block = latest;
205 self
206 }
207}
208
209#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
217#[add_arbitrary_tests(rlp)]
218pub struct Status {
219 pub version: EthVersion,
222
223 pub chain: Chain,
226
227 pub total_difficulty: U256,
229
230 pub blockhash: B256,
232
233 pub genesis: B256,
235
236 pub forkid: ForkId,
242}
243
244impl Default for Status {
246 fn default() -> Self {
247 let mainnet_genesis = MAINNET.genesis_hash();
248 Self {
249 version: EthVersion::Eth68,
250 chain: Chain::from_named(NamedChain::Mainnet),
251 total_difficulty: U256::from(17_179_869_184u64),
252 blockhash: mainnet_genesis,
253 genesis: mainnet_genesis,
254 forkid: MAINNET
255 .hardfork_fork_id(EthereumHardfork::Frontier)
256 .expect("The Frontier hardfork should always exist"),
257 }
258 }
259}
260
261impl Display for Status {
262 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
263 let hexed_blockhash = hex::encode(self.blockhash);
264 let hexed_genesis = hex::encode(self.genesis);
265 write!(
266 f,
267 "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
268 self.version,
269 self.chain,
270 self.total_difficulty,
271 hexed_blockhash,
272 hexed_genesis,
273 self.forkid
274 )
275 }
276}
277
278impl Debug for Status {
279 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280 let hexed_blockhash = hex::encode(self.blockhash);
281 let hexed_genesis = hex::encode(self.genesis);
282 if f.alternate() {
283 write!(
284 f,
285 "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
286 self.version,
287 self.chain,
288 self.total_difficulty,
289 hexed_blockhash,
290 hexed_genesis,
291 self.forkid
292 )
293 } else {
294 write!(
295 f,
296 "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
297 self.version,
298 self.chain,
299 self.total_difficulty,
300 hexed_blockhash,
301 hexed_genesis,
302 self.forkid
303 )
304 }
305 }
306}
307
308#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
313#[add_arbitrary_tests(rlp)]
314pub struct StatusEth69 {
315 pub version: EthVersion,
318
319 pub chain: Chain,
322
323 pub genesis: B256,
325
326 pub forkid: ForkId,
332
333 pub earliest: u64,
335
336 pub latest: u64,
338
339 pub blockhash: B256,
341}
342
343impl Display for StatusEth69 {
344 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
345 let hexed_blockhash = hex::encode(self.blockhash);
346 let hexed_genesis = hex::encode(self.genesis);
347 write!(
348 f,
349 "StatusEth69 {{ version: {}, chain: {}, genesis: {}, forkid: {:X?}, earliest: {}, latest: {}, blockhash: {} }}",
350 self.version,
351 self.chain,
352 hexed_genesis,
353 self.forkid,
354 self.earliest,
355 self.latest,
356 hexed_blockhash,
357 )
358 }
359}
360
361impl Debug for StatusEth69 {
362 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
363 let hexed_blockhash = hex::encode(self.blockhash);
364 let hexed_genesis = hex::encode(self.genesis);
365 if f.alternate() {
366 write!(
367 f,
368 "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
369 self.version, self.chain, hexed_blockhash, hexed_genesis, self.forkid
370 )
371 } else {
372 write!(
373 f,
374 "Status {{ version: {:?}, chain: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
375 self.version, self.chain, hexed_blockhash, hexed_genesis, self.forkid
376 )
377 }
378 }
379}
380
381#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384#[derive(Clone, Copy, Debug, PartialEq, Eq)]
385pub enum StatusMessage {
386 Legacy(Status),
388 Eth69(StatusEth69),
390}
391
392impl StatusMessage {
393 pub const fn genesis(&self) -> B256 {
395 match self {
396 Self::Legacy(legacy_status) => legacy_status.genesis,
397 Self::Eth69(status_69) => status_69.genesis,
398 }
399 }
400
401 pub const fn version(&self) -> EthVersion {
403 match self {
404 Self::Legacy(legacy_status) => legacy_status.version,
405 Self::Eth69(status_69) => status_69.version,
406 }
407 }
408
409 pub const fn chain(&self) -> &Chain {
411 match self {
412 Self::Legacy(legacy_status) => &legacy_status.chain,
413 Self::Eth69(status_69) => &status_69.chain,
414 }
415 }
416
417 pub const fn forkid(&self) -> ForkId {
419 match self {
420 Self::Legacy(legacy_status) => legacy_status.forkid,
421 Self::Eth69(status_69) => status_69.forkid,
422 }
423 }
424
425 pub const fn blockhash(&self) -> B256 {
427 match self {
428 Self::Legacy(legacy_status) => legacy_status.blockhash,
429 Self::Eth69(status_69) => status_69.blockhash,
430 }
431 }
432}
433
434impl Encodable for StatusMessage {
435 fn encode(&self, out: &mut dyn BufMut) {
436 match self {
437 Self::Legacy(s) => s.encode(out),
438 Self::Eth69(s) => s.encode(out),
439 }
440 }
441
442 fn length(&self) -> usize {
443 match self {
444 Self::Legacy(s) => s.length(),
445 Self::Eth69(s) => s.length(),
446 }
447 }
448}
449
450impl Display for StatusMessage {
451 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
452 match self {
453 Self::Legacy(s) => Display::fmt(s, f),
454 Self::Eth69(s69) => Display::fmt(s69, f),
455 }
456 }
457}
458#[cfg(test)]
459mod tests {
460 use crate::{EthVersion, Status, StatusEth69, StatusMessage, UnifiedStatus};
461 use alloy_consensus::constants::MAINNET_GENESIS_HASH;
462 use alloy_genesis::Genesis;
463 use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head};
464 use alloy_primitives::{b256, hex, B256, U256};
465 use alloy_rlp::{Decodable, Encodable};
466 use rand::Rng;
467 use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
468 use std::str::FromStr;
469
470 #[test]
471 fn encode_eth_status_message() {
472 let expected = hex!(
473 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
474 );
475 let status = Status {
476 version: EthVersion::Eth67,
477 chain: Chain::from_named(NamedChain::Mainnet),
478 total_difficulty: U256::from(36206751599115524359527u128),
479 blockhash: B256::from_str(
480 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
481 )
482 .unwrap(),
483 genesis: MAINNET_GENESIS_HASH,
484 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
485 };
486
487 let mut rlp_status = vec![];
488 status.encode(&mut rlp_status);
489 assert_eq!(rlp_status, expected);
490 }
491
492 #[test]
493 fn decode_eth_status_message() {
494 let data = hex!(
495 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
496 );
497 let expected = Status {
498 version: EthVersion::Eth67,
499 chain: Chain::from_named(NamedChain::Mainnet),
500 total_difficulty: U256::from(36206751599115524359527u128),
501 blockhash: B256::from_str(
502 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
503 )
504 .unwrap(),
505 genesis: MAINNET_GENESIS_HASH,
506 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
507 };
508 let status = Status::decode(&mut &data[..]).unwrap();
509 assert_eq!(status, expected);
510 }
511
512 #[test]
513 fn roundtrip_eth69() {
514 let unified_status = UnifiedStatus::builder()
515 .version(EthVersion::Eth69)
516 .chain(Chain::mainnet())
517 .genesis(MAINNET_GENESIS_HASH)
518 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
519 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
520 .earliest_block(Some(1))
521 .latest_block(Some(2))
522 .total_difficulty(None)
523 .build();
524
525 let status_message = unified_status.into_message();
526 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
527
528 assert_eq!(unified_status, roundtripped_unified_status);
529 }
530
531 #[test]
532 fn roundtrip_legacy() {
533 let unified_status = UnifiedStatus::builder()
534 .version(EthVersion::Eth68)
535 .chain(Chain::sepolia())
536 .genesis(MAINNET_GENESIS_HASH)
537 .forkid(ForkId { hash: ForkHash([0xaa, 0xbb, 0xcc, 0xdd]), next: 0 })
538 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
539 .total_difficulty(Some(U256::from(42u64)))
540 .earliest_block(None)
541 .latest_block(None)
542 .build();
543
544 let status_message = unified_status.into_message();
545 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
546 assert_eq!(unified_status, roundtripped_unified_status);
547 }
548
549 #[test]
550 fn encode_eth69_status_message() {
551 let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
552 let status = StatusEth69 {
553 version: EthVersion::Eth69,
554 chain: Chain::from_named(NamedChain::Mainnet),
555
556 genesis: MAINNET_GENESIS_HASH,
557 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
558 earliest: 15_537_394,
559 latest: 18_000_000,
560 blockhash: B256::from_str(
561 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
562 )
563 .unwrap(),
564 };
565
566 let mut rlp_status = vec![];
567 status.encode(&mut rlp_status);
568 assert_eq!(rlp_status, expected);
569
570 let status = UnifiedStatus::builder()
571 .version(EthVersion::Eth69)
572 .chain(Chain::from_named(NamedChain::Mainnet))
573 .genesis(MAINNET_GENESIS_HASH)
574 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
575 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
576 .earliest_block(Some(15_537_394))
577 .latest_block(Some(18_000_000))
578 .build()
579 .into_message();
580
581 let mut rlp_status = vec![];
582 status.encode(&mut rlp_status);
583 assert_eq!(rlp_status, expected);
584 }
585
586 #[test]
587 fn decode_eth69_status_message() {
588 let data = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
589 let expected = StatusEth69 {
590 version: EthVersion::Eth69,
591 chain: Chain::from_named(NamedChain::Mainnet),
592 genesis: MAINNET_GENESIS_HASH,
593 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
594 earliest: 15_537_394,
595 latest: 18_000_000,
596 blockhash: B256::from_str(
597 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
598 )
599 .unwrap(),
600 };
601 let status = StatusEth69::decode(&mut &data[..]).unwrap();
602 assert_eq!(status, expected);
603
604 let expected_message = UnifiedStatus::builder()
605 .version(EthVersion::Eth69)
606 .chain(Chain::from_named(NamedChain::Mainnet))
607 .genesis(MAINNET_GENESIS_HASH)
608 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
609 .earliest_block(Some(15_537_394))
610 .latest_block(Some(18_000_000))
611 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
612 .build()
613 .into_message();
614
615 let expected_status = if let StatusMessage::Eth69(status69) = expected_message {
616 status69
617 } else {
618 panic!("expected StatusMessage::Eth69 variant");
619 };
620
621 assert_eq!(status, expected_status);
622 }
623
624 #[test]
625 fn encode_network_status_message() {
626 let expected = hex!(
627 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
628 );
629 let status = Status {
630 version: EthVersion::Eth66,
631 chain: Chain::from_named(NamedChain::BinanceSmartChain),
632 total_difficulty: U256::from(37851386u64),
633 blockhash: B256::from_str(
634 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
635 )
636 .unwrap(),
637 genesis: B256::from_str(
638 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
639 )
640 .unwrap(),
641 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
642 };
643
644 let mut rlp_status = vec![];
645 status.encode(&mut rlp_status);
646 assert_eq!(rlp_status, expected);
647 }
648
649 #[test]
650 fn decode_network_status_message() {
651 let data = hex!(
652 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
653 );
654 let expected = Status {
655 version: EthVersion::Eth66,
656 chain: Chain::from_named(NamedChain::BinanceSmartChain),
657 total_difficulty: U256::from(37851386u64),
658 blockhash: B256::from_str(
659 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
660 )
661 .unwrap(),
662 genesis: B256::from_str(
663 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
664 )
665 .unwrap(),
666 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
667 };
668 let status = Status::decode(&mut &data[..]).unwrap();
669 assert_eq!(status, expected);
670 }
671
672 #[test]
673 fn decode_another_network_status_message() {
674 let data = hex!(
675 "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
676 );
677 let expected = Status {
678 version: EthVersion::Eth66,
679 chain: Chain::from_id(2100),
680 total_difficulty: U256::from_str(
681 "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
682 )
683 .unwrap(),
684 blockhash: B256::from_str(
685 "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
686 )
687 .unwrap(),
688 genesis: B256::from_str(
689 "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
690 )
691 .unwrap(),
692 forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
693 };
694 let status = Status::decode(&mut &data[..]).unwrap();
695 assert_eq!(status, expected);
696 }
697
698 #[test]
699 fn init_custom_status_fields() {
700 let mut rng = rand::rng();
701 let head_hash = rng.random();
702 let total_difficulty = U256::from(rng.random::<u64>());
703
704 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
706
707 let head = Head {
709 number: u64::MAX,
710 hash: head_hash,
711 difficulty: U256::from(13337),
712 total_difficulty,
713 timestamp: u64::MAX,
714 };
715
716 let hardforks = vec![
718 (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
719 (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
720 (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
721 (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
722 (EthereumHardfork::London, ForkCondition::Block(8)),
723 (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
724 ];
725
726 let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
727
728 for (fork, condition) in &hardforks {
729 chainspec = chainspec.with_fork(*fork, *condition);
730 }
731
732 let spec = chainspec.build();
733
734 let genesis_hash = spec.genesis_hash();
736 let mut forkhash = ForkHash::from(genesis_hash);
737 for (_, condition) in hardforks {
738 forkhash += match condition {
739 ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
740 _ => unreachable!("only block and timestamp forks are used in this test"),
741 }
742 }
743
744 let forkid = ForkId { hash: forkhash, next: 0 };
745
746 let status = UnifiedStatus::spec_builder(&spec, &head);
747
748 assert_eq!(status.chain, Chain::from_id(1337));
749 assert_eq!(status.forkid, forkid);
750 assert_eq!(status.total_difficulty.unwrap(), total_difficulty);
751 assert_eq!(status.blockhash, head_hash);
752 assert_eq!(status.genesis, genesis_hash);
753 }
754}