1use crate::{BlockRangeUpdate, 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 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 let hexed_blockhash = hex::encode(self.blockhash);
277 let hexed_genesis = hex::encode(self.genesis);
278 write!(
279 f,
280 "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
281 self.version,
282 self.chain,
283 self.total_difficulty,
284 hexed_blockhash,
285 hexed_genesis,
286 self.forkid
287 )
288 }
289}
290
291impl Debug for Status {
292 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293 let hexed_blockhash = hex::encode(self.blockhash);
294 let hexed_genesis = hex::encode(self.genesis);
295 if f.alternate() {
296 write!(
297 f,
298 "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
299 self.version,
300 self.chain,
301 self.total_difficulty,
302 hexed_blockhash,
303 hexed_genesis,
304 self.forkid
305 )
306 } else {
307 write!(
308 f,
309 "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
310 self.version,
311 self.chain,
312 self.total_difficulty,
313 hexed_blockhash,
314 hexed_genesis,
315 self.forkid
316 )
317 }
318 }
319}
320
321#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
326#[add_arbitrary_tests(rlp)]
327pub struct StatusEth69 {
328 pub version: EthVersion,
331
332 pub chain: Chain,
335
336 pub genesis: B256,
338
339 pub forkid: ForkId,
345
346 pub earliest: u64,
348
349 pub latest: u64,
351
352 pub blockhash: B256,
354}
355
356impl Display for StatusEth69 {
357 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
358 let hexed_blockhash = hex::encode(self.blockhash);
359 let hexed_genesis = hex::encode(self.genesis);
360 write!(
361 f,
362 "StatusEth69 {{ version: {}, chain: {}, genesis: {}, forkid: {:X?}, earliest: {}, latest: {}, blockhash: {} }}",
363 self.version,
364 self.chain,
365 hexed_genesis,
366 self.forkid,
367 self.earliest,
368 self.latest,
369 hexed_blockhash,
370 )
371 }
372}
373
374impl Debug for StatusEth69 {
375 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
376 let hexed_blockhash = hex::encode(self.blockhash);
377 let hexed_genesis = hex::encode(self.genesis);
378 if f.alternate() {
379 write!(
380 f,
381 "StatusEth69 {{\n\tversion: {:?},\n\tchain: {:?},\n\tgenesis: {},\n\tforkid: {:X?},\n\tearliest: {},\n\tlatest: {},\n\tblockhash: {}\n}}",
382 self.version, self.chain, hexed_genesis, self.forkid, self.earliest, self.latest, hexed_blockhash
383 )
384 } else {
385 write!(
386 f,
387 "StatusEth69 {{ version: {:?}, chain: {:?}, genesis: {}, forkid: {:X?}, earliest: {}, latest: {}, blockhash: {} }}",
388 self.version, self.chain, hexed_genesis, self.forkid, self.earliest, self.latest, hexed_blockhash
389 )
390 }
391 }
392}
393
394#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
398pub enum StatusMessage {
399 Legacy(Status),
401 Eth69(StatusEth69),
403}
404
405impl StatusMessage {
406 pub const fn genesis(&self) -> B256 {
408 match self {
409 Self::Legacy(legacy_status) => legacy_status.genesis,
410 Self::Eth69(status_69) => status_69.genesis,
411 }
412 }
413
414 pub const fn version(&self) -> EthVersion {
416 match self {
417 Self::Legacy(legacy_status) => legacy_status.version,
418 Self::Eth69(status_69) => status_69.version,
419 }
420 }
421
422 pub const fn chain(&self) -> &Chain {
424 match self {
425 Self::Legacy(legacy_status) => &legacy_status.chain,
426 Self::Eth69(status_69) => &status_69.chain,
427 }
428 }
429
430 pub const fn forkid(&self) -> ForkId {
432 match self {
433 Self::Legacy(legacy_status) => legacy_status.forkid,
434 Self::Eth69(status_69) => status_69.forkid,
435 }
436 }
437
438 pub const fn blockhash(&self) -> B256 {
440 match self {
441 Self::Legacy(legacy_status) => legacy_status.blockhash,
442 Self::Eth69(status_69) => status_69.blockhash,
443 }
444 }
445}
446
447impl Encodable for StatusMessage {
448 fn encode(&self, out: &mut dyn BufMut) {
449 match self {
450 Self::Legacy(s) => s.encode(out),
451 Self::Eth69(s) => s.encode(out),
452 }
453 }
454
455 fn length(&self) -> usize {
456 match self {
457 Self::Legacy(s) => s.length(),
458 Self::Eth69(s) => s.length(),
459 }
460 }
461}
462
463impl Display for StatusMessage {
464 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
465 match self {
466 Self::Legacy(s) => Display::fmt(s, f),
467 Self::Eth69(s69) => Display::fmt(s69, f),
468 }
469 }
470}
471#[cfg(test)]
472mod tests {
473 use crate::{BlockRangeUpdate, EthVersion, Status, StatusEth69, StatusMessage, UnifiedStatus};
474 use alloy_consensus::constants::MAINNET_GENESIS_HASH;
475 use alloy_genesis::Genesis;
476 use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head};
477 use alloy_primitives::{b256, hex, B256, U256};
478 use alloy_rlp::{Decodable, Encodable};
479 use rand::Rng;
480 use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
481 use std::str::FromStr;
482
483 #[test]
484 fn encode_eth_status_message() {
485 let expected = hex!(
486 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
487 );
488 let status = Status {
489 version: EthVersion::Eth67,
490 chain: Chain::from_named(NamedChain::Mainnet),
491 total_difficulty: U256::from(36206751599115524359527u128),
492 blockhash: B256::from_str(
493 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
494 )
495 .unwrap(),
496 genesis: MAINNET_GENESIS_HASH,
497 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
498 };
499
500 let mut rlp_status = vec![];
501 status.encode(&mut rlp_status);
502 assert_eq!(rlp_status, expected);
503 }
504
505 #[test]
506 fn decode_eth_status_message() {
507 let data = hex!(
508 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
509 );
510 let expected = Status {
511 version: EthVersion::Eth67,
512 chain: Chain::from_named(NamedChain::Mainnet),
513 total_difficulty: U256::from(36206751599115524359527u128),
514 blockhash: B256::from_str(
515 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
516 )
517 .unwrap(),
518 genesis: MAINNET_GENESIS_HASH,
519 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
520 };
521 let status = Status::decode(&mut &data[..]).unwrap();
522 assert_eq!(status, expected);
523 }
524
525 #[test]
526 fn roundtrip_eth69() {
527 let unified_status = UnifiedStatus::builder()
528 .version(EthVersion::Eth69)
529 .chain(Chain::mainnet())
530 .genesis(MAINNET_GENESIS_HASH)
531 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
532 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
533 .earliest_block(Some(1))
534 .latest_block(Some(2))
535 .total_difficulty(None)
536 .build();
537
538 let status_message = unified_status.into_message();
539 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
540
541 assert_eq!(unified_status, roundtripped_unified_status);
542 }
543
544 #[test]
545 fn roundtrip_legacy() {
546 let unified_status = UnifiedStatus::builder()
547 .version(EthVersion::Eth68)
548 .chain(Chain::sepolia())
549 .genesis(MAINNET_GENESIS_HASH)
550 .forkid(ForkId { hash: ForkHash([0xaa, 0xbb, 0xcc, 0xdd]), next: 0 })
551 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
552 .total_difficulty(Some(U256::from(42u64)))
553 .earliest_block(None)
554 .latest_block(None)
555 .build();
556
557 let status_message = unified_status.into_message();
558 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
559 assert_eq!(unified_status, roundtripped_unified_status);
560 }
561
562 #[test]
563 fn roundtrip_eth70() {
564 let unified_status = UnifiedStatus::builder()
565 .version(EthVersion::Eth70)
566 .chain(Chain::mainnet())
567 .genesis(MAINNET_GENESIS_HASH)
568 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
569 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
570 .total_difficulty(None)
571 .earliest_block(Some(1))
572 .latest_block(Some(2))
573 .build();
574
575 let status_message = unified_status.into_message();
576 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
577 assert_eq!(unified_status, roundtripped_unified_status);
578 }
579
580 #[test]
581 fn block_range_update_for_eth69_status() {
582 let latest_hash =
583 b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
584 let status = UnifiedStatus::builder()
585 .version(EthVersion::Eth69)
586 .earliest_block(Some(10))
587 .latest_block(Some(20))
588 .blockhash(latest_hash)
589 .build();
590
591 assert_eq!(
592 status.block_range_update(),
593 Some(BlockRangeUpdate { earliest: 10, latest: 20, latest_hash })
594 );
595 }
596
597 #[test]
598 fn block_range_update_is_none_for_legacy_status() {
599 let status = UnifiedStatus::builder().version(EthVersion::Eth68).build();
600
601 assert!(status.block_range_update().is_none());
602 }
603
604 #[test]
605 fn encode_eth69_status_message() {
606 let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
607 let status = StatusEth69 {
608 version: EthVersion::Eth69,
609 chain: Chain::from_named(NamedChain::Mainnet),
610
611 genesis: MAINNET_GENESIS_HASH,
612 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
613 earliest: 15_537_394,
614 latest: 18_000_000,
615 blockhash: B256::from_str(
616 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
617 )
618 .unwrap(),
619 };
620
621 let mut rlp_status = vec![];
622 status.encode(&mut rlp_status);
623 assert_eq!(rlp_status, expected);
624
625 let status = UnifiedStatus::builder()
626 .version(EthVersion::Eth69)
627 .chain(Chain::from_named(NamedChain::Mainnet))
628 .genesis(MAINNET_GENESIS_HASH)
629 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
630 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
631 .earliest_block(Some(15_537_394))
632 .latest_block(Some(18_000_000))
633 .build()
634 .into_message();
635
636 let mut rlp_status = vec![];
637 status.encode(&mut rlp_status);
638 assert_eq!(rlp_status, expected);
639 }
640
641 #[test]
642 fn decode_eth69_status_message() {
643 let data = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
644 let expected = StatusEth69 {
645 version: EthVersion::Eth69,
646 chain: Chain::from_named(NamedChain::Mainnet),
647 genesis: MAINNET_GENESIS_HASH,
648 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
649 earliest: 15_537_394,
650 latest: 18_000_000,
651 blockhash: B256::from_str(
652 "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
653 )
654 .unwrap(),
655 };
656 let status = StatusEth69::decode(&mut &data[..]).unwrap();
657 assert_eq!(status, expected);
658
659 let expected_message = UnifiedStatus::builder()
660 .version(EthVersion::Eth69)
661 .chain(Chain::from_named(NamedChain::Mainnet))
662 .genesis(MAINNET_GENESIS_HASH)
663 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
664 .earliest_block(Some(15_537_394))
665 .latest_block(Some(18_000_000))
666 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
667 .build()
668 .into_message();
669
670 let expected_status = if let StatusMessage::Eth69(status69) = expected_message {
671 status69
672 } else {
673 panic!("expected StatusMessage::Eth69 variant");
674 };
675
676 assert_eq!(status, expected_status);
677 }
678
679 #[test]
680 fn encode_network_status_message() {
681 let expected = hex!(
682 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
683 );
684 let status = Status {
685 version: EthVersion::Eth66,
686 chain: Chain::from_named(NamedChain::BinanceSmartChain),
687 total_difficulty: U256::from(37851386u64),
688 blockhash: B256::from_str(
689 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
690 )
691 .unwrap(),
692 genesis: B256::from_str(
693 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
694 )
695 .unwrap(),
696 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
697 };
698
699 let mut rlp_status = vec![];
700 status.encode(&mut rlp_status);
701 assert_eq!(rlp_status, expected);
702 }
703
704 #[test]
705 fn decode_network_status_message() {
706 let data = hex!(
707 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
708 );
709 let expected = Status {
710 version: EthVersion::Eth66,
711 chain: Chain::from_named(NamedChain::BinanceSmartChain),
712 total_difficulty: U256::from(37851386u64),
713 blockhash: B256::from_str(
714 "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
715 )
716 .unwrap(),
717 genesis: B256::from_str(
718 "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
719 )
720 .unwrap(),
721 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
722 };
723 let status = Status::decode(&mut &data[..]).unwrap();
724 assert_eq!(status, expected);
725 }
726
727 #[test]
728 fn decode_another_network_status_message() {
729 let data = hex!(
730 "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
731 );
732 let expected = Status {
733 version: EthVersion::Eth66,
734 chain: Chain::from_id(2100),
735 total_difficulty: U256::from_str(
736 "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
737 )
738 .unwrap(),
739 blockhash: B256::from_str(
740 "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
741 )
742 .unwrap(),
743 genesis: B256::from_str(
744 "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
745 )
746 .unwrap(),
747 forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
748 };
749 let status = Status::decode(&mut &data[..]).unwrap();
750 assert_eq!(status, expected);
751 }
752
753 #[test]
754 fn init_custom_status_fields() {
755 let mut rng = rand::rng();
756 let head_hash = rng.random();
757 let total_difficulty = U256::from(rng.random::<u64>());
758
759 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
761
762 let head = Head {
764 number: u64::MAX,
765 hash: head_hash,
766 difficulty: U256::from(13337),
767 total_difficulty,
768 timestamp: u64::MAX,
769 };
770
771 let hardforks = vec![
773 (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
774 (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
775 (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
776 (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
777 (EthereumHardfork::London, ForkCondition::Block(8)),
778 (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
779 ];
780
781 let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
782
783 for (fork, condition) in &hardforks {
784 chainspec = chainspec.with_fork(*fork, *condition);
785 }
786
787 let spec = chainspec.build();
788
789 let genesis_hash = spec.genesis_hash();
791 let mut forkhash = ForkHash::from(genesis_hash);
792 for (_, condition) in hardforks {
793 forkhash += match condition {
794 ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
795 _ => unreachable!("only block and timestamp forks are used in this test"),
796 }
797 }
798
799 let forkid = ForkId { hash: forkhash, next: 0 };
800
801 let status = UnifiedStatus::spec_builder(&spec, &head);
802
803 assert_eq!(status.chain, Chain::from_id(1337));
804 assert_eq!(status.forkid, forkid);
805 assert_eq!(status.total_difficulty.unwrap(), total_difficulty);
806 assert_eq!(status.blockhash, head_hash);
807 assert_eq!(status.genesis, genesis_hash);
808 }
809}