use crate::EthVersion;
use alloy_chains::{Chain, NamedChain};
use alloy_primitives::{hex, B256, U256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use reth_chainspec::{EthChainSpec, Hardforks, MAINNET};
use reth_codecs_derive::add_arbitrary_tests;
use reth_primitives::{EthereumHardfork, ForkId, Head};
use std::fmt::{Debug, Display};
#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[add_arbitrary_tests(rlp)]
pub struct Status {
pub version: EthVersion,
pub chain: Chain,
pub total_difficulty: U256,
pub blockhash: B256,
pub genesis: B256,
pub forkid: ForkId,
}
impl Status {
pub fn builder() -> StatusBuilder {
Default::default()
}
pub fn set_eth_version(&mut self, version: EthVersion) {
self.version = version;
}
pub fn spec_builder<Spec>(spec: Spec, head: &Head) -> StatusBuilder
where
Spec: EthChainSpec + Hardforks,
{
Self::builder()
.chain(spec.chain())
.genesis(spec.genesis_hash())
.blockhash(head.hash)
.total_difficulty(head.total_difficulty)
.forkid(spec.fork_id(head))
}
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hexed_blockhash = hex::encode(self.blockhash);
let hexed_genesis = hex::encode(self.genesis);
write!(
f,
"Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
self.version,
self.chain,
self.total_difficulty,
hexed_blockhash,
hexed_genesis,
self.forkid
)
}
}
impl Debug for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hexed_blockhash = hex::encode(self.blockhash);
let hexed_genesis = hex::encode(self.genesis);
if f.alternate() {
write!(
f,
"Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}",
self.version,
self.chain,
self.total_difficulty,
hexed_blockhash,
hexed_genesis,
self.forkid
)
} else {
write!(
f,
"Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}",
self.version,
self.chain,
self.total_difficulty,
hexed_blockhash,
hexed_genesis,
self.forkid
)
}
}
}
impl Default for Status {
fn default() -> Self {
let mainnet_genesis = MAINNET.genesis_hash();
Self {
version: EthVersion::Eth68,
chain: Chain::from_named(NamedChain::Mainnet),
total_difficulty: U256::from(17_179_869_184u64),
blockhash: mainnet_genesis,
genesis: mainnet_genesis,
forkid: MAINNET
.hardfork_fork_id(EthereumHardfork::Frontier)
.expect("The Frontier hardfork should always exist"),
}
}
}
#[derive(Debug, Default)]
pub struct StatusBuilder {
status: Status,
}
impl StatusBuilder {
pub const fn build(self) -> Status {
self.status
}
pub const fn version(mut self, version: EthVersion) -> Self {
self.status.version = version;
self
}
pub const fn chain(mut self, chain: Chain) -> Self {
self.status.chain = chain;
self
}
pub const fn total_difficulty(mut self, total_difficulty: U256) -> Self {
self.status.total_difficulty = total_difficulty;
self
}
pub const fn blockhash(mut self, blockhash: B256) -> Self {
self.status.blockhash = blockhash;
self
}
pub const fn genesis(mut self, genesis: B256) -> Self {
self.status.genesis = genesis;
self
}
pub const fn forkid(mut self, forkid: ForkId) -> Self {
self.status.forkid = forkid;
self
}
}
#[cfg(test)]
mod tests {
use crate::{EthVersion, Status};
use alloy_consensus::constants::MAINNET_GENESIS_HASH;
use alloy_genesis::Genesis;
use alloy_primitives::{hex, B256, U256};
use alloy_rlp::{Decodable, Encodable};
use rand::Rng;
use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain};
use reth_primitives::{EthereumHardfork, ForkHash, ForkId, Head};
use std::str::FromStr;
#[test]
fn encode_eth_status_message() {
let expected = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80");
let status = Status {
version: EthVersion::Eth67,
chain: Chain::from_named(NamedChain::Mainnet),
total_difficulty: U256::from(36206751599115524359527u128),
blockhash: B256::from_str(
"feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
)
.unwrap(),
genesis: MAINNET_GENESIS_HASH,
forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
};
let mut rlp_status = vec![];
status.encode(&mut rlp_status);
assert_eq!(rlp_status, expected);
}
#[test]
fn decode_eth_status_message() {
let data = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80");
let expected = Status {
version: EthVersion::Eth67,
chain: Chain::from_named(NamedChain::Mainnet),
total_difficulty: U256::from(36206751599115524359527u128),
blockhash: B256::from_str(
"feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d",
)
.unwrap(),
genesis: MAINNET_GENESIS_HASH,
forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 },
};
let status = Status::decode(&mut &data[..]).unwrap();
assert_eq!(status, expected);
}
#[test]
fn encode_network_status_message() {
let expected = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80");
let status = Status {
version: EthVersion::Eth66,
chain: Chain::from_named(NamedChain::BinanceSmartChain),
total_difficulty: U256::from(37851386u64),
blockhash: B256::from_str(
"f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
)
.unwrap(),
genesis: B256::from_str(
"0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
)
.unwrap(),
forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
};
let mut rlp_status = vec![];
status.encode(&mut rlp_status);
assert_eq!(rlp_status, expected);
}
#[test]
fn decode_network_status_message() {
let data = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80");
let expected = Status {
version: EthVersion::Eth66,
chain: Chain::from_named(NamedChain::BinanceSmartChain),
total_difficulty: U256::from(37851386u64),
blockhash: B256::from_str(
"f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b",
)
.unwrap(),
genesis: B256::from_str(
"0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b",
)
.unwrap(),
forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 },
};
let status = Status::decode(&mut &data[..]).unwrap();
assert_eq!(status, expected);
}
#[test]
fn decode_another_network_status_message() {
let data = hex!("f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880");
let expected = Status {
version: EthVersion::Eth66,
chain: Chain::from_id(2100),
total_difficulty: U256::from_str(
"0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8",
)
.unwrap(),
blockhash: B256::from_str(
"523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c",
)
.unwrap(),
genesis: B256::from_str(
"6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd",
)
.unwrap(),
forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 },
};
let status = Status::decode(&mut &data[..]).unwrap();
assert_eq!(status, expected);
}
#[test]
fn init_custom_status_fields() {
let mut rng = rand::thread_rng();
let head_hash = rng.gen();
let total_difficulty = U256::from(rng.gen::<u64>());
let genesis = Genesis { nonce: rng.gen::<u64>(), ..Default::default() };
let head = Head {
number: u64::MAX,
hash: head_hash,
difficulty: U256::from(13337),
total_difficulty,
timestamp: u64::MAX,
};
let hardforks = vec![
(EthereumHardfork::Tangerine, ForkCondition::Block(1)),
(EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)),
(EthereumHardfork::Byzantium, ForkCondition::Block(3)),
(EthereumHardfork::MuirGlacier, ForkCondition::Block(5)),
(EthereumHardfork::London, ForkCondition::Block(8)),
(EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)),
];
let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337));
for (fork, condition) in &hardforks {
chainspec = chainspec.with_fork(*fork, *condition);
}
let spec = chainspec.build();
let genesis_hash = spec.genesis_hash();
let mut forkhash = ForkHash::from(genesis_hash);
for (_, condition) in hardforks {
forkhash += match condition {
ForkCondition::Block(n) | ForkCondition::Timestamp(n) => n,
_ => unreachable!("only block and timestamp forks are used in this test"),
}
}
let forkid = ForkId { hash: forkhash, next: 0 };
let status = Status::spec_builder(&spec, &head).build();
assert_eq!(status.chain, Chain::from_id(1337));
assert_eq!(status.forkid, forkid);
assert_eq!(status.total_difficulty, total_difficulty);
assert_eq!(status.blockhash, head_hash);
assert_eq!(status.genesis, genesis_hash);
}
}