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#[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 pub version: EthVersion,
23
24 pub chain: Chain,
27
28 pub total_difficulty: U256,
30
31 pub blockhash: B256,
33
34 pub genesis: B256,
36
37 pub forkid: ForkId,
43}
44
45impl Status {
46 pub fn builder() -> StatusBuilder {
48 Default::default()
49 }
50
51 pub fn set_eth_version(&mut self, version: EthVersion) {
53 self.version = version;
54 }
55
56 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
120impl 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#[derive(Debug, Default)]
169pub struct StatusBuilder {
170 status: Status,
171}
172
173impl StatusBuilder {
174 pub const fn build(self) -> Status {
176 self.status
177 }
178
179 pub const fn version(mut self, version: EthVersion) -> Self {
181 self.status.version = version;
182 self
183 }
184
185 pub const fn chain(mut self, chain: Chain) -> Self {
187 self.status.chain = chain;
188 self
189 }
190
191 pub const fn total_difficulty(mut self, total_difficulty: U256) -> Self {
193 self.status.total_difficulty = total_difficulty;
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 genesis(mut self, genesis: B256) -> Self {
205 self.status.genesis = genesis;
206 self
207 }
208
209 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 let genesis = Genesis { nonce: rng.gen(), ..Default::default() };
342
343 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 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 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}