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/// The status message is used in the eth protocol handshake to ensure that peers are on the same
11/// network and are following the same fork.
12///
13/// When performing a handshake, the total difficulty is not guaranteed to correspond to the block
14/// hash. This information should be treated as untrusted.
15#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
18#[add_arbitrary_tests(rlp)]
19pub struct Status {
20    /// The current protocol version. For example, peers running `eth/66` would have a version of
21    /// 66.
22    pub version: EthVersion,
23
24    /// The chain id, as introduced in
25    /// [EIP155](https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids).
26    pub chain: Chain,
27
28    /// Total difficulty of the best chain.
29    pub total_difficulty: U256,
30
31    /// The highest difficulty block hash the peer has seen
32    pub blockhash: B256,
33
34    /// The genesis hash of the peer's chain.
35    pub genesis: B256,
36
37    /// The fork identifier, a [CRC32
38    /// checksum](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm) for
39    /// identifying the peer's fork as defined by
40    /// [EIP-2124](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md).
41    /// This was added in [`eth/64`](https://eips.ethereum.org/EIPS/eip-2364)
42    pub forkid: ForkId,
43}
44
45impl Status {
46    /// Helper for returning a builder for the status message.
47    pub fn builder() -> StatusBuilder {
48        Default::default()
49    }
50
51    /// Sets the [`EthVersion`] for the status.
52    pub const fn set_eth_version(&mut self, version: EthVersion) {
53        self.version = version;
54    }
55
56    /// Create a [`StatusBuilder`] from the given [`EthChainSpec`] and head block.
57    ///
58    /// Sets the `chain` and `genesis`, `blockhash`, and `forkid` fields based on the
59    /// [`EthChainSpec`] and head.
60    pub fn spec_builder<Spec>(spec: Spec, head: &Head) -> StatusBuilder
61    where
62        Spec: EthChainSpec + Hardforks,
63    {
64        Self::builder()
65            .chain(spec.chain())
66            .genesis(spec.genesis_hash())
67            .blockhash(head.hash)
68            .total_difficulty(head.total_difficulty)
69            .forkid(spec.fork_id(head))
70    }
71
72    /// Converts this [`Status`] into the [Eth69](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md) variant that excludes the total difficulty field.
73    pub const fn into_eth69(self) -> StatusEth69 {
74        StatusEth69 {
75            version: EthVersion::Eth69,
76            chain: self.chain,
77            blockhash: self.blockhash,
78            genesis: self.genesis,
79            forkid: self.forkid,
80        }
81    }
82}
83
84impl Display for Status {
85    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
86        let hexed_blockhash = hex::encode(self.blockhash);
87        let hexed_genesis = hex::encode(self.genesis);
88        write!(
89            f,
90            "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
91            self.version,
92            self.chain,
93            self.total_difficulty,
94            hexed_blockhash,
95            hexed_genesis,
96            self.forkid
97        )
98    }
99}
100
101impl Debug for Status {
102    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103        let hexed_blockhash = hex::encode(self.blockhash);
104        let hexed_genesis = hex::encode(self.genesis);
105        if f.alternate() {
106            write!(
107                f,
108                "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
109                self.version,
110                self.chain,
111                self.total_difficulty,
112                hexed_blockhash,
113                hexed_genesis,
114                self.forkid
115            )
116        } else {
117            write!(
118                f,
119                "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
120                self.version,
121                self.chain,
122                self.total_difficulty,
123                hexed_blockhash,
124                hexed_genesis,
125                self.forkid
126            )
127        }
128    }
129}
130
131// <https://etherscan.io/block/0>
132impl Default for Status {
133    fn default() -> Self {
134        let mainnet_genesis = MAINNET.genesis_hash();
135        Self {
136            version: EthVersion::Eth68,
137            chain: Chain::from_named(NamedChain::Mainnet),
138            total_difficulty: U256::from(17_179_869_184u64),
139            blockhash: mainnet_genesis,
140            genesis: mainnet_genesis,
141            forkid: MAINNET
142                .hardfork_fork_id(EthereumHardfork::Frontier)
143                .expect("The Frontier hardfork should always exist"),
144        }
145    }
146}
147
148/// Builder for [`Status`] messages.
149///
150/// # Example
151/// ```
152/// use alloy_consensus::constants::MAINNET_GENESIS_HASH;
153/// use alloy_primitives::{B256, U256};
154/// use reth_chainspec::{Chain, EthereumHardfork, MAINNET};
155/// use reth_eth_wire_types::{EthVersion, Status};
156///
157/// // this is just an example status message!
158/// let status = Status::builder()
159///     .version(EthVersion::Eth66)
160///     .chain(Chain::mainnet())
161///     .total_difficulty(U256::from(100))
162///     .blockhash(B256::from(MAINNET_GENESIS_HASH))
163///     .genesis(B256::from(MAINNET_GENESIS_HASH))
164///     .forkid(MAINNET.hardfork_fork_id(EthereumHardfork::Paris).unwrap())
165///     .build();
166///
167/// assert_eq!(
168///     status,
169///     Status {
170///         version: EthVersion::Eth66,
171///         chain: Chain::mainnet(),
172///         total_difficulty: U256::from(100),
173///         blockhash: B256::from(MAINNET_GENESIS_HASH),
174///         genesis: B256::from(MAINNET_GENESIS_HASH),
175///         forkid: MAINNET.hardfork_fork_id(EthereumHardfork::Paris).unwrap(),
176///     }
177/// );
178/// ```
179#[derive(Debug, Default)]
180pub struct StatusBuilder {
181    status: Status,
182}
183
184impl StatusBuilder {
185    /// Consumes the type and creates the actual [`Status`] message.
186    pub const fn build(self) -> Status {
187        self.status
188    }
189
190    /// Sets the protocol version.
191    pub const fn version(mut self, version: EthVersion) -> Self {
192        self.status.version = version;
193        self
194    }
195
196    /// Sets the chain id.
197    pub const fn chain(mut self, chain: Chain) -> Self {
198        self.status.chain = chain;
199        self
200    }
201
202    /// Sets the total difficulty.
203    pub const fn total_difficulty(mut self, total_difficulty: U256) -> Self {
204        self.status.total_difficulty = total_difficulty;
205        self
206    }
207
208    /// Sets the block hash.
209    pub const fn blockhash(mut self, blockhash: B256) -> Self {
210        self.status.blockhash = blockhash;
211        self
212    }
213
214    /// Sets the genesis hash.
215    pub const fn genesis(mut self, genesis: B256) -> Self {
216        self.status.genesis = genesis;
217        self
218    }
219
220    /// Sets the fork id.
221    pub const fn forkid(mut self, forkid: ForkId) -> Self {
222        self.status.forkid = forkid;
223        self
224    }
225}
226
227/// Similar to [`Status`], but for `eth/69` version, which does not contain
228/// the `total_difficulty` field.
229#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
230#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
231#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
232#[add_arbitrary_tests(rlp)]
233pub struct StatusEth69 {
234    /// The current protocol version.
235    /// Here, version is `eth/69`.
236    pub version: EthVersion,
237
238    /// The chain id, as introduced in
239    /// [EIP155](https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids).
240    pub chain: Chain,
241
242    /// The highest difficulty block hash the peer has seen
243    pub blockhash: B256,
244
245    /// The genesis hash of the peer's chain.
246    pub genesis: B256,
247
248    /// The fork identifier, a [CRC32
249    /// checksum](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm) for
250    /// identifying the peer's fork as defined by
251    /// [EIP-2124](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md).
252    /// This was added in [`eth/64`](https://eips.ethereum.org/EIPS/eip-2364)
253    pub forkid: ForkId,
254}
255
256impl Display for StatusEth69 {
257    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
258        let hexed_blockhash = hex::encode(self.blockhash);
259        let hexed_genesis = hex::encode(self.genesis);
260        write!(
261            f,
262            "Status {{ version: {}, chain: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
263            self.version, self.chain, hexed_blockhash, hexed_genesis, self.forkid
264        )
265    }
266}
267
268impl Debug for StatusEth69 {
269    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
270        let hexed_blockhash = hex::encode(self.blockhash);
271        let hexed_genesis = hex::encode(self.genesis);
272        if f.alternate() {
273            write!(
274                f,
275                "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
276                self.version, self.chain, hexed_blockhash, hexed_genesis, self.forkid
277            )
278        } else {
279            write!(
280                f,
281                "Status {{ version: {:?}, chain: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
282                self.version, self.chain, hexed_blockhash, hexed_genesis, self.forkid
283            )
284        }
285    }
286}
287
288// <https://etherscan.io/block/0>
289impl Default for StatusEth69 {
290    fn default() -> Self {
291        Status::default().into()
292    }
293}
294
295impl From<Status> for StatusEth69 {
296    fn from(status: Status) -> Self {
297        status.into_eth69()
298    }
299}
300
301/// `StatusMessage` can store either the Legacy version (with TD) or the
302/// eth/69 version (omits TD).
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub enum StatusMessage {
306    /// The legacy status (`eth/66` through `eth/68`) with `total_difficulty`.
307    Legacy(Status),
308    /// The new `eth/69` status with no `total_difficulty`.
309    Eth69(StatusEth69),
310}
311
312impl StatusMessage {
313    /// Returns the genesis hash from the status message.
314    pub const fn genesis(&self) -> B256 {
315        match self {
316            Self::Legacy(legacy_status) => legacy_status.genesis,
317            Self::Eth69(status_69) => status_69.genesis,
318        }
319    }
320
321    /// Returns the protocol version.
322    pub const fn version(&self) -> EthVersion {
323        match self {
324            Self::Legacy(legacy_status) => legacy_status.version,
325            Self::Eth69(status_69) => status_69.version,
326        }
327    }
328
329    /// Returns the chain identifier.
330    pub const fn chain(&self) -> &Chain {
331        match self {
332            Self::Legacy(legacy_status) => &legacy_status.chain,
333            Self::Eth69(status_69) => &status_69.chain,
334        }
335    }
336
337    /// Returns the fork identifier.
338    pub const fn forkid(&self) -> ForkId {
339        match self {
340            Self::Legacy(legacy_status) => legacy_status.forkid,
341            Self::Eth69(status_69) => status_69.forkid,
342        }
343    }
344
345    /// Converts to legacy Status since full support for EIP-7642
346    /// is not fully implemented
347    /// `<https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md>`
348    pub fn to_legacy(self) -> Status {
349        match self {
350            Self::Legacy(legacy_status) => legacy_status,
351            Self::Eth69(status_69) => Status {
352                version: status_69.version,
353                chain: status_69.chain,
354                // total_difficulty is omitted in Eth69.
355                total_difficulty: U256::default(),
356                blockhash: status_69.blockhash,
357                genesis: status_69.genesis,
358                forkid: status_69.forkid,
359            },
360        }
361    }
362}
363
364impl Encodable for StatusMessage {
365    fn encode(&self, out: &mut dyn BufMut) {
366        match self {
367            Self::Legacy(s) => s.encode(out),
368            Self::Eth69(s) => s.encode(out),
369        }
370    }
371
372    fn length(&self) -> usize {
373        match self {
374            Self::Legacy(s) => s.length(),
375            Self::Eth69(s) => s.length(),
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use crate::{EthVersion, Status, StatusEth69};
383    use alloy_consensus::constants::MAINNET_GENESIS_HASH;
384    use alloy_genesis::Genesis;
385    use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head};
386    use alloy_primitives::{hex, B256, U256};
387    use alloy_rlp::{Decodable, Encodable};
388    use rand::Rng;
389    use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
390    use std::str::FromStr;
391
392    #[test]
393    fn encode_eth_status_message() {
394        let expected = hex!(
395            "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
396        );
397        let status = Status {
398            version: EthVersion::Eth67,
399            chain: Chain::from_named(NamedChain::Mainnet),
400            total_difficulty: U256::from(36206751599115524359527u128),
401            blockhash: B256::from_str(
402                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
403            )
404            .unwrap(),
405            genesis: MAINNET_GENESIS_HASH,
406            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
407        };
408
409        let mut rlp_status = vec![];
410        status.encode(&mut rlp_status);
411        assert_eq!(rlp_status, expected);
412    }
413
414    #[test]
415    fn decode_eth_status_message() {
416        let data = hex!(
417            "f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
418        );
419        let expected = Status {
420            version: EthVersion::Eth67,
421            chain: Chain::from_named(NamedChain::Mainnet),
422            total_difficulty: U256::from(36206751599115524359527u128),
423            blockhash: B256::from_str(
424                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
425            )
426            .unwrap(),
427            genesis: MAINNET_GENESIS_HASH,
428            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
429        };
430        let status = Status::decode(&mut &data[..]).unwrap();
431        assert_eq!(status, expected);
432    }
433
434    #[test]
435    fn test_status_to_statuseth69_conversion() {
436        let status = StatusEth69 {
437            version: EthVersion::Eth69,
438            chain: Chain::from_named(NamedChain::Mainnet),
439            blockhash: B256::from_str(
440                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
441            )
442            .unwrap(),
443            genesis: MAINNET_GENESIS_HASH,
444            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
445        };
446        let status_converted: StatusEth69 = Status {
447            version: EthVersion::Eth69,
448            chain: Chain::from_named(NamedChain::Mainnet),
449            total_difficulty: U256::from(36206751599115524359527u128),
450            blockhash: B256::from_str(
451                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
452            )
453            .unwrap(),
454            genesis: MAINNET_GENESIS_HASH,
455            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
456        }
457        .into();
458        assert_eq!(status, status_converted);
459    }
460
461    #[test]
462    fn encode_eth69_status_message() {
463        let expected = hex!(
464            "f84b4501a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
465        );
466        let status = StatusEth69 {
467            version: EthVersion::Eth69,
468            chain: Chain::from_named(NamedChain::Mainnet),
469            blockhash: B256::from_str(
470                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
471            )
472            .unwrap(),
473            genesis: MAINNET_GENESIS_HASH,
474            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
475        };
476
477        let mut rlp_status = vec![];
478        status.encode(&mut rlp_status);
479        assert_eq!(rlp_status, expected);
480
481        let status: StatusEth69 = Status::builder()
482            .chain(Chain::from_named(NamedChain::Mainnet))
483            .blockhash(
484                B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")
485                    .unwrap(),
486            )
487            .genesis(MAINNET_GENESIS_HASH)
488            .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
489            .build()
490            .into();
491        let mut rlp_status = vec![];
492        status.encode(&mut rlp_status);
493        assert_eq!(rlp_status, expected);
494    }
495
496    #[test]
497    fn decode_eth69_status_message() {
498        let data = hex!(
499            "0xf84b4501a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"
500        );
501        let expected = StatusEth69 {
502            version: EthVersion::Eth69,
503            chain: Chain::from_named(NamedChain::Mainnet),
504            blockhash: B256::from_str(
505                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
506            )
507            .unwrap(),
508            genesis: MAINNET_GENESIS_HASH,
509            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
510        };
511        let status = StatusEth69::decode(&mut &data[..]).unwrap();
512        assert_eq!(status, expected);
513    }
514
515    #[test]
516    fn encode_network_status_message() {
517        let expected = hex!(
518            "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
519        );
520        let status = Status {
521            version: EthVersion::Eth66,
522            chain: Chain::from_named(NamedChain::BinanceSmartChain),
523            total_difficulty: U256::from(37851386u64),
524            blockhash: B256::from_str(
525                "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
526            )
527            .unwrap(),
528            genesis: B256::from_str(
529                "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
530            )
531            .unwrap(),
532            forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
533        };
534
535        let mut rlp_status = vec![];
536        status.encode(&mut rlp_status);
537        assert_eq!(rlp_status, expected);
538    }
539
540    #[test]
541    fn decode_network_status_message() {
542        let data = hex!(
543            "f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"
544        );
545        let expected = Status {
546            version: EthVersion::Eth66,
547            chain: Chain::from_named(NamedChain::BinanceSmartChain),
548            total_difficulty: U256::from(37851386u64),
549            blockhash: B256::from_str(
550                "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
551            )
552            .unwrap(),
553            genesis: B256::from_str(
554                "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
555            )
556            .unwrap(),
557            forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
558        };
559        let status = Status::decode(&mut &data[..]).unwrap();
560        assert_eq!(status, expected);
561    }
562
563    #[test]
564    fn decode_another_network_status_message() {
565        let data = hex!(
566            "f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"
567        );
568        let expected = Status {
569            version: EthVersion::Eth66,
570            chain: Chain::from_id(2100),
571            total_difficulty: U256::from_str(
572                "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
573            )
574            .unwrap(),
575            blockhash: B256::from_str(
576                "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
577            )
578            .unwrap(),
579            genesis: B256::from_str(
580                "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
581            )
582            .unwrap(),
583            forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
584        };
585        let status = Status::decode(&mut &data[..]).unwrap();
586        assert_eq!(status, expected);
587    }
588
589    #[test]
590    fn init_custom_status_fields() {
591        let mut rng = rand::rng();
592        let head_hash = rng.random();
593        let total_difficulty = U256::from(rng.random::<u64>());
594
595        // create a genesis that has a random part, so we can check that the hash is preserved
596        let genesis = Genesis { nonce: rng.random(), ..Default::default() };
597
598        // build head
599        let head = Head {
600            number: u64::MAX,
601            hash: head_hash,
602            difficulty: U256::from(13337),
603            total_difficulty,
604            timestamp: u64::MAX,
605        };
606
607        // add a few hardforks
608        let hardforks = vec![
609            (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
610            (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
611            (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
612            (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
613            (EthereumHardfork::London, ForkCondition::Block(8)),
614            (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
615        ];
616
617        let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
618
619        for (fork, condition) in &hardforks {
620            chainspec = chainspec.with_fork(*fork, *condition);
621        }
622
623        let spec = chainspec.build();
624
625        // calculate proper forkid to check against
626        let genesis_hash = spec.genesis_hash();
627        let mut forkhash = ForkHash::from(genesis_hash);
628        for (_, condition) in hardforks {
629            forkhash += match condition {
630                ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
631                _ => unreachable!("only block and timestamp forks are used in this test"),
632            }
633        }
634
635        let forkid = ForkId { hash: forkhash, next: 0 };
636
637        let status = Status::spec_builder(&spec, &head).build();
638
639        assert_eq!(status.chain, Chain::from_id(1337));
640        assert_eq!(status.forkid, forkid);
641        assert_eq!(status.total_difficulty, total_difficulty);
642        assert_eq!(status.blockhash, head_hash);
643        assert_eq!(status.genesis, genesis_hash);
644    }
645}