feat: support time-based forking (#985)

This commit is contained in:
Aurélien
2023-01-27 16:49:54 +01:00
committed by GitHub
parent 8cfe24081e
commit 9cdead5646
17 changed files with 453 additions and 312 deletions

View File

@@ -7,7 +7,7 @@ use std::{fmt, str::FromStr};
// The chain spec module.
mod spec;
pub use spec::{ChainSpec, ChainSpecBuilder, ParisStatus, GOERLI, MAINNET, SEPOLIA};
pub use spec::{ChainSpec, ChainSpecBuilder, GOERLI, MAINNET, SEPOLIA};
// The chain info module.
mod info;

View File

@@ -1,6 +1,6 @@
use crate::{
BlockNumber, Chain, ForkFilter, ForkHash, ForkId, Genesis, GenesisAccount, Hardfork, Header,
H160, H256, U256,
BlockNumber, Chain, ForkDiscriminant, ForkFilter, ForkHash, ForkId, ForkKind, Genesis,
GenesisAccount, Hardfork, Header, H160, H256, U256,
};
use ethers_core::utils::Genesis as EthersGenesis;
use hex_literal::hex;
@@ -15,25 +15,24 @@ pub static MAINNET: Lazy<ChainSpec> = Lazy::new(|| ChainSpec {
.expect("Can't deserialize Mainnet genesis json"),
genesis_hash: H256(hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")),
hardforks: BTreeMap::from([
(Hardfork::Frontier, 0),
(Hardfork::Homestead, 1150000),
(Hardfork::Dao, 1920000),
(Hardfork::Tangerine, 2463000),
(Hardfork::SpuriousDragon, 2675000),
(Hardfork::Byzantium, 4370000),
(Hardfork::Constantinople, 7280000),
(Hardfork::Petersburg, 7280000),
(Hardfork::Istanbul, 9069000),
(Hardfork::Muirglacier, 9200000),
(Hardfork::Berlin, 12244000),
(Hardfork::London, 12965000),
(Hardfork::ArrowGlacier, 13773000),
(Hardfork::GrayGlacier, 15050000),
(Hardfork::Latest, 15050000),
(Hardfork::Frontier, ForkKind::Block(0)),
(Hardfork::Homestead, ForkKind::Block(1150000)),
(Hardfork::Dao, ForkKind::Block(1920000)),
(Hardfork::Tangerine, ForkKind::Block(2463000)),
(Hardfork::SpuriousDragon, ForkKind::Block(2675000)),
(Hardfork::Byzantium, ForkKind::Block(4370000)),
(Hardfork::Constantinople, ForkKind::Block(7280000)),
(Hardfork::Petersburg, ForkKind::Block(7280000)),
(Hardfork::Istanbul, ForkKind::Block(9069000)),
(Hardfork::Muirglacier, ForkKind::Block(9200000)),
(Hardfork::Berlin, ForkKind::Block(12244000)),
(Hardfork::London, ForkKind::Block(12965000)),
(Hardfork::ArrowGlacier, ForkKind::Block(13773000)),
(Hardfork::GrayGlacier, ForkKind::Block(15050000)),
(Hardfork::Paris, ForkKind::TTD(Some(15537394))),
]),
dao_fork_support: true,
paris_block: Some(15537394),
paris_ttd: Some(U256::from(58750000000000000000000_u128)),
paris_ttd: Some(U256::from(58_750_000_000_000_000_000_000_u128)),
});
/// The Goerli spec
@@ -43,14 +42,14 @@ pub static GOERLI: Lazy<ChainSpec> = Lazy::new(|| ChainSpec {
.expect("Can't deserialize Goerli genesis json"),
genesis_hash: H256(hex!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")),
hardforks: BTreeMap::from([
(Hardfork::Frontier, 0),
(Hardfork::Istanbul, 1561651),
(Hardfork::Berlin, 4460644),
(Hardfork::London, 5062605),
(Hardfork::Frontier, ForkKind::Block(0)),
(Hardfork::Istanbul, ForkKind::Block(1561651)),
(Hardfork::Berlin, ForkKind::Block(4460644)),
(Hardfork::London, ForkKind::Block(5062605)),
(Hardfork::Paris, ForkKind::TTD(Some(7382818))),
]),
dao_fork_support: true,
paris_block: Some(7382818),
paris_ttd: Some(U256::from(10790000)),
paris_ttd: Some(U256::from(10_790_000)),
});
/// The Sepolia spec
@@ -60,23 +59,22 @@ pub static SEPOLIA: Lazy<ChainSpec> = Lazy::new(|| ChainSpec {
.expect("Can't deserialize Sepolia genesis json"),
genesis_hash: H256(hex!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")),
hardforks: BTreeMap::from([
(Hardfork::Frontier, 0),
(Hardfork::Homestead, 0),
(Hardfork::Dao, 0),
(Hardfork::Tangerine, 0),
(Hardfork::SpuriousDragon, 0),
(Hardfork::Byzantium, 0),
(Hardfork::Constantinople, 0),
(Hardfork::Petersburg, 0),
(Hardfork::Istanbul, 0),
(Hardfork::Muirglacier, 0),
(Hardfork::Berlin, 0),
(Hardfork::London, 0),
(Hardfork::MergeNetsplit, 1735371),
(Hardfork::Frontier, ForkKind::Block(0)),
(Hardfork::Homestead, ForkKind::Block(0)),
(Hardfork::Dao, ForkKind::Block(0)),
(Hardfork::Tangerine, ForkKind::Block(0)),
(Hardfork::SpuriousDragon, ForkKind::Block(0)),
(Hardfork::Byzantium, ForkKind::Block(0)),
(Hardfork::Constantinople, ForkKind::Block(0)),
(Hardfork::Petersburg, ForkKind::Block(0)),
(Hardfork::Istanbul, ForkKind::Block(0)),
(Hardfork::Muirglacier, ForkKind::Block(0)),
(Hardfork::Berlin, ForkKind::Block(0)),
(Hardfork::London, ForkKind::Block(0)),
(Hardfork::Paris, ForkKind::Block(1735371)),
]),
dao_fork_support: true,
paris_block: Some(1450408),
paris_ttd: Some(U256::from(17000000000000000_u64)),
paris_ttd: Some(U256::from(17_000_000_000_000_000_u64)),
});
/// The Ethereum chain spec
@@ -92,14 +90,11 @@ pub struct ChainSpec {
pub genesis_hash: H256,
/// The active hard forks and their block numbers
pub hardforks: BTreeMap<Hardfork, BlockNumber>,
pub hardforks: BTreeMap<Hardfork, ForkKind>,
/// Whether or not the DAO fork is supported
pub dao_fork_support: bool,
/// The block number of the merge
pub paris_block: Option<u64>,
/// The merge terminal total difficulty
pub paris_ttd: Option<U256>,
}
@@ -120,19 +115,42 @@ impl ChainSpec {
self.genesis_hash
}
/// Returns the supported hardforks and their fork block numbers
pub fn hardforks(&self) -> &BTreeMap<Hardfork, BlockNumber> {
/// Returns the supported hardforks and their [ForkKind]
pub fn hardforks(&self) -> &BTreeMap<Hardfork, ForkKind> {
&self.hardforks
}
/// Get the first block number of the hardfork.
/// Get the first block number of the given hardfork. This method returns `None` for
/// timestamp-based forks and if the merge netsplit block is not known (when the given fork
/// is TTD-based)
pub fn fork_block(&self, fork: Hardfork) -> Option<BlockNumber> {
self.hardforks.get(&fork).and_then(|kind| match kind {
ForkKind::Block(block_number) => Some(*block_number),
ForkKind::TTD(block_number) => *block_number,
_ => None,
})
}
/// Get the first block number/timestamp of the hardfork.
pub fn fork_kind(&self, fork: Hardfork) -> Option<ForkKind> {
self.hardforks.get(&fork).copied()
}
/// Returns `true` if the given fork is active on the given block
pub fn fork_active(&self, fork: Hardfork, current_block: BlockNumber) -> bool {
self.fork_block(fork).map(|target| target <= current_block).unwrap_or_default()
/// Returns `true` if the given fork is active on the given should update docs here to reflect
/// that this returns true if the fork is active on the given block / ttd / timestamp contained
/// in the [ForkDiscriminant]
pub fn fork_active(&self, fork: Hardfork, discriminant: ForkDiscriminant) -> bool {
match self.hardforks.get(&fork) {
Some(kind) => match kind {
ForkKind::Block(block_number) => *block_number <= discriminant.block_number,
ForkKind::TTD(block_number) => {
self.paris_ttd <= Some(discriminant.total_difficulty) ||
*block_number <= Some(discriminant.block_number)
}
ForkKind::Time(timestamp) => *timestamp <= discriminant.timestamp,
},
None => false,
}
}
/// Returns `true` if the DAO fork is supported
@@ -140,42 +158,56 @@ impl ChainSpec {
self.dao_fork_support
}
/// Get the Paris status
pub fn paris_status(&self) -> ParisStatus {
match self.paris_ttd {
Some(terminal_total_difficulty) => {
ParisStatus::Supported { terminal_total_difficulty, block: self.paris_block }
}
None => ParisStatus::NotSupported,
}
/// The merge terminal total difficulty
pub fn terminal_total_difficulty(&self) -> Option<U256> {
self.paris_ttd
}
/// Get an iterator of all harforks with theirs respectives block number
pub fn forks_iter(&self) -> impl Iterator<Item = (Hardfork, BlockNumber)> + '_ {
/// Get an iterator of all harforks with theirs respectives [ForkKind]
pub fn forks_iter(&self) -> impl Iterator<Item = (Hardfork, ForkKind)> + '_ {
self.hardforks.iter().map(|(f, b)| (*f, *b))
}
/// Creates a [`ForkFilter`](crate::ForkFilter) for the given [BlockNumber].
pub fn fork_filter(&self, block: BlockNumber) -> ForkFilter {
let future_forks =
self.forks_iter().map(|(_, b)| b).filter(|b| *b > block).collect::<Vec<_>>();
/// Creates a [`ForkFilter`](crate::ForkFilter) for the given [ForkDiscriminant].
pub fn fork_filter(&self, discriminant: ForkDiscriminant) -> ForkFilter {
let future_forks = self
.forks_iter()
.filter(|(f, _)| !self.fork_active(*f, discriminant))
.filter_map(|(_, k)| match k {
ForkKind::Block(block_number) => Some(block_number),
ForkKind::TTD(_) => None,
ForkKind::Time(timestamp) => Some(timestamp),
})
.collect::<Vec<_>>();
ForkFilter::new(block, self.genesis_hash(), future_forks)
ForkFilter::new(discriminant.block_number, self.genesis_hash(), future_forks)
}
/// Compute the forkid for the given [BlockNumber]
pub fn fork_id(&self, block: BlockNumber) -> ForkId {
/// Compute the forkid for the given [ForkDiscriminant]
pub fn fork_id(&self, discriminant: ForkDiscriminant) -> ForkId {
let mut curr_forkhash = ForkHash::from(self.genesis_hash());
let mut curr_block_number = 0;
let mut forks =
self.forks_iter().filter(|(_, k)| !k.is_active_at_genesis()).collect::<Vec<_>>();
for (_, b) in self.forks_iter() {
if block >= b {
if b != curr_block_number {
curr_forkhash += b;
curr_block_number = b;
forks.dedup_by(|a, b| a.1 == b.1);
for (_, kind) in forks {
match kind {
ForkKind::Block(b) => {
if discriminant.block_number >= b {
curr_forkhash += b;
} else {
return ForkId { hash: curr_forkhash, next: b }
}
}
} else {
return ForkId { hash: curr_forkhash, next: b }
ForkKind::Time(t) => {
if discriminant.timestamp >= t {
curr_forkhash += t;
} else {
return ForkId { hash: curr_forkhash, next: t }
}
}
_ => {}
}
}
ForkId { hash: curr_forkhash, next: 0 }
@@ -208,7 +240,7 @@ impl From<EthersGenesis> for ChainSpec {
let genesis_hash = Header::from(genesis_block.clone()).seal().hash();
let paris_ttd = genesis.config.terminal_total_difficulty.map(|ttd| ttd.into());
let hardfork_opts = vec![
let mut hardfork_opts = vec![
(Hardfork::Homestead, genesis.config.homestead_block),
(Hardfork::Dao, genesis.config.dao_fork_block),
(Hardfork::Tangerine, genesis.config.eip150_block),
@@ -222,12 +254,17 @@ impl From<EthersGenesis> for ChainSpec {
(Hardfork::London, genesis.config.london_block),
(Hardfork::ArrowGlacier, genesis.config.arrow_glacier_block),
(Hardfork::GrayGlacier, genesis.config.gray_glacier_block),
(Hardfork::MergeNetsplit, genesis.config.merge_netsplit_block),
];
// Paris block is not used to fork, and is not used in genesis.json
// except in Sepolia
if genesis.config.chain_id == Chain::sepolia().id() {
hardfork_opts.push((Hardfork::Paris, genesis.config.merge_netsplit_block))
}
let configured_hardforks = hardfork_opts
.iter()
.filter_map(|(hardfork, opt)| opt.map(|block| (*hardfork, block)))
.filter_map(|(hardfork, opt)| opt.map(|block| (*hardfork, ForkKind::Block(block))))
.collect::<BTreeMap<_, _>>();
Self {
@@ -237,8 +274,6 @@ impl From<EthersGenesis> for ChainSpec {
hardforks: configured_hardforks,
genesis_hash,
paris_ttd,
// paris block is not used to fork, and is not used in genesis.json
paris_block: None,
}
}
}
@@ -249,9 +284,8 @@ pub struct ChainSpecBuilder {
chain: Option<Chain>,
genesis: Option<Genesis>,
genesis_hash: Option<H256>,
hardforks: BTreeMap<Hardfork, BlockNumber>,
hardforks: BTreeMap<Hardfork, ForkKind>,
dao_fork_support: bool,
paris_block: Option<u64>,
paris_ttd: Option<U256>,
}
@@ -264,7 +298,6 @@ impl ChainSpecBuilder {
genesis_hash: Some(MAINNET.genesis_hash),
hardforks: MAINNET.hardforks.clone(),
dao_fork_support: MAINNET.dao_fork_support,
paris_block: MAINNET.paris_block,
paris_ttd: MAINNET.paris_ttd,
}
}
@@ -287,84 +320,98 @@ impl ChainSpecBuilder {
self
}
/// Insert the given fork at the given block number
pub fn with_fork(mut self, fork: Hardfork, block: BlockNumber) -> Self {
self.hardforks.insert(fork, block);
/// Remove all [Hardfork]s from the spec. Useful when the builder has been created from a
/// existing chain spec.
pub fn clear_forks(mut self) -> Self {
self.hardforks.clear();
self
}
/// Insert the given fork at the given [ForkKind]
pub fn with_fork(mut self, fork: Hardfork, kind: ForkKind) -> Self {
self.hardforks.insert(fork, kind);
self
}
/// Enables Frontier
pub fn frontier_activated(mut self) -> Self {
self.hardforks.insert(Hardfork::Frontier, 0);
self.hardforks.insert(Hardfork::Frontier, ForkKind::Block(0));
self
}
/// Enables Homestead
pub fn homestead_activated(mut self) -> Self {
self = self.frontier_activated();
self.hardforks.insert(Hardfork::Homestead, 0);
self.hardforks.insert(Hardfork::Homestead, ForkKind::Block(0));
self
}
/// Enables Tangerine
pub fn tangerine_whistle_activated(mut self) -> Self {
self = self.homestead_activated();
self.hardforks.insert(Hardfork::Tangerine, 0);
self.hardforks.insert(Hardfork::Tangerine, ForkKind::Block(0));
self
}
/// Enables SpuriousDragon
pub fn spurious_dragon_activated(mut self) -> Self {
self = self.tangerine_whistle_activated();
self.hardforks.insert(Hardfork::SpuriousDragon, 0);
self.hardforks.insert(Hardfork::SpuriousDragon, ForkKind::Block(0));
self
}
/// Enables Byzantium
pub fn byzantium_activated(mut self) -> Self {
self = self.spurious_dragon_activated();
self.hardforks.insert(Hardfork::Byzantium, 0);
self.hardforks.insert(Hardfork::Byzantium, ForkKind::Block(0));
self
}
/// Enables Petersburg
pub fn petersburg_activated(mut self) -> Self {
self = self.byzantium_activated();
self.hardforks.insert(Hardfork::Petersburg, 0);
self.hardforks.insert(Hardfork::Petersburg, ForkKind::Block(0));
self
}
/// Enables Istanbul
pub fn istanbul_activated(mut self) -> Self {
self = self.petersburg_activated();
self.hardforks.insert(Hardfork::Istanbul, 0);
self.hardforks.insert(Hardfork::Istanbul, ForkKind::Block(0));
self
}
/// Enables Berlin
pub fn berlin_activated(mut self) -> Self {
self = self.istanbul_activated();
self.hardforks.insert(Hardfork::Berlin, 0);
self.hardforks.insert(Hardfork::Berlin, ForkKind::Block(0));
self
}
/// Enables London
pub fn london_activated(mut self) -> Self {
self = self.berlin_activated();
self.hardforks.insert(Hardfork::London, 0);
self
}
/// Sets the DAO fork as supported
pub fn dao_fork_supported(mut self) -> Self {
self.dao_fork_support = true;
self.hardforks.insert(Hardfork::London, ForkKind::Block(0));
self
}
/// Enables Paris
pub fn paris_activated(mut self) -> Self {
self = self.berlin_activated();
self.paris_block = Some(0);
self.hardforks.insert(Hardfork::Paris, ForkKind::TTD(Some(0)));
self
}
/// Enables Shangai
pub fn shangai_activated(mut self) -> Self {
self = self.paris_activated();
self.hardforks.insert(Hardfork::Shanghai, ForkKind::Time(0));
self
}
/// Sets the DAO fork as supported
pub fn dao_fork_supported(mut self) -> Self {
self.dao_fork_support = true;
self
}
@@ -376,7 +423,6 @@ impl ChainSpecBuilder {
genesis_hash: self.genesis_hash.expect("The genesis hash is required"),
hardforks: self.hardforks,
dao_fork_support: self.dao_fork_support,
paris_block: self.paris_block,
paris_ttd: self.paris_ttd,
}
}
@@ -390,51 +436,17 @@ impl From<&ChainSpec> for ChainSpecBuilder {
genesis_hash: Some(value.genesis_hash),
hardforks: value.hardforks.clone(),
dao_fork_support: value.dao_fork_support,
paris_block: value.paris_block,
paris_ttd: value.paris_ttd,
}
}
}
/// Merge Status
#[derive(Debug)]
pub enum ParisStatus {
/// Paris is not supported
NotSupported,
/// Paris settings has been set in the chain spec
Supported {
/// The merge terminal total difficulty
terminal_total_difficulty: U256,
/// The Paris block number
block: Option<BlockNumber>,
},
}
impl ParisStatus {
/// Returns the Paris block number if it is known ahead of time.
///
/// This is only the case for chains that have already activated the merge.
pub fn block_number(&self) -> Option<BlockNumber> {
match &self {
ParisStatus::NotSupported => None,
ParisStatus::Supported { block, .. } => *block,
}
}
/// Returns the merge terminal total difficulty
pub fn terminal_total_difficulty(&self) -> Option<U256> {
match &self {
ParisStatus::NotSupported => None,
ParisStatus::Supported { terminal_total_difficulty, .. } => {
Some(*terminal_total_difficulty)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{Chain, ChainSpec, ForkHash, Genesis, Hardfork, Header, GOERLI, MAINNET, SEPOLIA};
use crate::{
Chain, ChainSpec, ForkDiscriminant, ForkHash, ForkKind, Genesis, Hardfork, Header, GOERLI,
MAINNET, SEPOLIA,
};
#[test]
fn test_empty_forkid() {
@@ -446,22 +458,23 @@ mod tests {
.chain(Chain::mainnet())
.genesis(empty_genesis)
.genesis_hash(empty_sealed.hash())
.with_fork(Hardfork::Frontier, 0)
.with_fork(Hardfork::Homestead, 0)
.with_fork(Hardfork::Tangerine, 0)
.with_fork(Hardfork::SpuriousDragon, 0)
.with_fork(Hardfork::Byzantium, 0)
.with_fork(Hardfork::Constantinople, 0)
.with_fork(Hardfork::Istanbul, 0)
.with_fork(Hardfork::Muirglacier, 0)
.with_fork(Hardfork::Berlin, 0)
.with_fork(Hardfork::London, 0)
.with_fork(Hardfork::ArrowGlacier, 0)
.with_fork(Hardfork::GrayGlacier, 0)
.clear_forks()
.with_fork(Hardfork::Frontier, ForkKind::Block(0))
.with_fork(Hardfork::Homestead, ForkKind::Block(0))
.with_fork(Hardfork::Tangerine, ForkKind::Block(0))
.with_fork(Hardfork::SpuriousDragon, ForkKind::Block(0))
.with_fork(Hardfork::Byzantium, ForkKind::Block(0))
.with_fork(Hardfork::Constantinople, ForkKind::Block(0))
.with_fork(Hardfork::Istanbul, ForkKind::Block(0))
.with_fork(Hardfork::Muirglacier, ForkKind::Block(0))
.with_fork(Hardfork::Berlin, ForkKind::Block(0))
.with_fork(Hardfork::London, ForkKind::Block(0))
.with_fork(Hardfork::ArrowGlacier, ForkKind::Block(0))
.with_fork(Hardfork::GrayGlacier, ForkKind::Block(0))
.build();
// test at block one - all forks should be active
let res_forkid = spec.fork_id(1);
let res_forkid = spec.fork_id(1_u64.into());
let expected_forkhash = ForkHash::from(spec.genesis_hash());
// if blocks get activated at genesis then they should not be accumulated into the forkhash
@@ -478,96 +491,96 @@ mod tests {
.chain(Chain::mainnet())
.genesis(empty_genesis.clone())
.genesis_hash(empty_sealed.hash())
.with_fork(Hardfork::Frontier, 0)
.with_fork(Hardfork::Homestead, 1)
.with_fork(Hardfork::Frontier, ForkKind::Block(0))
.with_fork(Hardfork::Homestead, ForkKind::Block(1))
.build();
let duplicate_spec = ChainSpec::builder()
.chain(Chain::mainnet())
.genesis(empty_genesis)
.genesis_hash(empty_sealed.hash())
.with_fork(Hardfork::Frontier, 0)
.with_fork(Hardfork::Homestead, 1)
.with_fork(Hardfork::Tangerine, 1)
.with_fork(Hardfork::Frontier, ForkKind::Block(0))
.with_fork(Hardfork::Homestead, ForkKind::Block(1))
.with_fork(Hardfork::Tangerine, ForkKind::Block(1))
.build();
assert_eq!(unique_spec.fork_id(2), duplicate_spec.fork_id(2));
assert_eq!(unique_spec.fork_id(2.into()), duplicate_spec.fork_id(2.into()));
}
// these tests check that the forkid computation is accurate
#[test]
fn test_mainnet_forkids() {
let frontier_forkid = MAINNET.fork_id(0);
let frontier_forkid = MAINNET.fork_id(0.into());
assert_eq!([0xfc, 0x64, 0xec, 0x04], frontier_forkid.hash.0);
assert_eq!(1150000, frontier_forkid.next);
let homestead_forkid = MAINNET.fork_id(1150000);
let homestead_forkid = MAINNET.fork_id(1150000.into());
assert_eq!([0x97, 0xc2, 0xc3, 0x4c], homestead_forkid.hash.0);
assert_eq!(1920000, homestead_forkid.next);
let dao_forkid = MAINNET.fork_id(1920000);
let dao_forkid = MAINNET.fork_id(1920000.into());
assert_eq!([0x91, 0xd1, 0xf9, 0x48], dao_forkid.hash.0);
assert_eq!(2463000, dao_forkid.next);
let tangerine_forkid = MAINNET.fork_id(2463000);
let tangerine_forkid = MAINNET.fork_id(2463000.into());
assert_eq!([0x7a, 0x64, 0xda, 0x13], tangerine_forkid.hash.0);
assert_eq!(2675000, tangerine_forkid.next);
let spurious_forkid = MAINNET.fork_id(2675000);
let spurious_forkid = MAINNET.fork_id(2675000.into());
assert_eq!([0x3e, 0xdd, 0x5b, 0x10], spurious_forkid.hash.0);
assert_eq!(4370000, spurious_forkid.next);
let byzantium_forkid = MAINNET.fork_id(4370000);
let byzantium_forkid = MAINNET.fork_id(4370000.into());
assert_eq!([0xa0, 0x0b, 0xc3, 0x24], byzantium_forkid.hash.0);
assert_eq!(7280000, byzantium_forkid.next);
let constantinople_forkid = MAINNET.fork_id(7280000);
let constantinople_forkid = MAINNET.fork_id(7280000.into());
assert_eq!([0x66, 0x8d, 0xb0, 0xaf], constantinople_forkid.hash.0);
assert_eq!(9069000, constantinople_forkid.next);
let istanbul_forkid = MAINNET.fork_id(9069000);
let istanbul_forkid = MAINNET.fork_id(9069000.into());
assert_eq!([0x87, 0x9d, 0x6e, 0x30], istanbul_forkid.hash.0);
assert_eq!(9200000, istanbul_forkid.next);
let muir_glacier_forkid = MAINNET.fork_id(9200000);
let muir_glacier_forkid = MAINNET.fork_id(9200000.into());
assert_eq!([0xe0, 0x29, 0xe9, 0x91], muir_glacier_forkid.hash.0);
assert_eq!(12244000, muir_glacier_forkid.next);
let berlin_forkid = MAINNET.fork_id(12244000);
let berlin_forkid = MAINNET.fork_id(12244000.into());
assert_eq!([0x0e, 0xb4, 0x40, 0xf6], berlin_forkid.hash.0);
assert_eq!(12965000, berlin_forkid.next);
let london_forkid = MAINNET.fork_id(12965000);
let london_forkid = MAINNET.fork_id(12965000.into());
assert_eq!([0xb7, 0x15, 0x07, 0x7d], london_forkid.hash.0);
assert_eq!(13773000, london_forkid.next);
let arrow_glacier_forkid = MAINNET.fork_id(13773000);
let arrow_glacier_forkid = MAINNET.fork_id(13773000.into());
assert_eq!([0x20, 0xc3, 0x27, 0xfc], arrow_glacier_forkid.hash.0);
assert_eq!(15050000, arrow_glacier_forkid.next);
let gray_glacier_forkid = MAINNET.fork_id(15050000);
let gray_glacier_forkid = MAINNET.fork_id(15050000.into());
assert_eq!([0xf0, 0xaf, 0xd0, 0xe3], gray_glacier_forkid.hash.0);
assert_eq!(0, gray_glacier_forkid.next); // TODO: update post-gray glacier
let latest_forkid = MAINNET.fork_id(15050000);
let latest_forkid = MAINNET.fork_id(15050000.into());
assert_eq!(0, latest_forkid.next);
}
#[test]
fn test_goerli_forkids() {
let frontier_forkid = GOERLI.fork_id(0);
let frontier_forkid = GOERLI.fork_id(ForkDiscriminant::block(0));
assert_eq!([0xa3, 0xf5, 0xab, 0x08], frontier_forkid.hash.0);
assert_eq!(1561651, frontier_forkid.next);
let istanbul_forkid = GOERLI.fork_id(1561651);
let istanbul_forkid = GOERLI.fork_id(ForkDiscriminant::block(1561651));
assert_eq!([0xc2, 0x5e, 0xfa, 0x5c], istanbul_forkid.hash.0);
assert_eq!(4460644, istanbul_forkid.next);
let berlin_forkid = GOERLI.fork_id(4460644);
let berlin_forkid = GOERLI.fork_id(ForkDiscriminant::block(4460644));
assert_eq!([0x75, 0x7a, 0x1c, 0x47], berlin_forkid.hash.0);
assert_eq!(5062605, berlin_forkid.next);
let london_forkid = GOERLI.fork_id(12965000);
let london_forkid = GOERLI.fork_id(ForkDiscriminant::block(12965000));
assert_eq!([0xb8, 0xc6, 0x29, 0x9d], london_forkid.hash.0);
assert_eq!(0, london_forkid.next);
}
@@ -575,7 +588,7 @@ mod tests {
#[test]
fn test_sepolia_forkids() {
// Test vector is from <https://github.com/ethereum/go-ethereum/blob/59a48e0289b1a7470a8285e665cab12b29117a70/core/forkid/forkid_test.go#L146-L151>
let mergenetsplit_forkid = SEPOLIA.fork_id(1735371);
let mergenetsplit_forkid = SEPOLIA.fork_id(ForkDiscriminant::block(1735371));
assert_eq!([0xb9, 0x6c, 0xbd, 0x13], mergenetsplit_forkid.hash.0);
assert_eq!(0, mergenetsplit_forkid.next);
}

View File

@@ -83,8 +83,8 @@ impl Add<BlockNumber> for ForkHash {
pub struct ForkId {
/// CRC32 checksum of the all fork blocks from genesis.
pub hash: ForkHash,
/// Next upcoming fork block number, 0 if not yet known.
pub next: BlockNumber,
/// Next upcoming fork block number or timestamp, 0 if not yet known.
pub next: u64,
}
/// Reason for rejecting provided `ForkId`.
@@ -127,7 +127,7 @@ impl ForkFilter {
pub fn new<F, B>(head: BlockNumber, genesis: H256, forks: F) -> Self
where
F: IntoIterator<Item = B>,
B: Into<BlockNumber>,
B: Into<u64>,
{
let genesis_fork_hash = ForkHash::from(genesis);
let mut forks = forks.into_iter().map(Into::into).collect::<BTreeSet<_>>();

View File

@@ -0,0 +1,92 @@
use serde::{Deserialize, Serialize};
use crate::{BlockNumber, ChainSpec, Header, U256};
/// Hardforks can be based on block numbers (pre-merge), TTD (Paris)
/// or timestamp (post-merge)
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum ForkKind {
/// A fork's block number
Block(BlockNumber),
/// The terminal total difficulty (used by Paris fork)
TTD(Option<BlockNumber>),
/// The unix timestamp of a fork
Time(u64),
}
impl ForkKind {
/// Returns `true` is the fork is active at genesis
pub fn is_active_at_genesis(&self) -> bool {
match self {
ForkKind::Block(block_number) => *block_number == 0_u64,
ForkKind::TTD(_) => false,
ForkKind::Time(_) => false,
}
}
}
/// This struct is used when it's needed to determine is a hardfork is active
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ForkDiscriminant {
/// The block number
pub block_number: BlockNumber,
/// The total difficulty
pub total_difficulty: U256,
/// The timestamp
pub timestamp: u64,
}
impl ForkDiscriminant {
/// Returns a new [ForkDiscriminant]
pub fn new(block_number: BlockNumber, total_difficulty: U256, timestamp: u64) -> Self {
Self { block_number, total_difficulty, timestamp }
}
/// Return a [ForkDiscriminant] with the given block
pub fn block(block_number: BlockNumber) -> Self {
Self { block_number, ..Default::default() }
}
/// Return a [ForkDiscriminant] with the given ttd
pub fn ttd(total_difficulty: U256, block_number: Option<BlockNumber>) -> Self {
Self {
block_number: block_number.unwrap_or_default(),
total_difficulty,
..Default::default()
}
}
/// Return a [ForkDiscriminant] with the given timestamp
pub fn timestamp(timestamp: u64) -> Self {
Self { timestamp, ..Default::default() }
}
/// Return a [ForkDiscriminant] from the given [ForkKind]
pub fn from_kind(kind: ForkKind, chain_spec: &ChainSpec) -> Self {
match kind {
ForkKind::Block(block_number) => ForkDiscriminant::block(block_number),
ForkKind::TTD(block_number) => {
ForkDiscriminant::ttd(chain_spec.paris_ttd.unwrap_or_default(), block_number)
}
ForkKind::Time(timestamp) => ForkDiscriminant::timestamp(timestamp),
}
}
}
impl From<BlockNumber> for ForkDiscriminant {
fn from(value: BlockNumber) -> Self {
Self { block_number: value, ..Default::default() }
}
}
impl From<&Header> for ForkDiscriminant {
fn from(value: &Header) -> Self {
Self {
block_number: value.number,
total_difficulty: value.difficulty,
timestamp: value.timestamp,
}
}
}

View File

@@ -2,12 +2,10 @@ use serde::{Deserialize, Serialize};
use std::{fmt::Display, str::FromStr};
use crate::{BlockNumber, ChainSpec, ForkFilter, ForkHash, ForkId};
use crate::{forkkind::ForkDiscriminant, ChainSpec, ForkFilter, ForkId};
#[allow(missing_docs)]
#[derive(
Debug, Default, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Hardfork {
Frontier,
Homestead,
@@ -23,10 +21,8 @@ pub enum Hardfork {
London,
ArrowGlacier,
GrayGlacier,
MergeNetsplit,
Paris,
Shanghai,
#[default]
Latest,
}
impl Hardfork {
@@ -37,24 +33,10 @@ impl Hardfork {
///
/// If the hard fork is not present in the [`ChainSpec`] then `None` is returned.
pub fn fork_id(&self, chain_spec: &ChainSpec) -> Option<ForkId> {
if let Some(fork_block) = chain_spec.fork_block(*self) {
let mut curr_forkhash = ForkHash::from(chain_spec.genesis_hash());
let mut curr_block_number = 0;
for (_, b) in chain_spec.forks_iter() {
if fork_block >= b {
if b != curr_block_number {
curr_forkhash += b;
curr_block_number = b;
}
} else {
return Some(ForkId { hash: curr_forkhash, next: b })
}
}
Some(ForkId { hash: curr_forkhash, next: 0 })
} else {
None
}
chain_spec
.fork_kind(*self)
.map(|k| ForkDiscriminant::from_kind(k, chain_spec))
.map(|d| chain_spec.fork_id(d))
}
/// Creates a [`ForkFilter`](crate::ForkFilter) for the given hardfork.
@@ -64,15 +46,10 @@ impl Hardfork {
///
/// This returns `None` if the hardfork is not present in the given [`ChainSpec`].
pub fn fork_filter(&self, chain_spec: &ChainSpec) -> Option<ForkFilter> {
if let Some(fork_block) = chain_spec.fork_block(*self) {
let future_forks: Vec<BlockNumber> =
chain_spec.forks_iter().filter(|(_, b)| b > &fork_block).map(|(_, b)| b).collect();
// pass in the chain spec's genesis hash to initialize the fork filter
Some(ForkFilter::new(fork_block, chain_spec.genesis_hash(), future_forks))
} else {
None
}
chain_spec
.fork_kind(*self)
.map(|k| ForkDiscriminant::from_kind(k, chain_spec))
.map(|d| chain_spec.fork_filter(d))
}
}
@@ -95,8 +72,7 @@ impl FromStr for Hardfork {
"berlin" | "11" => Hardfork::Berlin,
"london" | "12" => Hardfork::London,
"arrowglacier" | "13" => Hardfork::ArrowGlacier,
"grayglacier" => Hardfork::GrayGlacier,
"latest" | "14" => Hardfork::Latest,
"grayglacier" | "14" => Hardfork::GrayGlacier,
_ => return Err(format!("Unknown hardfork {s}")),
};
Ok(hardfork)

View File

@@ -17,6 +17,7 @@ mod chain;
pub mod constants;
mod error;
mod forkid;
mod forkkind;
mod genesis;
mod hardfork;
mod header;
@@ -37,13 +38,12 @@ pub use account::Account;
pub use bits::H512;
pub use block::{Block, BlockHashOrNumber, SealedBlock};
pub use bloom::Bloom;
pub use chain::{
Chain, ChainInfo, ChainSpec, ChainSpecBuilder, ParisStatus, GOERLI, MAINNET, SEPOLIA,
};
pub use chain::{Chain, ChainInfo, ChainSpec, ChainSpecBuilder, GOERLI, MAINNET, SEPOLIA};
pub use constants::{
EMPTY_OMMER_ROOT, GOERLI_GENESIS, KECCAK_EMPTY, MAINNET_GENESIS, SEPOLIA_GENESIS,
};
pub use forkid::{ForkFilter, ForkHash, ForkId, ForkTransition, ValidationError};
pub use forkkind::{ForkDiscriminant, ForkKind};
pub use genesis::{Genesis, GenesisAccount};
pub use hardfork::Hardfork;
pub use header::{Header, HeadersDirection, SealedHeader};