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::{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(
520 B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")
521 .unwrap(),
522 )
523 .earliest_block(Some(1))
524 .latest_block(Some(2))
525 .total_difficulty(None)
526 .build();
527
528 let status_message = unified_status.into_message();
529 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
530
531 assert_eq!(unified_status, roundtripped_unified_status);
532 }
533
534 #[test]
535 fn roundtrip_legacy() {
536 let unified_status = UnifiedStatus::builder()
537 .version(EthVersion::Eth68)
538 .chain(Chain::sepolia())
539 .genesis(MAINNET_GENESIS_HASH)
540 .forkid(ForkId { hash: ForkHash([0xaa, 0xbb, 0xcc, 0xdd]), next: 0 })
541 .blockhash(
542 B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")
543 .unwrap(),
544 )
545 .total_difficulty(Some(U256::from(42u64)))
546 .earliest_block(None)
547 .latest_block(None)
548 .build();
549
550 let status_message = unified_status.into_message();
551 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
552 assert_eq!(unified_status, roundtripped_unified_status);
553 }
554
555 #[test]
556 fn encode_eth69_status_message() {
557 let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
558 let status = StatusEth69 {
559 version: EthVersion::Eth69,
560 chain: Chain::from_named(NamedChain::Mainnet),
561
562 genesis: MAINNET_GENESIS_HASH,
563 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
564 earliest: 15_537_394,
565 latest: 18_000_000,
566 blockhash: B256::from_str(
567 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
568 )
569 .unwrap(),
570 };
571
572 let mut rlp_status = vec![];
573 status.encode(&mut rlp_status);
574 assert_eq!(rlp_status, expected);
575
576 let status = UnifiedStatus::builder()
577 .version(EthVersion::Eth69)
578 .chain(Chain::from_named(NamedChain::Mainnet))
579 .genesis(MAINNET_GENESIS_HASH)
580 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
581 .blockhash(
582 B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")
583 .unwrap(),
584 )
585 .earliest_block(Some(15_537_394))
586 .latest_block(Some(18_000_000))
587 .build()
588 .into_message();
589
590 let mut rlp_status = vec![];
591 status.encode(&mut rlp_status);
592 assert_eq!(rlp_status, expected);
593 }
594
595 #[test]
596 fn decode_eth69_status_message() {
597 let data = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
598 let expected = StatusEth69 {
599 version: EthVersion::Eth69,
600 chain: Chain::from_named(NamedChain::Mainnet),
601 genesis: MAINNET_GENESIS_HASH,
602 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
603 earliest: 15_537_394,
604 latest: 18_000_000,
605 blockhash: B256::from_str(
606 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
607 )
608 .unwrap(),
609 };
610 let status = StatusEth69::decode(&mut &data[..]).unwrap();
611 assert_eq!(status, expected);
612
613 let expected_message = UnifiedStatus::builder()
614 .version(EthVersion::Eth69)
615 .chain(Chain::from_named(NamedChain::Mainnet))
616 .genesis(MAINNET_GENESIS_HASH)
617 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
618 .earliest_block(Some(15_537_394))
619 .latest_block(Some(18_000_000))
620 .blockhash(
621 B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")
622 .unwrap(),
623 )
624 .build()
625 .into_message();
626
627 let expected_status = if let StatusMessage::Eth69(status69) = expected_message {
628 status69
629 } else {
630 panic!("expected StatusMessage::Eth69 variant");
631 };
632
633 assert_eq!(status, expected_status);
634 }
635
636 #[test]
637 fn encode_network_status_message() {
638 let expected = hex!(
639 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
640 );
641 let status = Status {
642 version: EthVersion::Eth66,
643 chain: Chain::from_named(NamedChain::BinanceSmartChain),
644 total_difficulty: U256::from(37851386u64),
645 blockhash: B256::from_str(
646 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
647 )
648 .unwrap(),
649 genesis: B256::from_str(
650 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
651 )
652 .unwrap(),
653 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
654 };
655
656 let mut rlp_status = vec![];
657 status.encode(&mut rlp_status);
658 assert_eq!(rlp_status, expected);
659 }
660
661 #[test]
662 fn decode_network_status_message() {
663 let data = hex!(
664 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
665 );
666 let expected = Status {
667 version: EthVersion::Eth66,
668 chain: Chain::from_named(NamedChain::BinanceSmartChain),
669 total_difficulty: U256::from(37851386u64),
670 blockhash: B256::from_str(
671 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
672 )
673 .unwrap(),
674 genesis: B256::from_str(
675 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
676 )
677 .unwrap(),
678 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
679 };
680 let status = Status::decode(&mut &data[..]).unwrap();
681 assert_eq!(status, expected);
682 }
683
684 #[test]
685 fn decode_another_network_status_message() {
686 let data = hex!(
687 "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
688 );
689 let expected = Status {
690 version: EthVersion::Eth66,
691 chain: Chain::from_id(2100),
692 total_difficulty: U256::from_str(
693 "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
694 )
695 .unwrap(),
696 blockhash: B256::from_str(
697 "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
698 )
699 .unwrap(),
700 genesis: B256::from_str(
701 "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
702 )
703 .unwrap(),
704 forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
705 };
706 let status = Status::decode(&mut &data[..]).unwrap();
707 assert_eq!(status, expected);
708 }
709
710 #[test]
711 fn init_custom_status_fields() {
712 let mut rng = rand::rng();
713 let head_hash = rng.random();
714 let total_difficulty = U256::from(rng.random::<u64>());
715
716 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
718
719 let head = Head {
721 number: u64::MAX,
722 hash: head_hash,
723 difficulty: U256::from(13337),
724 total_difficulty,
725 timestamp: u64::MAX,
726 };
727
728 let hardforks = vec![
730 (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
731 (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
732 (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
733 (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
734 (EthereumHardfork::London, ForkCondition::Block(8)),
735 (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
736 ];
737
738 let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
739
740 for (fork, condition) in &hardforks {
741 chainspec = chainspec.with_fork(*fork, *condition);
742 }
743
744 let spec = chainspec.build();
745
746 let genesis_hash = spec.genesis_hash();
748 let mut forkhash = ForkHash::from(genesis_hash);
749 for (_, condition) in hardforks {
750 forkhash += match condition {
751 ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
752 _ => unreachable!("only block and timestamp forks are used in this test"),
753 }
754 }
755
756 let forkid = ForkId { hash: forkhash, next: 0 };
757
758 let status = UnifiedStatus::spec_builder(&spec, &head);
759
760 assert_eq!(status.chain, Chain::from_id(1337));
761 assert_eq!(status.forkid, forkid);
762 assert_eq!(status.total_difficulty.unwrap(), total_difficulty);
763 assert_eq!(status.blockhash, head_hash);
764 assert_eq!(status.genesis, genesis_hash);
765 }
766}