1use crate::{BlockRangeUpdate, EthVersion};
2use alloy_chains::{Chain, NamedChain};
3use alloy_hardforks::{EthereumHardfork, ForkId, Head};
4use alloy_primitives::{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 fn block_range_update(&self) -> Option<BlockRangeUpdate> {
84 (self.version >= EthVersion::Eth69)
85 .then_some(())
86 .and_then(|_| self.earliest_block.zip(self.latest_block))
87 .map(|(earliest, latest)| BlockRangeUpdate {
88 earliest,
89 latest,
90 latest_hash: self.blockhash,
91 })
92 }
93
94 pub const fn set_eth_version(&mut self, v: EthVersion) {
96 self.version = v;
97 }
98
99 pub fn into_legacy(self) -> Status {
102 Status {
103 version: self.version,
104 chain: self.chain,
105 genesis: self.genesis,
106 forkid: self.forkid,
107 blockhash: self.blockhash,
108 total_difficulty: self.total_difficulty.unwrap_or(U256::ZERO),
109 }
110 }
111
112 pub fn into_eth69(self) -> StatusEth69 {
114 StatusEth69 {
115 version: self.version,
116 chain: self.chain,
117 genesis: self.genesis,
118 forkid: self.forkid,
119 earliest: self.earliest_block.unwrap_or(0),
120 latest: self.latest_block.unwrap_or(0),
121 blockhash: self.blockhash,
122 }
123 }
124
125 pub fn into_message(self) -> StatusMessage {
127 if self.version >= EthVersion::Eth69 {
128 StatusMessage::Eth69(self.into_eth69())
129 } else {
130 StatusMessage::Legacy(self.into_legacy())
131 }
132 }
133
134 pub const fn from_message(msg: StatusMessage) -> Self {
136 match msg {
137 StatusMessage::Legacy(s) => Self {
138 version: s.version,
139 chain: s.chain,
140 genesis: s.genesis,
141 forkid: s.forkid,
142 blockhash: s.blockhash,
143 total_difficulty: Some(s.total_difficulty),
144 earliest_block: None,
145 latest_block: None,
146 },
147 StatusMessage::Eth69(e) => Self {
148 version: e.version,
149 chain: e.chain,
150 genesis: e.genesis,
151 forkid: e.forkid,
152 blockhash: e.blockhash,
153 total_difficulty: None,
154 earliest_block: Some(e.earliest),
155 latest_block: Some(e.latest),
156 },
157 }
158 }
159}
160
161#[derive(Debug, Default)]
163pub struct StatusBuilder {
164 status: UnifiedStatus,
165}
166
167impl StatusBuilder {
168 pub const fn build(self) -> UnifiedStatus {
170 self.status
171 }
172
173 pub const fn version(mut self, version: EthVersion) -> Self {
175 self.status.version = version;
176 self
177 }
178
179 pub const fn chain(mut self, chain: Chain) -> Self {
181 self.status.chain = chain;
182 self
183 }
184
185 pub const fn genesis(mut self, genesis: B256) -> Self {
187 self.status.genesis = genesis;
188 self
189 }
190
191 pub const fn forkid(mut self, forkid: ForkId) -> Self {
193 self.status.forkid = forkid;
194 self
195 }
196
197 pub const fn blockhash(mut self, blockhash: B256) -> Self {
199 self.status.blockhash = blockhash;
200 self
201 }
202
203 pub const fn total_difficulty(mut self, td: Option<U256>) -> Self {
205 self.status.total_difficulty = td;
206 self
207 }
208
209 pub const fn earliest_block(mut self, earliest: Option<u64>) -> Self {
211 self.status.earliest_block = earliest;
212 self
213 }
214
215 pub const fn latest_block(mut self, latest: Option<u64>) -> Self {
217 self.status.latest_block = latest;
218 self
219 }
220}
221
222#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
229#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
230#[add_arbitrary_tests(rlp)]
231pub struct Status {
232 pub version: EthVersion,
235
236 pub chain: Chain,
239
240 pub total_difficulty: U256,
242
243 pub blockhash: B256,
245
246 pub genesis: B256,
248
249 pub forkid: ForkId,
255}
256
257impl Default for Status {
259 fn default() -> Self {
260 let mainnet_genesis = MAINNET.genesis_hash();
261 Self {
262 version: EthVersion::Eth68,
263 chain: Chain::from_named(NamedChain::Mainnet),
264 total_difficulty: U256::from(17_179_869_184u64),
265 blockhash: mainnet_genesis,
266 genesis: mainnet_genesis,
267 forkid: MAINNET
268 .hardfork_fork_id(EthereumHardfork::Frontier)
269 .expect("The Frontier hardfork should always exist"),
270 }
271 }
272}
273
274impl Display for Status {
275 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
276 write!(
277 f,
278 "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {:x}, genesis: {:x}, forkid: {:X?} }}",
279 self.version,
280 self.chain,
281 self.total_difficulty,
282 self.blockhash,
283 self.genesis,
284 self.forkid
285 )
286 }
287}
288
289impl Debug for Status {
290 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291 if f.alternate() {
292 write!(
293 f,
294 "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {:x},\n\tgenesis: {:x},\n\tforkid: {:X?}\n}}",
295 self.version,
296 self.chain,
297 self.total_difficulty,
298 self.blockhash,
299 self.genesis,
300 self.forkid
301 )
302 } else {
303 write!(
304 f,
305 "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {:x}, genesis: {:x}, forkid: {:X?} }}",
306 self.version,
307 self.chain,
308 self.total_difficulty,
309 self.blockhash,
310 self.genesis,
311 self.forkid
312 )
313 }
314 }
315}
316
317#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
322#[add_arbitrary_tests(rlp)]
323pub struct StatusEth69 {
324 pub version: EthVersion,
327
328 pub chain: Chain,
331
332 pub genesis: B256,
334
335 pub forkid: ForkId,
341
342 pub earliest: u64,
344
345 pub latest: u64,
347
348 pub blockhash: B256,
350}
351
352impl Display for StatusEth69 {
353 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
354 write!(
355 f,
356 "StatusEth69 {{ version: {}, chain: {}, genesis: {:x}, forkid: {:X?}, earliest: {}, latest: {}, blockhash: {:x} }}",
357 self.version,
358 self.chain,
359 self.genesis,
360 self.forkid,
361 self.earliest,
362 self.latest,
363 self.blockhash,
364 )
365 }
366}
367
368impl Debug for StatusEth69 {
369 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
370 if f.alternate() {
371 write!(
372 f,
373 "StatusEth69 {{\n\tversion: {:?},\n\tchain: {:?},\n\tgenesis: {:x},\n\tforkid: {:X?},\n\tearliest: {},\n\tlatest: {},\n\tblockhash: {:x}\n}}",
374 self.version, self.chain, self.genesis, self.forkid, self.earliest, self.latest, self.blockhash
375 )
376 } else {
377 write!(
378 f,
379 "StatusEth69 {{ version: {:?}, chain: {:?}, genesis: {:x}, forkid: {:X?}, earliest: {}, latest: {}, blockhash: {:x} }}",
380 self.version, self.chain, self.genesis, self.forkid, self.earliest, self.latest, self.blockhash
381 )
382 }
383 }
384}
385
386#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390pub enum StatusMessage {
391 Legacy(Status),
393 Eth69(StatusEth69),
395}
396
397impl StatusMessage {
398 pub const fn genesis(&self) -> B256 {
400 match self {
401 Self::Legacy(legacy_status) => legacy_status.genesis,
402 Self::Eth69(status_69) => status_69.genesis,
403 }
404 }
405
406 pub const fn version(&self) -> EthVersion {
408 match self {
409 Self::Legacy(legacy_status) => legacy_status.version,
410 Self::Eth69(status_69) => status_69.version,
411 }
412 }
413
414 pub const fn chain(&self) -> &Chain {
416 match self {
417 Self::Legacy(legacy_status) => &legacy_status.chain,
418 Self::Eth69(status_69) => &status_69.chain,
419 }
420 }
421
422 pub const fn forkid(&self) -> ForkId {
424 match self {
425 Self::Legacy(legacy_status) => legacy_status.forkid,
426 Self::Eth69(status_69) => status_69.forkid,
427 }
428 }
429
430 pub const fn blockhash(&self) -> B256 {
432 match self {
433 Self::Legacy(legacy_status) => legacy_status.blockhash,
434 Self::Eth69(status_69) => status_69.blockhash,
435 }
436 }
437}
438
439impl Encodable for StatusMessage {
440 fn encode(&self, out: &mut dyn BufMut) {
441 match self {
442 Self::Legacy(s) => s.encode(out),
443 Self::Eth69(s) => s.encode(out),
444 }
445 }
446
447 fn length(&self) -> usize {
448 match self {
449 Self::Legacy(s) => s.length(),
450 Self::Eth69(s) => s.length(),
451 }
452 }
453}
454
455impl Display for StatusMessage {
456 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
457 match self {
458 Self::Legacy(s) => Display::fmt(s, f),
459 Self::Eth69(s69) => Display::fmt(s69, f),
460 }
461 }
462}
463#[cfg(test)]
464mod tests {
465 use crate::{BlockRangeUpdate, EthVersion, Status, StatusEth69, StatusMessage, UnifiedStatus};
466 use alloy_consensus::constants::MAINNET_GENESIS_HASH;
467 use alloy_genesis::Genesis;
468 use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head};
469 use alloy_primitives::{b256, hex, B256, U256};
470 use alloy_rlp::{Decodable, Encodable};
471 use rand::Rng;
472 use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
473 use std::str::FromStr;
474
475 #[test]
476 fn encode_eth_status_message() {
477 let expected = hex!(
478 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
479 );
480 let status = Status {
481 version: EthVersion::Eth67,
482 chain: Chain::from_named(NamedChain::Mainnet),
483 total_difficulty: U256::from(36206751599115524359527u128),
484 blockhash: B256::from_str(
485 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
486 )
487 .unwrap(),
488 genesis: MAINNET_GENESIS_HASH,
489 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
490 };
491
492 let mut rlp_status = vec![];
493 status.encode(&mut rlp_status);
494 assert_eq!(rlp_status, expected);
495 }
496
497 #[test]
498 fn decode_eth_status_message() {
499 let data = hex!(
500 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
501 );
502 let expected = Status {
503 version: EthVersion::Eth67,
504 chain: Chain::from_named(NamedChain::Mainnet),
505 total_difficulty: U256::from(36206751599115524359527u128),
506 blockhash: B256::from_str(
507 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
508 )
509 .unwrap(),
510 genesis: MAINNET_GENESIS_HASH,
511 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
512 };
513 let status = Status::decode(&mut &data[..]).unwrap();
514 assert_eq!(status, expected);
515 }
516
517 #[test]
518 fn roundtrip_eth69() {
519 let unified_status = UnifiedStatus::builder()
520 .version(EthVersion::Eth69)
521 .chain(Chain::mainnet())
522 .genesis(MAINNET_GENESIS_HASH)
523 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
524 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
525 .earliest_block(Some(1))
526 .latest_block(Some(2))
527 .total_difficulty(None)
528 .build();
529
530 let status_message = unified_status.into_message();
531 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
532
533 assert_eq!(unified_status, roundtripped_unified_status);
534 }
535
536 #[test]
537 fn roundtrip_legacy() {
538 let unified_status = UnifiedStatus::builder()
539 .version(EthVersion::Eth68)
540 .chain(Chain::sepolia())
541 .genesis(MAINNET_GENESIS_HASH)
542 .forkid(ForkId { hash: ForkHash([0xaa, 0xbb, 0xcc, 0xdd]), next: 0 })
543 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
544 .total_difficulty(Some(U256::from(42u64)))
545 .earliest_block(None)
546 .latest_block(None)
547 .build();
548
549 let status_message = unified_status.into_message();
550 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
551 assert_eq!(unified_status, roundtripped_unified_status);
552 }
553
554 #[test]
555 fn roundtrip_eth70() {
556 let unified_status = UnifiedStatus::builder()
557 .version(EthVersion::Eth70)
558 .chain(Chain::mainnet())
559 .genesis(MAINNET_GENESIS_HASH)
560 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
561 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
562 .total_difficulty(None)
563 .earliest_block(Some(1))
564 .latest_block(Some(2))
565 .build();
566
567 let status_message = unified_status.into_message();
568 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
569 assert_eq!(unified_status, roundtripped_unified_status);
570 }
571
572 #[test]
573 fn block_range_update_for_eth69_status() {
574 let latest_hash =
575 b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
576 let status = UnifiedStatus::builder()
577 .version(EthVersion::Eth69)
578 .earliest_block(Some(10))
579 .latest_block(Some(20))
580 .blockhash(latest_hash)
581 .build();
582
583 assert_eq!(
584 status.block_range_update(),
585 Some(BlockRangeUpdate { earliest: 10, latest: 20, latest_hash })
586 );
587 }
588
589 #[test]
590 fn block_range_update_is_none_for_legacy_status() {
591 let status = UnifiedStatus::builder().version(EthVersion::Eth68).build();
592
593 assert!(status.block_range_update().is_none());
594 }
595
596 #[test]
597 fn encode_eth69_status_message() {
598 let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
599 let status = StatusEth69 {
600 version: EthVersion::Eth69,
601 chain: Chain::from_named(NamedChain::Mainnet),
602
603 genesis: MAINNET_GENESIS_HASH,
604 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
605 earliest: 15_537_394,
606 latest: 18_000_000,
607 blockhash: B256::from_str(
608 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
609 )
610 .unwrap(),
611 };
612
613 let mut rlp_status = vec![];
614 status.encode(&mut rlp_status);
615 assert_eq!(rlp_status, expected);
616
617 let status = UnifiedStatus::builder()
618 .version(EthVersion::Eth69)
619 .chain(Chain::from_named(NamedChain::Mainnet))
620 .genesis(MAINNET_GENESIS_HASH)
621 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
622 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
623 .earliest_block(Some(15_537_394))
624 .latest_block(Some(18_000_000))
625 .build()
626 .into_message();
627
628 let mut rlp_status = vec![];
629 status.encode(&mut rlp_status);
630 assert_eq!(rlp_status, expected);
631 }
632
633 #[test]
634 fn decode_eth69_status_message() {
635 let data = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
636 let expected = StatusEth69 {
637 version: EthVersion::Eth69,
638 chain: Chain::from_named(NamedChain::Mainnet),
639 genesis: MAINNET_GENESIS_HASH,
640 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
641 earliest: 15_537_394,
642 latest: 18_000_000,
643 blockhash: B256::from_str(
644 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
645 )
646 .unwrap(),
647 };
648 let status = StatusEth69::decode(&mut &data[..]).unwrap();
649 assert_eq!(status, expected);
650
651 let expected_message = UnifiedStatus::builder()
652 .version(EthVersion::Eth69)
653 .chain(Chain::from_named(NamedChain::Mainnet))
654 .genesis(MAINNET_GENESIS_HASH)
655 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
656 .earliest_block(Some(15_537_394))
657 .latest_block(Some(18_000_000))
658 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
659 .build()
660 .into_message();
661
662 let expected_status = if let StatusMessage::Eth69(status69) = expected_message {
663 status69
664 } else {
665 panic!("expected StatusMessage::Eth69 variant");
666 };
667
668 assert_eq!(status, expected_status);
669 }
670
671 #[test]
672 fn encode_network_status_message() {
673 let expected = hex!(
674 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
675 );
676 let status = Status {
677 version: EthVersion::Eth66,
678 chain: Chain::from_named(NamedChain::BinanceSmartChain),
679 total_difficulty: U256::from(37851386u64),
680 blockhash: B256::from_str(
681 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
682 )
683 .unwrap(),
684 genesis: B256::from_str(
685 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
686 )
687 .unwrap(),
688 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
689 };
690
691 let mut rlp_status = vec![];
692 status.encode(&mut rlp_status);
693 assert_eq!(rlp_status, expected);
694 }
695
696 #[test]
697 fn decode_network_status_message() {
698 let data = hex!(
699 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
700 );
701 let expected = Status {
702 version: EthVersion::Eth66,
703 chain: Chain::from_named(NamedChain::BinanceSmartChain),
704 total_difficulty: U256::from(37851386u64),
705 blockhash: B256::from_str(
706 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
707 )
708 .unwrap(),
709 genesis: B256::from_str(
710 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
711 )
712 .unwrap(),
713 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
714 };
715 let status = Status::decode(&mut &data[..]).unwrap();
716 assert_eq!(status, expected);
717 }
718
719 #[test]
720 fn decode_another_network_status_message() {
721 let data = hex!(
722 "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
723 );
724 let expected = Status {
725 version: EthVersion::Eth66,
726 chain: Chain::from_id(2100),
727 total_difficulty: U256::from_str(
728 "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
729 )
730 .unwrap(),
731 blockhash: B256::from_str(
732 "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
733 )
734 .unwrap(),
735 genesis: B256::from_str(
736 "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
737 )
738 .unwrap(),
739 forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
740 };
741 let status = Status::decode(&mut &data[..]).unwrap();
742 assert_eq!(status, expected);
743 }
744
745 #[test]
746 fn init_custom_status_fields() {
747 let mut rng = rand::rng();
748 let head_hash = rng.random();
749 let total_difficulty = U256::from(rng.random::<u64>());
750
751 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
753
754 let head = Head {
756 number: u64::MAX,
757 hash: head_hash,
758 difficulty: U256::from(13337),
759 total_difficulty,
760 timestamp: u64::MAX,
761 };
762
763 let hardforks = vec![
765 (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
766 (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
767 (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
768 (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
769 (EthereumHardfork::London, ForkCondition::Block(8)),
770 (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
771 ];
772
773 let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
774
775 for (fork, condition) in &hardforks {
776 chainspec = chainspec.with_fork(*fork, *condition);
777 }
778
779 let spec = chainspec.build();
780
781 let genesis_hash = spec.genesis_hash();
783 let mut forkhash = ForkHash::from(genesis_hash);
784 for (_, condition) in hardforks {
785 forkhash += match condition {
786 ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
787 _ => unreachable!("only block and timestamp forks are used in this test"),
788 }
789 }
790
791 let forkid = ForkId { hash: forkhash, next: 0 };
792
793 let status = UnifiedStatus::spec_builder(&spec, &head);
794
795 assert_eq!(status.chain, Chain::from_id(1337));
796 assert_eq!(status.forkid, forkid);
797 assert_eq!(status.total_difficulty.unwrap(), total_difficulty);
798 assert_eq!(status.blockhash, head_hash);
799 assert_eq!(status.genesis, genesis_hash);
800 }
801}