1use crate::{BlockRangeUpdate, EthVersion};
2use alloy_chains::Chain;
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::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::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
466 use crate::{BlockRangeUpdate, EthVersion, Status, StatusEth69, StatusMessage, UnifiedStatus};
467 use alloy_consensus::constants::MAINNET_GENESIS_HASH;
468 use alloy_genesis::Genesis;
469 use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head};
470 use alloy_primitives::{b256, hex, U256};
471 use alloy_rlp::{Decodable, Encodable};
472 use rand::Rng;
473 use reth_chainspec::{Chain, ChainSpec, ForkCondition};
474 use std::str::FromStr;
475
476 #[test]
477 fn encode_eth_status_message() {
478 let expected = hex!(
479 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
480 );
481 let status = Status {
482 version: EthVersion::Eth67,
483 chain: Chain::mainnet(),
484 total_difficulty: U256::from(36206751599115524359527u128),
485 blockhash: b256!("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"),
486 genesis: MAINNET_GENESIS_HASH,
487 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
488 };
489
490 let mut rlp_status = vec![];
491 status.encode(&mut rlp_status);
492 assert_eq!(rlp_status, expected);
493 }
494
495 #[test]
496 fn decode_eth_status_message() {
497 let data = hex!(
498 "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
499 );
500 let expected = Status {
501 version: EthVersion::Eth67,
502 chain: Chain::mainnet(),
503 total_difficulty: U256::from(36206751599115524359527u128),
504 blockhash: b256!("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"),
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 roundtrip_eth70() {
551 let unified_status = UnifiedStatus::builder()
552 .version(EthVersion::Eth70)
553 .chain(Chain::mainnet())
554 .genesis(MAINNET_GENESIS_HASH)
555 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
556 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
557 .total_difficulty(None)
558 .earliest_block(Some(1))
559 .latest_block(Some(2))
560 .build();
561
562 let status_message = unified_status.into_message();
563 let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
564 assert_eq!(unified_status, roundtripped_unified_status);
565 }
566
567 #[test]
568 fn block_range_update_for_eth69_status() {
569 let latest_hash =
570 b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
571 let status = UnifiedStatus::builder()
572 .version(EthVersion::Eth69)
573 .earliest_block(Some(10))
574 .latest_block(Some(20))
575 .blockhash(latest_hash)
576 .build();
577
578 assert_eq!(
579 status.block_range_update(),
580 Some(BlockRangeUpdate { earliest: 10, latest: 20, latest_hash })
581 );
582 }
583
584 #[test]
585 fn block_range_update_is_none_for_legacy_status() {
586 let status = UnifiedStatus::builder().version(EthVersion::Eth68).build();
587
588 assert!(status.block_range_update().is_none());
589 }
590
591 #[test]
592 fn encode_eth69_status_message() {
593 let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
594 let status = StatusEth69 {
595 version: EthVersion::Eth69,
596 chain: Chain::mainnet(),
597
598 genesis: MAINNET_GENESIS_HASH,
599 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
600 earliest: 15_537_394,
601 latest: 18_000_000,
602 blockhash: b256!("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"),
603 };
604
605 let mut rlp_status = vec![];
606 status.encode(&mut rlp_status);
607 assert_eq!(rlp_status, expected);
608
609 let status = UnifiedStatus::builder()
610 .version(EthVersion::Eth69)
611 .chain(Chain::mainnet())
612 .genesis(MAINNET_GENESIS_HASH)
613 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
614 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
615 .earliest_block(Some(15_537_394))
616 .latest_block(Some(18_000_000))
617 .build()
618 .into_message();
619
620 let mut rlp_status = vec![];
621 status.encode(&mut rlp_status);
622 assert_eq!(rlp_status, expected);
623 }
624
625 #[test]
626 fn decode_eth69_status_message() {
627 let data = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
628 let expected = StatusEth69 {
629 version: EthVersion::Eth69,
630 chain: Chain::mainnet(),
631 genesis: MAINNET_GENESIS_HASH,
632 forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
633 earliest: 15_537_394,
634 latest: 18_000_000,
635 blockhash: b256!("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"),
636 };
637 let status = StatusEth69::decode(&mut &data[..]).unwrap();
638 assert_eq!(status, expected);
639
640 let expected_message = UnifiedStatus::builder()
641 .version(EthVersion::Eth69)
642 .chain(Chain::mainnet())
643 .genesis(MAINNET_GENESIS_HASH)
644 .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
645 .earliest_block(Some(15_537_394))
646 .latest_block(Some(18_000_000))
647 .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
648 .build()
649 .into_message();
650
651 let expected_status = if let StatusMessage::Eth69(status69) = expected_message {
652 status69
653 } else {
654 panic!("expected StatusMessage::Eth69 variant");
655 };
656
657 assert_eq!(status, expected_status);
658 }
659
660 #[test]
661 fn encode_network_status_message() {
662 let expected = hex!(
663 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
664 );
665 let status = Status {
666 version: EthVersion::Eth66,
667 chain: Chain::bsc_mainnet(),
668 total_difficulty: U256::from(37851386u64),
669 blockhash: b256!("f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b"),
670 genesis: b256!("0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b"),
671 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
672 };
673
674 let mut rlp_status = vec![];
675 status.encode(&mut rlp_status);
676 assert_eq!(rlp_status, expected);
677 }
678
679 #[test]
680 fn decode_network_status_message() {
681 let data = hex!(
682 "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
683 );
684 let expected = Status {
685 version: EthVersion::Eth66,
686 chain: Chain::bsc_mainnet(),
687 total_difficulty: U256::from(37851386u64),
688 blockhash: b256!("f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b"),
689 genesis: b256!("0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b"),
690 forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
691 };
692 let status = Status::decode(&mut &data[..]).unwrap();
693 assert_eq!(status, expected);
694 }
695
696 #[test]
697 fn decode_another_network_status_message() {
698 let data = hex!(
699 "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
700 );
701 let expected = Status {
702 version: EthVersion::Eth66,
703 chain: Chain::from_id(2100),
704 total_difficulty: U256::from_str(
705 "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
706 )
707 .unwrap(),
708 blockhash: b256!("523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c"),
709 genesis: b256!("6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd"),
710 forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
711 };
712 let status = Status::decode(&mut &data[..]).unwrap();
713 assert_eq!(status, expected);
714 }
715
716 #[test]
717 fn init_custom_status_fields() {
718 let mut rng = rand::rng();
719 let head_hash = rng.random();
720 let total_difficulty = U256::from(rng.random::<u64>());
721
722 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
724
725 let head = Head {
727 number: u64::MAX,
728 hash: head_hash,
729 difficulty: U256::from(13337),
730 total_difficulty,
731 timestamp: u64::MAX,
732 };
733
734 let hardforks = vec![
736 (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
737 (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
738 (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
739 (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
740 (EthereumHardfork::London, ForkCondition::Block(8)),
741 (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
742 ];
743
744 let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
745
746 for (fork, condition) in &hardforks {
747 chainspec = chainspec.with_fork(*fork, *condition);
748 }
749
750 let spec = chainspec.build();
751
752 let genesis_hash = spec.genesis_hash();
754 let mut forkhash = ForkHash::from(genesis_hash);
755 for (_, condition) in hardforks {
756 forkhash += match condition {
757 ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
758 _ => unreachable!("only block and timestamp forks are used in this test"),
759 }
760 }
761
762 let forkid = ForkId { hash: forkhash, next: 0 };
763
764 let status = UnifiedStatus::spec_builder(&spec, &head);
765
766 assert_eq!(status.chain, Chain::from_id(1337));
767 assert_eq!(status.forkid, forkid);
768 assert_eq!(status.total_difficulty.unwrap(), total_difficulty);
769 assert_eq!(status.blockhash, head_hash);
770 assert_eq!(status.genesis, genesis_hash);
771 }
772}