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#[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 const 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 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
131impl 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#[derive(Debug, Default)]
180pub struct StatusBuilder {
181 status: Status,
182}
183
184impl StatusBuilder {
185 pub const fn build(self) -> Status {
187 self.status
188 }
189
190 pub const fn version(mut self, version: EthVersion) -> Self {
192 self.status.version = version;
193 self
194 }
195
196 pub const fn chain(mut self, chain: Chain) -> Self {
198 self.status.chain = chain;
199 self
200 }
201
202 pub const fn total_difficulty(mut self, total_difficulty: U256) -> Self {
204 self.status.total_difficulty = total_difficulty;
205 self
206 }
207
208 pub const fn blockhash(mut self, blockhash: B256) -> Self {
210 self.status.blockhash = blockhash;
211 self
212 }
213
214 pub const fn genesis(mut self, genesis: B256) -> Self {
216 self.status.genesis = genesis;
217 self
218 }
219
220 pub const fn forkid(mut self, forkid: ForkId) -> Self {
222 self.status.forkid = forkid;
223 self
224 }
225}
226
227#[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 pub version: EthVersion,
237
238 pub chain: Chain,
241
242 pub blockhash: B256,
244
245 pub genesis: B256,
247
248 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
288impl 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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub enum StatusMessage {
306 Legacy(Status),
308 Eth69(StatusEth69),
310}
311
312impl StatusMessage {
313 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 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 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 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 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: 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 let genesis = Genesis { nonce: rng.random(), ..Default::default() };
597
598 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 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 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}