reth_eth_wire_types/
status.rs

1use crate::EthVersion;
2use alloy_chains::{Chain, NamedChain};
3use alloy_primitives::{hex, B256, U256};
4use alloy_rlp::{RlpDecodable, RlpEncodable};
5use reth_chainspec::{EthChainSpec, Hardforks, MAINNET};
6use reth_codecs_derive::add_arbitrary_tests;
7use reth_ethereum_forks::{EthereumHardfork, ForkId, Head};
8use std::fmt::{Debug, Display};
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 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
73impl Display for Status {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        let hexed_blockhash = hex::encode(self.blockhash);
76        let hexed_genesis = hex::encode(self.genesis);
77        write!(
78            f,
79            "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
80            self.version,
81            self.chain,
82            self.total_difficulty,
83            hexed_blockhash,
84            hexed_genesis,
85            self.forkid
86        )
87    }
88}
89
90impl Debug for Status {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        let hexed_blockhash = hex::encode(self.blockhash);
93        let hexed_genesis = hex::encode(self.genesis);
94        if f.alternate() {
95            write!(
96                f,
97                "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
98                self.version,
99                self.chain,
100                self.total_difficulty,
101                hexed_blockhash,
102                hexed_genesis,
103                self.forkid
104            )
105        } else {
106            write!(
107                f,
108                "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
109                self.version,
110                self.chain,
111                self.total_difficulty,
112                hexed_blockhash,
113                hexed_genesis,
114                self.forkid
115            )
116        }
117    }
118}
119
120// <https://etherscan.io/block/0>
121impl Default for Status {
122    fn default() -> Self {
123        let mainnet_genesis = MAINNET.genesis_hash();
124        Self {
125            version: EthVersion::Eth68,
126            chain: Chain::from_named(NamedChain::Mainnet),
127            total_difficulty: U256::from(17_179_869_184u64),
128            blockhash: mainnet_genesis,
129            genesis: mainnet_genesis,
130            forkid: MAINNET
131                .hardfork_fork_id(EthereumHardfork::Frontier)
132                .expect("The Frontier hardfork should always exist"),
133        }
134    }
135}
136
137/// Builder for [`Status`] messages.
138///
139/// # Example
140/// ```
141/// use alloy_consensus::constants::MAINNET_GENESIS_HASH;
142/// use alloy_primitives::{B256, U256};
143/// use reth_chainspec::{Chain, EthereumHardfork, MAINNET};
144/// use reth_eth_wire_types::{EthVersion, Status};
145///
146/// // this is just an example status message!
147/// let status = Status::builder()
148///     .version(EthVersion::Eth66)
149///     .chain(Chain::mainnet())
150///     .total_difficulty(U256::from(100))
151///     .blockhash(B256::from(MAINNET_GENESIS_HASH))
152///     .genesis(B256::from(MAINNET_GENESIS_HASH))
153///     .forkid(MAINNET.hardfork_fork_id(EthereumHardfork::Paris).unwrap())
154///     .build();
155///
156/// assert_eq!(
157///     status,
158///     Status {
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///     }
166/// );
167/// ```
168#[derive(Debug, Default)]
169pub struct StatusBuilder {
170    status: Status,
171}
172
173impl StatusBuilder {
174    /// Consumes the type and creates the actual [`Status`] message.
175    pub const fn build(self) -> Status {
176        self.status
177    }
178
179    /// Sets the protocol version.
180    pub const fn version(mut self, version: EthVersion) -> Self {
181        self.status.version = version;
182        self
183    }
184
185    /// Sets the chain id.
186    pub const fn chain(mut self, chain: Chain) -> Self {
187        self.status.chain = chain;
188        self
189    }
190
191    /// Sets the total difficulty.
192    pub const fn total_difficulty(mut self, total_difficulty: U256) -> Self {
193        self.status.total_difficulty = total_difficulty;
194        self
195    }
196
197    /// Sets the block hash.
198    pub const fn blockhash(mut self, blockhash: B256) -> Self {
199        self.status.blockhash = blockhash;
200        self
201    }
202
203    /// Sets the genesis hash.
204    pub const fn genesis(mut self, genesis: B256) -> Self {
205        self.status.genesis = genesis;
206        self
207    }
208
209    /// Sets the fork id.
210    pub const fn forkid(mut self, forkid: ForkId) -> Self {
211        self.status.forkid = forkid;
212        self
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use crate::{EthVersion, Status};
219    use alloy_consensus::constants::MAINNET_GENESIS_HASH;
220    use alloy_genesis::Genesis;
221    use alloy_primitives::{hex, B256, U256};
222    use alloy_rlp::{Decodable, Encodable};
223    use rand::Rng;
224    use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
225    use reth_primitives::{EthereumHardfork, ForkHash, ForkId, Head};
226    use std::str::FromStr;
227
228    #[test]
229    fn encode_eth_status_message() {
230        let expected = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80");
231        let status = Status {
232            version: EthVersion::Eth67,
233            chain: Chain::from_named(NamedChain::Mainnet),
234            total_difficulty: U256::from(36206751599115524359527u128),
235            blockhash: B256::from_str(
236                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
237            )
238            .unwrap(),
239            genesis: MAINNET_GENESIS_HASH,
240            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
241        };
242
243        let mut rlp_status = vec![];
244        status.encode(&mut rlp_status);
245        assert_eq!(rlp_status, expected);
246    }
247
248    #[test]
249    fn decode_eth_status_message() {
250        let data = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80");
251        let expected = Status {
252            version: EthVersion::Eth67,
253            chain: Chain::from_named(NamedChain::Mainnet),
254            total_difficulty: U256::from(36206751599115524359527u128),
255            blockhash: B256::from_str(
256                "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
257            )
258            .unwrap(),
259            genesis: MAINNET_GENESIS_HASH,
260            forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
261        };
262        let status = Status::decode(&mut &data[..]).unwrap();
263        assert_eq!(status, expected);
264    }
265
266    #[test]
267    fn encode_network_status_message() {
268        let expected = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80");
269        let status = Status {
270            version: EthVersion::Eth66,
271            chain: Chain::from_named(NamedChain::BinanceSmartChain),
272            total_difficulty: U256::from(37851386u64),
273            blockhash: B256::from_str(
274                "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
275            )
276            .unwrap(),
277            genesis: B256::from_str(
278                "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
279            )
280            .unwrap(),
281            forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
282        };
283
284        let mut rlp_status = vec![];
285        status.encode(&mut rlp_status);
286        assert_eq!(rlp_status, expected);
287    }
288
289    #[test]
290    fn decode_network_status_message() {
291        let data = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80");
292        let expected = Status {
293            version: EthVersion::Eth66,
294            chain: Chain::from_named(NamedChain::BinanceSmartChain),
295            total_difficulty: U256::from(37851386u64),
296            blockhash: B256::from_str(
297                "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
298            )
299            .unwrap(),
300            genesis: B256::from_str(
301                "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
302            )
303            .unwrap(),
304            forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
305        };
306        let status = Status::decode(&mut &data[..]).unwrap();
307        assert_eq!(status, expected);
308    }
309
310    #[test]
311    fn decode_another_network_status_message() {
312        let data = hex!("f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880");
313        let expected = Status {
314            version: EthVersion::Eth66,
315            chain: Chain::from_id(2100),
316            total_difficulty: U256::from_str(
317                "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
318            )
319            .unwrap(),
320            blockhash: B256::from_str(
321                "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
322            )
323            .unwrap(),
324            genesis: B256::from_str(
325                "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
326            )
327            .unwrap(),
328            forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
329        };
330        let status = Status::decode(&mut &data[..]).unwrap();
331        assert_eq!(status, expected);
332    }
333
334    #[test]
335    fn init_custom_status_fields() {
336        let mut rng = rand::thread_rng();
337        let head_hash = rng.gen();
338        let total_difficulty = U256::from(rng.gen::<u64>());
339
340        // create a genesis that has a random part, so we can check that the hash is preserved
341        let genesis = Genesis { nonce: rng.gen(), ..Default::default() };
342
343        // build head
344        let head = Head {
345            number: u64::MAX,
346            hash: head_hash,
347            difficulty: U256::from(13337),
348            total_difficulty,
349            timestamp: u64::MAX,
350        };
351
352        // add a few hardforks
353        let hardforks = vec![
354            (EthereumHardfork::Tangerine, ForkCondition::Block(1)),
355            (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
356            (EthereumHardfork::Byzantium, ForkCondition::Block(3)),
357            (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
358            (EthereumHardfork::London, ForkCondition::Block(8)),
359            (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
360        ];
361
362        let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
363
364        for (fork, condition) in &hardforks {
365            chainspec = chainspec.with_fork(*fork, *condition);
366        }
367
368        let spec = chainspec.build();
369
370        // calculate proper forkid to check against
371        let genesis_hash = spec.genesis_hash();
372        let mut forkhash = ForkHash::from(genesis_hash);
373        for (_, condition) in hardforks {
374            forkhash += match condition {
375                ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
376                _ => unreachable!("only block and timestamp forks are used in this test"),
377            }
378        }
379
380        let forkid = ForkId { hash: forkhash, next: 0 };
381
382        let status = Status::spec_builder(&spec, &head).build();
383
384        assert_eq!(status.chain, Chain::from_id(1337));
385        assert_eq!(status.forkid, forkid);
386        assert_eq!(status.total_difficulty, total_difficulty);
387        assert_eq!(status.blockhash, head_hash);
388        assert_eq!(status.genesis, genesis_hash);
389    }
390}