reth_eth_wire_types/
status.rs

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/// `UnifiedStatus` is an internal superset of all ETH status fields for all `eth/` versions.
11///
12/// This type can be converted into [`Status`] or [`StatusEth69`] depending on the version and
13/// unsupported fields are stripped out.
14#[derive(Clone, Debug, PartialEq, Eq, Copy)]
15pub struct UnifiedStatus {
16    /// The eth protocol version (e.g. eth/66 to eth/69).
17    pub version: EthVersion,
18    /// The chain ID identifying the peer’s network.
19    pub chain: Chain,
20    /// The genesis block hash of the peer’s chain.
21    pub genesis: B256,
22    /// The fork ID as defined by EIP-2124.
23    pub forkid: ForkId,
24    /// The latest block hash known to the peer.
25    pub blockhash: B256,
26    /// The total difficulty of the peer’s best chain (eth/66–68 only).
27    pub total_difficulty: Option<U256>,
28    /// The earliest block this node can serve (eth/69 only).
29    pub earliest_block: Option<u64>,
30    /// The latest block number this node has (eth/69 only).
31    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    /// Helper for creating the `UnifiedStatus` builder
54    pub fn builder() -> StatusBuilder {
55        Default::default()
56    }
57
58    /// Build from chain‑spec + head.  Earliest/latest default to full history.
59    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    /// Override the `(earliest, latest)` history range we’ll advertise to
75    /// eth/69 peers.
76    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    /// Sets the [`EthVersion`] for the status.
82    pub const fn set_eth_version(&mut self, v: EthVersion) {
83        self.version = v;
84    }
85
86    /// Consume this `UnifiedStatus` and produce the legacy [`Status`] message used by all
87    /// `eth/66`–`eth/68`.
88    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    /// Consume this `UnifiedStatus` and produce the [`StatusEth69`] message used by `eth/69`.
100    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    /// Convert this `UnifiedStatus` into the appropriate `StatusMessage` variant based on version.
113    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    /// Build a `UnifiedStatus` from a received `StatusMessage`.
122    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/// Builder type for constructing a [`UnifiedStatus`] message.
149#[derive(Debug, Default)]
150pub struct StatusBuilder {
151    status: UnifiedStatus,
152}
153
154impl StatusBuilder {
155    /// Consumes the builder and returns the constructed [`UnifiedStatus`].
156    pub const fn build(self) -> UnifiedStatus {
157        self.status
158    }
159
160    /// Sets the eth protocol version (e.g., eth/66, eth/69).
161    pub const fn version(mut self, version: EthVersion) -> Self {
162        self.status.version = version;
163        self
164    }
165
166    /// Sets the chain ID
167    pub const fn chain(mut self, chain: Chain) -> Self {
168        self.status.chain = chain;
169        self
170    }
171
172    /// Sets the genesis block hash of the chain.
173    pub const fn genesis(mut self, genesis: B256) -> Self {
174        self.status.genesis = genesis;
175        self
176    }
177
178    /// Sets the fork ID, used for fork compatibility checks.
179    pub const fn forkid(mut self, forkid: ForkId) -> Self {
180        self.status.forkid = forkid;
181        self
182    }
183
184    /// Sets the block hash of the current head.
185    pub const fn blockhash(mut self, blockhash: B256) -> Self {
186        self.status.blockhash = blockhash;
187        self
188    }
189
190    /// Sets the total difficulty, if relevant (Some for eth/66–68).
191    pub const fn total_difficulty(mut self, td: Option<U256>) -> Self {
192        self.status.total_difficulty = td;
193        self
194    }
195
196    /// Sets the earliest available block, if known (Some for eth/69).
197    pub const fn earliest_block(mut self, earliest: Option<u64>) -> Self {
198        self.status.earliest_block = earliest;
199        self
200    }
201
202    /// Sets the latest known block, if known (Some for eth/69).
203    pub const fn latest_block(mut self, latest: Option<u64>) -> Self {
204        self.status.latest_block = latest;
205        self
206    }
207}
208
209/// The status message is used in the eth protocol handshake to ensure that peers are on the same
210/// network and are following the same fork.
211///
212/// When performing a handshake, the total difficulty is not guaranteed to correspond to the block
213/// hash. This information should be treated as untrusted.
214#[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    /// The current protocol version. For example, peers running `eth/66` would have a version of
220    /// 66.
221    pub version: EthVersion,
222
223    /// The chain id, as introduced in
224    /// [EIP155](https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids).
225    pub chain: Chain,
226
227    /// Total difficulty of the best chain.
228    pub total_difficulty: U256,
229
230    /// The highest difficulty block hash the peer has seen
231    pub blockhash: B256,
232
233    /// The genesis hash of the peer's chain.
234    pub genesis: B256,
235
236    /// The fork identifier, a [CRC32
237    /// checksum](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm) for
238    /// identifying the peer's fork as defined by
239    /// [EIP-2124](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md).
240    /// This was added in [`eth/64`](https://eips.ethereum.org/EIPS/eip-2364)
241    pub forkid: ForkId,
242}
243
244// <https://etherscan.io/block/0>
245impl 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/// Similar to [`Status`], but for `eth/69` version, which does not contain
309/// the `total_difficulty` field.
310#[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    /// The current protocol version.
316    /// Here, version is `eth/69`.
317    pub version: EthVersion,
318
319    /// The chain id, as introduced in
320    /// [EIP155](https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids).
321    pub chain: Chain,
322
323    /// The genesis hash of the peer's chain.
324    pub genesis: B256,
325
326    /// The fork identifier, a [CRC32
327    /// checksum](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm) for
328    /// identifying the peer's fork as defined by
329    /// [EIP-2124](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md).
330    /// This was added in [`eth/64`](https://eips.ethereum.org/EIPS/eip-2364)
331    pub forkid: ForkId,
332
333    /// Earliest block number this node can serve
334    pub earliest: u64,
335
336    /// Latest block number this node has (current head)
337    pub latest: u64,
338
339    /// Hash of the latest block this node has (current head)
340    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/// `StatusMessage` can store either the Legacy version (with TD) or the
382/// eth/69 version (omits TD).
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384#[derive(Clone, Copy, Debug, PartialEq, Eq)]
385pub enum StatusMessage {
386    /// The legacy status (`eth/66` through `eth/68`) with `total_difficulty`.
387    Legacy(Status),
388    /// The new `eth/69` status with no `total_difficulty`.
389    Eth69(StatusEth69),
390}
391
392impl StatusMessage {
393    /// Returns the genesis hash from the status message.
394    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    /// Returns the protocol version.
402    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    /// Returns the chain identifier.
410    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    /// Returns the fork identifier.
418    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    /// Returns the latest block hash
426    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        // create a genesis that has a random part, so we can check that the hash is preserved
717        let genesis = Genesis { nonce: rng.random(), ..Default::default() };
718
719        // build head
720        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        // add a few hardforks
729        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        // calculate proper forkid to check against
747        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}