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

@ -10,8 +10,8 @@ use reth_db::{
Error as DbError,
};
use reth_primitives::{
keccak256, Account as RethAccount, Address, ChainSpec, JsonU256, SealedBlock, SealedHeader,
StorageEntry, H256, U256,
keccak256, Account as RethAccount, Address, ChainSpec, Hardfork, JsonU256, SealedBlock,
SealedHeader, StorageEntry, H256, U256,
};
use reth_rlp::Decodable;
use reth_stages::{stages::execution::ExecutionStage, ExecInput, Stage, StageId, Transaction};
@ -20,7 +20,7 @@ use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use tracing::{debug, trace};
use tracing::{debug, trace, warn};
/// The outcome of a test.
#[derive(Debug)]
@ -125,7 +125,7 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
let chain_spec: ChainSpec = suite.network.into();
// if paris aka merge is not activated we dont have block rewards;
let has_block_reward = chain_spec.paris_status().block_number().is_some();
let has_block_reward = chain_spec.fork_block(Hardfork::Paris).is_some();
// Create db and acquire transaction
let db = create_test_rw_db();
@ -198,8 +198,9 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
{
let mut transaction = Transaction::new(db.as_ref())?;
// ignore error
let _ = stage.execute(&mut transaction, input).await;
if let Err(err) = stage.execute(&mut transaction, input).await {
warn!("{:#}", err);
}
transaction.commit()?;
}

View File

@ -1,7 +1,9 @@
//! Consensus for ethereum network
use crate::verification;
use reth_interfaces::consensus::{Consensus, Error, ForkchoiceState};
use reth_primitives::{BlockNumber, ChainSpec, SealedBlock, SealedHeader, H256};
use reth_primitives::{
BlockNumber, ChainSpec, ForkDiscriminant, Hardfork, SealedBlock, SealedHeader, H256,
};
use tokio::sync::{watch, watch::error::SendError};
/// Ethereum beacon consensus
@ -46,7 +48,13 @@ impl Consensus for BeaconConsensus {
verification::validate_header_standalone(header, &self.chain_spec)?;
verification::validate_header_regarding_parent(parent, header, &self.chain_spec)?;
if Some(header.number) < self.chain_spec.paris_status().block_number() {
if !self.chain_spec.fork_active(
Hardfork::Paris,
ForkDiscriminant::ttd(
self.chain_spec.terminal_total_difficulty().unwrap_or_default(),
Some(header.number),
),
) {
// TODO Consensus checks for old blocks:
// * difficulty, mix_hash & nonce aka PoW stuff
// low priority as syncing is done in reverse order
@ -59,6 +67,12 @@ impl Consensus for BeaconConsensus {
}
fn has_block_reward(&self, block_num: BlockNumber) -> bool {
Some(block_num) < self.chain_spec.paris_status().block_number()
!self.chain_spec.fork_active(
Hardfork::Paris,
ForkDiscriminant::ttd(
self.chain_spec.terminal_total_difficulty().unwrap_or_default(),
Some(block_num),
),
)
}
}

View File

@ -188,7 +188,7 @@ impl<Client: HeaderProvider + BlockProvider + StateProvider> ConsensusEngine
};
if let Some(parent_td) = self.client.header_td(&block.parent_hash)? {
if Some(parent_td) <= self.chain_spec.paris_status().terminal_total_difficulty() {
if Some(parent_td) <= self.chain_spec.terminal_total_difficulty() {
return Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
validation_error: EngineApiError::PayloadPreMerge.to_string(),
}))
@ -271,7 +271,6 @@ impl<Client: HeaderProvider + BlockProvider + StateProvider> ConsensusEngine
let merge_terminal_td = self
.chain_spec
.paris_status()
.terminal_total_difficulty()
.ok_or(EngineApiError::UnknownMergeTerminalTotalDifficulty)?;
@ -516,8 +515,7 @@ mod tests {
let (result_tx, result_rx) = oneshot::channel();
let parent = transform_block(random_block(100, None, None, Some(0)), |mut b| {
b.header.difficulty =
chain_spec.paris_status().terminal_total_difficulty().unwrap();
b.header.difficulty = chain_spec.terminal_total_difficulty().unwrap();
b
});
let block = random_block(101, Some(parent.hash()), None, Some(0));
@ -555,7 +553,7 @@ mod tests {
let parent = transform_block(random_block(100, None, None, Some(0)), |mut b| {
b.header.timestamp = parent_timestamp;
b.header.difficulty =
chain_spec.paris_status().terminal_total_difficulty().unwrap() + U256::from(1);
chain_spec.terminal_total_difficulty().unwrap() + U256::from(1);
b
});
let block =
@ -633,10 +631,7 @@ mod tests {
tokio::spawn(engine);
let transition_config = TransitionConfiguration {
terminal_total_difficulty: chain_spec
.paris_status()
.terminal_total_difficulty()
.unwrap() +
terminal_total_difficulty: chain_spec.terminal_total_difficulty().unwrap() +
U256::from(1),
..Default::default()
};
@ -651,7 +646,7 @@ mod tests {
assert_matches!(
result_rx.await,
Ok(Err(EngineApiError::TerminalTD { execution, consensus }))
if execution == chain_spec.paris_status().terminal_total_difficulty().unwrap()
if execution == chain_spec.terminal_total_difficulty().unwrap()
&& consensus == U256::from(transition_config.terminal_total_difficulty)
);
}
@ -675,10 +670,7 @@ mod tests {
let execution_terminal_block = random_block(terminal_block_number, None, None, None);
let transition_config = TransitionConfiguration {
terminal_total_difficulty: chain_spec
.paris_status()
.terminal_total_difficulty()
.unwrap(),
terminal_total_difficulty: chain_spec.terminal_total_difficulty().unwrap(),
terminal_block_hash: consensus_terminal_block.hash(),
terminal_block_number: terminal_block_number.into(),
};
@ -737,10 +729,7 @@ mod tests {
let terminal_block = random_block(terminal_block_number, None, None, None);
let transition_config = TransitionConfiguration {
terminal_total_difficulty: chain_spec
.paris_status()
.terminal_total_difficulty()
.unwrap(),
terminal_total_difficulty: chain_spec.terminal_total_difficulty().unwrap(),
terminal_block_hash: terminal_block.hash(),
terminal_block_number: terminal_block_number.into(),
};

View File

@ -1,8 +1,9 @@
//! ALl functions for verification of block
use reth_interfaces::{consensus::Error, Result as RethResult};
use reth_primitives::{
BlockNumber, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader, Transaction,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, EMPTY_OMMER_ROOT, U256,
BlockNumber, ChainSpec, ForkDiscriminant, Hardfork, Header, SealedBlock, SealedHeader,
Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, EMPTY_OMMER_ROOT,
U256,
};
use reth_provider::{AccountProvider, HeaderProvider};
use std::{
@ -39,14 +40,21 @@ pub fn validate_header_standalone(
}
// Check if base fee is set.
if chain_spec.fork_active(Hardfork::London, header.number) && header.base_fee_per_gas.is_none()
if chain_spec.fork_active(Hardfork::London, header.number.into()) &&
header.base_fee_per_gas.is_none()
{
return Err(Error::BaseFeeMissing)
}
// EIP-3675: Upgrade consensus to Proof-of-Stake:
// https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0
if Some(header.number) >= chain_spec.paris_status().block_number() {
if chain_spec.fork_active(
Hardfork::Paris,
ForkDiscriminant::ttd(
chain_spec.terminal_total_difficulty().unwrap_or_default(),
Some(header.number),
),
) {
if header.difficulty != U256::ZERO {
return Err(Error::TheMergeDifficultyIsNotZero)
}
@ -78,7 +86,7 @@ pub fn validate_transaction_regarding_header(
let chain_id = match transaction {
Transaction::Legacy(TxLegacy { chain_id, .. }) => {
// EIP-155: Simple replay attack protection: https://eips.ethereum.org/EIPS/eip-155
if chain_spec.fork_active(Hardfork::SpuriousDragon, at_block_number) &&
if chain_spec.fork_active(Hardfork::SpuriousDragon, at_block_number.into()) &&
chain_id.is_some()
{
return Err(Error::TransactionOldLegacyChainId)
@ -87,7 +95,7 @@ pub fn validate_transaction_regarding_header(
}
Transaction::Eip2930(TxEip2930 { chain_id, .. }) => {
// EIP-2930: Optional access lists: https://eips.ethereum.org/EIPS/eip-2930 (New transaction type)
if !chain_spec.fork_active(Hardfork::Berlin, at_block_number) {
if !chain_spec.fork_active(Hardfork::Berlin, at_block_number.into()) {
return Err(Error::TransactionEip2930Disabled)
}
Some(*chain_id)
@ -99,7 +107,7 @@ pub fn validate_transaction_regarding_header(
..
}) => {
// EIP-1559: Fee market change for ETH 1.0 chain https://eips.ethereum.org/EIPS/eip-1559
if !chain_spec.fork_active(Hardfork::Berlin, at_block_number) {
if !chain_spec.fork_active(Hardfork::Berlin, at_block_number.into()) {
return Err(Error::TransactionEip1559Disabled)
}
@ -259,7 +267,13 @@ pub fn validate_header_regarding_parent(
}
// difficulty check is done by consensus.
if chain_spec.paris_status().block_number() > Some(child.number) {
if !chain_spec.fork_active(
Hardfork::Paris,
ForkDiscriminant::ttd(
chain_spec.terminal_total_difficulty().unwrap_or_default(),
Some(child.number),
),
) {
// TODO how this needs to be checked? As ice age did increment it by some formula
}
@ -287,7 +301,7 @@ pub fn validate_header_regarding_parent(
}
// EIP-1559 check base fee
if chain_spec.fork_active(Hardfork::London, child.number) {
if chain_spec.fork_active(Hardfork::London, child.number.into()) {
let base_fee = child.base_fee_per_gas.ok_or(Error::BaseFeeMissing)?;
let expected_base_fee = if chain_spec.fork_block(Hardfork::London) == Some(child.number) {

View File

@ -1,6 +1,6 @@
//! Reth block execution/validation configuration and constants
use reth_primitives::{BlockNumber, ChainSpec, Hardfork};
use reth_primitives::{ChainSpec, ForkDiscriminant, Hardfork};
/// Two ethereum worth of wei
pub const WEI_2ETH: u128 = 2000000000000000000u128;
@ -10,19 +10,19 @@ pub const WEI_3ETH: u128 = 3000000000000000000u128;
pub const WEI_5ETH: u128 = 5000000000000000000u128;
/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, for_block: BlockNumber) -> revm::SpecId {
match for_block {
b if chain_spec.fork_active(Hardfork::Shanghai, b) => revm::MERGE_EOF,
b if Some(b) >= chain_spec.paris_status().block_number() => revm::MERGE,
b if chain_spec.fork_active(Hardfork::London, b) => revm::LONDON,
b if chain_spec.fork_active(Hardfork::Berlin, b) => revm::BERLIN,
b if chain_spec.fork_active(Hardfork::Istanbul, b) => revm::ISTANBUL,
b if chain_spec.fork_active(Hardfork::Petersburg, b) => revm::PETERSBURG,
b if chain_spec.fork_active(Hardfork::Byzantium, b) => revm::BYZANTIUM,
b if chain_spec.fork_active(Hardfork::SpuriousDragon, b) => revm::SPURIOUS_DRAGON,
b if chain_spec.fork_active(Hardfork::Tangerine, b) => revm::TANGERINE,
b if chain_spec.fork_active(Hardfork::Homestead, b) => revm::HOMESTEAD,
b if chain_spec.fork_active(Hardfork::Frontier, b) => revm::FRONTIER,
pub fn revm_spec(chain_spec: &ChainSpec, discriminant: ForkDiscriminant) -> revm::SpecId {
match discriminant {
d if chain_spec.fork_active(Hardfork::Shanghai, d) => revm::MERGE_EOF,
d if chain_spec.fork_active(Hardfork::Paris, d) => revm::MERGE,
d if chain_spec.fork_active(Hardfork::London, d) => revm::LONDON,
d if chain_spec.fork_active(Hardfork::Berlin, d) => revm::BERLIN,
d if chain_spec.fork_active(Hardfork::Istanbul, d) => revm::ISTANBUL,
d if chain_spec.fork_active(Hardfork::Petersburg, d) => revm::PETERSBURG,
d if chain_spec.fork_active(Hardfork::Byzantium, d) => revm::BYZANTIUM,
d if chain_spec.fork_active(Hardfork::SpuriousDragon, d) => revm::SPURIOUS_DRAGON,
d if chain_spec.fork_active(Hardfork::Tangerine, d) => revm::TANGERINE,
d if chain_spec.fork_active(Hardfork::Homestead, d) => revm::HOMESTEAD,
d if chain_spec.fork_active(Hardfork::Frontier, d) => revm::FRONTIER,
_ => panic!("wrong configuration"),
}
}
@ -30,62 +30,106 @@ pub fn revm_spec(chain_spec: &ChainSpec, for_block: BlockNumber) -> revm::SpecId
#[cfg(test)]
mod tests {
use crate::config::revm_spec;
use reth_primitives::{ChainSpecBuilder, MAINNET};
use reth_primitives::{ChainSpecBuilder, ForkDiscriminant, MAINNET, U256};
#[test]
fn test_to_revm_spec() {
let discriminant = ForkDiscriminant::new(1, U256::from(1), 1);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().shangai_activated().build(), discriminant),
revm::MERGE_EOF
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), discriminant),
revm::MERGE
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), discriminant),
revm::LONDON
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), discriminant),
revm::BERLIN
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), discriminant),
revm::ISTANBUL
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().petersburg_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().petersburg_activated().build(), discriminant),
revm::PETERSBURG
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), discriminant),
revm::BYZANTIUM
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().spurious_dragon_activated().build(), 1),
revm_spec(
&ChainSpecBuilder::mainnet().spurious_dragon_activated().build(),
discriminant
),
revm::SPURIOUS_DRAGON
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(), 1),
revm_spec(
&ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(),
discriminant
),
revm::TANGERINE
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), discriminant),
revm::HOMESTEAD
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), 1),
revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), discriminant),
revm::FRONTIER
);
}
#[test]
fn test_eth_spec() {
assert_eq!(revm_spec(&MAINNET, 15537394 + 10), revm::MERGE);
assert_eq!(revm_spec(&MAINNET, 15537394 - 10), revm::LONDON);
assert_eq!(revm_spec(&MAINNET, 12244000 + 10), revm::BERLIN);
assert_eq!(revm_spec(&MAINNET, 12244000 - 10), revm::ISTANBUL);
assert_eq!(revm_spec(&MAINNET, 7280000 + 10), revm::PETERSBURG);
assert_eq!(revm_spec(&MAINNET, 7280000 - 10), revm::BYZANTIUM);
assert_eq!(revm_spec(&MAINNET, 2675000 + 10), revm::SPURIOUS_DRAGON);
assert_eq!(revm_spec(&MAINNET, 2675000 - 10), revm::TANGERINE);
assert_eq!(revm_spec(&MAINNET, 1150000 + 10), revm::HOMESTEAD);
assert_eq!(revm_spec(&MAINNET, 1150000 - 10), revm::FRONTIER);
let post_merge_td = MAINNET.terminal_total_difficulty().unwrap();
let pre_merge_td = post_merge_td.saturating_sub(U256::from(10));
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(15537394 + 10, post_merge_td, 1674477448)),
revm::MERGE
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(15537394 - 10, pre_merge_td, 1674477448)),
revm::LONDON
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(12244000 + 10, pre_merge_td, 1674477448)),
revm::BERLIN
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(12244000 - 10, pre_merge_td, 1674477448)),
revm::ISTANBUL
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(7280000 + 10, pre_merge_td, 1674477448)),
revm::PETERSBURG
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(7280000 - 10, pre_merge_td, 1674477448)),
revm::BYZANTIUM
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(2675000 + 10, pre_merge_td, 1674477448)),
revm::SPURIOUS_DRAGON
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(2675000 - 10, pre_merge_td, 1674477448)),
revm::TANGERINE
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(1150000 + 10, pre_merge_td, 1674477448)),
revm::HOMESTEAD
);
assert_eq!(
revm_spec(&MAINNET, ForkDiscriminant::new(1150000 - 10, pre_merge_td, 1674477448)),
revm::FRONTIER
);
}
}

View File

@ -302,7 +302,7 @@ pub fn execute<DB: StateProvider>(
let mut evm = EVM::new();
evm.database(db);
let spec_id = revm_spec(chain_spec, header.number);
let spec_id = revm_spec(chain_spec, header.into());
evm.env.cfg.chain_id = U256::from(chain_spec.chain().id());
evm.env.cfg.spec_id = spec_id;
evm.env.cfg.perf_all_precompiles_have_balance = false;
@ -451,10 +451,10 @@ pub fn block_reward_changeset<DB: StateProvider>(
// amount. We raise the blocks beneficiary account by Rblock; for each ommer, we raise the
// blocks beneficiary by an additional 1/32 of the block reward and the beneficiary of the
// ommer gets rewarded depending on the blocknumber. Formally we define the function Ω:
match header.number {
n if Some(n) >= chain_spec.paris_status().block_number() => None,
n if Some(n) >= chain_spec.fork_block(Hardfork::Petersburg) => Some(WEI_2ETH),
n if Some(n) >= chain_spec.fork_block(Hardfork::Byzantium) => Some(WEI_3ETH),
match header.into() {
d if chain_spec.fork_active(Hardfork::Paris, d) => None,
d if chain_spec.fork_active(Hardfork::Petersburg, d) => Some(WEI_2ETH),
d if chain_spec.fork_active(Hardfork::Byzantium, d) => Some(WEI_3ETH),
_ => Some(WEI_5ETH),
}
.map(|reward| -> Result<_, _> {
@ -538,8 +538,8 @@ mod tests {
transaction::DbTx,
};
use reth_primitives::{
hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, SealedBlock,
StorageKey, H160, H256, MAINNET, U256,
hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, ForkKind,
SealedBlock, StorageKey, H160, H256, MAINNET, U256,
};
use reth_provider::{AccountProvider, BlockHashProvider, StateProvider};
use reth_rlp::Decodable;
@ -776,7 +776,7 @@ mod tests {
let chain_spec = ChainSpecBuilder::from(&*MAINNET)
.homestead_activated()
.with_fork(Hardfork::Dao, 1)
.with_fork(Hardfork::Dao, ForkKind::Block(1))
.build();
let mut db = SubState::new(State::new(db));

View File

@ -21,7 +21,7 @@ use reth_primitives::{Chain, ForkId, PeerId, H256, U256};
/// .total_difficulty(U256::from(100))
/// .blockhash(H256::from(MAINNET_GENESIS))
/// .genesis(H256::from(MAINNET_GENESIS))
/// .forkid(Hardfork::Latest.fork_id(&MAINNET).unwrap())
/// .forkid(Hardfork::London.fork_id(&MAINNET).unwrap())
/// .build();
///
/// assert_eq!(
@ -32,7 +32,7 @@ use reth_primitives::{Chain, ForkId, PeerId, H256, U256};
/// total_difficulty: U256::from(100),
/// blockhash: H256::from(MAINNET_GENESIS),
/// genesis: H256::from(MAINNET_GENESIS),
/// forkid: Hardfork::Latest.fork_id(&MAINNET).unwrap(),
/// forkid: Hardfork::London.fork_id(&MAINNET).unwrap(),
/// }
/// );
/// ```

View File

@ -1,6 +1,6 @@
//! Types for broadcasting new data.
use reth_codecs::derive_arbitrary;
use reth_primitives::{Header, TransactionSigned, H256, U128};
use reth_primitives::{BlockNumber, Header, TransactionSigned, H256, U128};
use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@ -47,7 +47,7 @@ pub struct BlockHashNumber {
/// The block hash
pub hash: H256,
/// The block number
pub number: u64,
pub number: BlockNumber,
}
impl From<Vec<BlockHashNumber>> for NewBlockHashes {

View File

@ -50,10 +50,8 @@ impl From<Genesis> for Status {
let mut chainspec = ChainSpec::from(genesis);
let mut header = Header::from(chainspec.genesis().clone());
let hardforks = chainspec.hardforks();
// set initial base fee depending on eip-1559
if Some(&0u64) == hardforks.get(&Hardfork::London) {
if Some(0u64) == chainspec.fork_block(Hardfork::London) {
header.base_fee_per_gas = Some(EIP1559_INITIAL_BASE_FEE);
}
@ -64,7 +62,7 @@ impl From<Genesis> for Status {
chainspec.genesis_hash = sealed_header.hash();
// we need to calculate the fork id AFTER re-setting the genesis hash
let forkid = chainspec.fork_id(0);
let forkid = chainspec.fork_id(0.into());
Status {
version: EthVersion::Eth67 as u8,
@ -97,7 +95,7 @@ impl Status {
.chain(spec.chain)
.genesis(spec.genesis_hash())
.blockhash(head.hash)
.forkid(spec.fork_id(head.number))
.forkid(spec.fork_id(head.number.into()))
}
}

View File

@ -297,7 +297,7 @@ impl NetworkConfigBuilder {
let status = Status::spec_builder(&chain_spec, &head).build();
// set a fork filter based on the chain spec and head
let fork_filter = chain_spec.fork_filter(head.number);
let fork_filter = chain_spec.fork_filter(head.number.into());
// If default DNS config is used then we add the known dns network to bootstrap from
if let Some(dns_networks) =

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,44 +158,58 @@ 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 {
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;
curr_block_number = 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};

View File

@ -53,7 +53,7 @@ pub fn init_genesis<DB: Database>(db: Arc<DB>, chain: ChainSpec) -> Result<H256,
let mut header: Header = genesis.clone().into();
// set base fee if EIP-1559 is enabled
if chain.fork_active(Hardfork::London, 0) {
if chain.fork_active(Hardfork::London, 0.into()) {
header.base_fee_per_gas = Some(EIP1559_INITIAL_BASE_FEE);
}