Skip to content

Commit

Permalink
feat: add NetworkPrimitives to NetworkBuilder (paradigmxyz#13169)
Browse files Browse the repository at this point in the history
Co-authored-by: Arsenii Kulikov <[email protected]>
  • Loading branch information
Rjected and klkvr authored Dec 10, 2024
1 parent 73f1583 commit 37f3933
Show file tree
Hide file tree
Showing 26 changed files with 201 additions and 143 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions crates/e2e-test-utils/src/network.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use futures_util::StreamExt;
use reth_network_api::{
events::PeerEvent, test_utils::PeersHandleProvider, NetworkEvent, NetworkEventListenerProvider,
PeersInfo,
PeerRequest, PeersInfo,
};
use reth_network_peers::{NodeRecord, PeerId};
use reth_tokio_util::EventStream;
use reth_tracing::tracing::info;

/// Helper for network operations
#[derive(Debug)]
pub struct NetworkTestContext<Network> {
network_events: EventStream<NetworkEvent>,
pub struct NetworkTestContext<Network: NetworkEventListenerProvider> {
network_events: EventStream<NetworkEvent<PeerRequest<Network::Primitives>>>,
network: Network,
}

Expand Down
120 changes: 64 additions & 56 deletions crates/ethereum/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH};
use alloy_consensus::{BlockHeader, EMPTY_OMMER_ROOT_HASH};
use alloy_eips::merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS;
use alloy_primitives::U256;
use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
use reth_consensus::{
Expand All @@ -20,10 +21,8 @@ use reth_consensus_common::validation::{
validate_against_parent_timestamp, validate_block_pre_execution, validate_body_against_header,
validate_header_base_fee, validate_header_extradata, validate_header_gas,
};
use reth_primitives::{
Block, BlockBody, BlockWithSenders, NodePrimitives, Receipt, SealedBlock, SealedHeader,
};
use reth_primitives_traits::constants::MINIMUM_GAS_LIMIT;
use reth_primitives::{BlockWithSenders, NodePrimitives, Receipt, SealedBlock, SealedHeader};
use reth_primitives_traits::{constants::MINIMUM_GAS_LIMIT, BlockBody};
use std::{fmt::Debug, sync::Arc, time::SystemTime};

/// The bound divisor of the gas limit, used in update calculations.
Expand Down Expand Up @@ -51,43 +50,46 @@ impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec>
///
/// The maximum allowable difference between self and parent gas limits is determined by the
/// parent's gas limit divided by the [`GAS_LIMIT_BOUND_DIVISOR`].
fn validate_against_parent_gas_limit(
fn validate_against_parent_gas_limit<H: BlockHeader>(
&self,
header: &SealedHeader,
parent: &SealedHeader,
header: &SealedHeader<H>,
parent: &SealedHeader<H>,
) -> Result<(), ConsensusError> {
// Determine the parent gas limit, considering elasticity multiplier on the London fork.
let parent_gas_limit =
if self.chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number) {
parent.gas_limit *
if self.chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number())
{
parent.gas_limit() *
self.chain_spec
.base_fee_params_at_timestamp(header.timestamp)
.base_fee_params_at_timestamp(header.timestamp())
.elasticity_multiplier as u64
} else {
parent.gas_limit
parent.gas_limit()
};

// Check for an increase in gas limit beyond the allowed threshold.

if header.gas_limit > parent_gas_limit {
if header.gas_limit - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR {
if header.gas_limit() > parent_gas_limit {
if header.gas_limit() - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR {
return Err(ConsensusError::GasLimitInvalidIncrease {
parent_gas_limit,
child_gas_limit: header.gas_limit,
child_gas_limit: header.gas_limit(),
})
}
}
// Check for a decrease in gas limit beyond the allowed threshold.
else if parent_gas_limit - header.gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR
else if parent_gas_limit - header.gas_limit() >=
parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR
{
return Err(ConsensusError::GasLimitInvalidDecrease {
parent_gas_limit,
child_gas_limit: header.gas_limit,
child_gas_limit: header.gas_limit(),
})
}
// Check if the self gas limit is below the minimum required limit.
else if header.gas_limit < MINIMUM_GAS_LIMIT {
return Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: header.gas_limit })
else if header.gas_limit() < MINIMUM_GAS_LIMIT {
return Err(ConsensusError::GasLimitInvalidMinimum {
child_gas_limit: header.gas_limit(),
})
}

Ok(())
Expand All @@ -97,72 +99,75 @@ impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec>
impl<ChainSpec, N> FullConsensus<N> for EthBeaconConsensus<ChainSpec>
where
ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug,
N: NodePrimitives<
BlockHeader = Header,
BlockBody = BlockBody,
Block = Block,
Receipt = Receipt,
>,
N: NodePrimitives<Receipt = Receipt>,
{
fn validate_block_post_execution(
&self,
block: &BlockWithSenders,
block: &BlockWithSenders<N::Block>,
input: PostExecutionInput<'_>,
) -> Result<(), ConsensusError> {
validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
}
}

impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensus
impl<H, B, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensus<H, B>
for EthBeaconConsensus<ChainSpec>
where
H: BlockHeader,
B: BlockBody,
{
fn validate_body_against_header(
&self,
body: &BlockBody,
header: &SealedHeader,
body: &B,
header: &SealedHeader<H>,
) -> Result<(), ConsensusError> {
validate_body_against_header(body, header.header())
}

fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError> {
fn validate_block_pre_execution(
&self,
block: &SealedBlock<H, B>,
) -> Result<(), ConsensusError> {
validate_block_pre_execution(block, &self.chain_spec)
}
}

impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderValidator
impl<H, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderValidator<H>
for EthBeaconConsensus<ChainSpec>
where
H: BlockHeader,
{
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> {
validate_header_gas(header.header())?;
validate_header_base_fee(header.header(), &self.chain_spec)?;

// EIP-4895: Beacon chain push withdrawals as operations
if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) &&
header.withdrawals_root.is_none()
if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
header.withdrawals_root().is_none()
{
return Err(ConsensusError::WithdrawalsRootMissing)
} else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) &&
header.withdrawals_root.is_some()
} else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
header.withdrawals_root().is_some()
{
return Err(ConsensusError::WithdrawalsRootUnexpected)
}

// Ensures that EIP-4844 fields are valid once cancun is active.
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) {
validate_4844_header_standalone(header.header())?;
} else if header.blob_gas_used.is_some() {
} else if header.blob_gas_used().is_some() {
return Err(ConsensusError::BlobGasUsedUnexpected)
} else if header.excess_blob_gas.is_some() {
} else if header.excess_blob_gas().is_some() {
return Err(ConsensusError::ExcessBlobGasUnexpected)
} else if header.parent_beacon_block_root.is_some() {
} else if header.parent_beacon_block_root().is_some() {
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
}

if self.chain_spec.is_prague_active_at_timestamp(header.timestamp) {
if header.requests_hash.is_none() {
if self.chain_spec.is_prague_active_at_timestamp(header.timestamp()) {
if header.requests_hash().is_none() {
return Err(ConsensusError::RequestsHashMissing)
}
} else if header.requests_hash.is_some() {
} else if header.requests_hash().is_some() {
return Err(ConsensusError::RequestsHashUnexpected)
}

Expand All @@ -171,8 +176,8 @@ impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderVa

fn validate_header_against_parent(
&self,
header: &SealedHeader,
parent: &SealedHeader,
header: &SealedHeader<H>,
parent: &SealedHeader<H>,
) -> Result<(), ConsensusError> {
validate_against_parent_hash_number(header.header(), parent)?;

Expand All @@ -189,7 +194,7 @@ impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderVa
)?;

// ensure that the blob gas fields for this block
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) {
validate_against_parent_4844(header.header(), parent.header())?;
}

Expand All @@ -198,24 +203,26 @@ impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderVa

fn validate_header_with_total_difficulty(
&self,
header: &Header,
header: &H,
total_difficulty: U256,
) -> Result<(), ConsensusError> {
let is_post_merge = self
.chain_spec
.fork(EthereumHardfork::Paris)
.active_at_ttd(total_difficulty, header.difficulty);
.active_at_ttd(total_difficulty, header.difficulty());

if is_post_merge {
if !header.is_zero_difficulty() {
// TODO: add `is_zero_difficulty` to `alloy_consensus::BlockHeader` trait
if !header.difficulty().is_zero() {
return Err(ConsensusError::TheMergeDifficultyIsNotZero)
}

if !header.nonce.is_zero() {
// TODO: helper fn in `alloy_consensus::BlockHeader` trait
if !header.nonce().is_some_and(|nonce| nonce.is_zero()) {
return Err(ConsensusError::TheMergeNonceIsNotZero)
}

if header.ommers_hash != EMPTY_OMMER_ROOT_HASH {
if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH {
return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty)
}

Expand All @@ -241,9 +248,10 @@ impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderVa
let present_timestamp =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();

if header.exceeds_allowed_future_timestamp(present_timestamp) {
// TODO: move this to `alloy_consensus::BlockHeader`
if header.timestamp() > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS {
return Err(ConsensusError::TimestampIsInFuture {
timestamp: header.timestamp,
timestamp: header.timestamp(),
present_timestamp,
})
}
Expand All @@ -263,7 +271,7 @@ mod tests {
use reth_primitives::proofs;

fn header_with_gas_limit(gas_limit: u64) -> SealedHeader {
let header = Header { gas_limit, ..Default::default() };
let header = reth_primitives::Header { gas_limit, ..Default::default() };
SealedHeader::new(header, B256::ZERO)
}

Expand Down Expand Up @@ -343,7 +351,7 @@ mod tests {
// that the header is valid
let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());

let header = Header {
let header = reth_primitives::Header {
base_fee_per_gas: Some(1337),
withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
..Default::default()
Expand Down
25 changes: 15 additions & 10 deletions crates/ethereum/consensus/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
use alloy_consensus::{proofs::calculate_receipt_root, TxReceipt};
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
use alloy_eips::eip7685::Requests;
use alloy_primitives::{Bloom, B256};
use reth_chainspec::EthereumHardforks;
use reth_consensus::ConsensusError;
use reth_primitives::{gas_spent_by_transactions, BlockWithSenders, GotExpected, Receipt};
use reth_primitives_traits::Block;

/// Validate a block with regard to execution results:
///
/// - Compares the receipts root in the block header to the block body
/// - Compares the gas used in the block header to the actual gas usage after execution
pub fn validate_block_post_execution<ChainSpec: EthereumHardforks>(
block: &BlockWithSenders,
pub fn validate_block_post_execution<B, ChainSpec>(
block: &BlockWithSenders<B>,
chain_spec: &ChainSpec,
receipts: &[Receipt],
requests: &Requests,
) -> Result<(), ConsensusError> {
) -> Result<(), ConsensusError>
where
B: Block,
ChainSpec: EthereumHardforks,
{
// Check if gas used matches the value set in header.
let cumulative_gas_used =
receipts.last().map(|receipt| receipt.cumulative_gas_used).unwrap_or(0);
if block.gas_used != cumulative_gas_used {
if block.header().gas_used() != cumulative_gas_used {
return Err(ConsensusError::BlockGasUsed {
gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used },
gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() },
gas_spent_by_tx: gas_spent_by_transactions(receipts),
})
}
Expand All @@ -29,18 +34,18 @@ pub fn validate_block_post_execution<ChainSpec: EthereumHardforks>(
// operation as hashing that is required for state root got calculated in every
// transaction This was replaced with is_success flag.
// See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
if chain_spec.is_byzantium_active_at_block(block.header.number) {
if chain_spec.is_byzantium_active_at_block(block.header().number()) {
if let Err(error) =
verify_receipts(block.header.receipts_root, block.header.logs_bloom, receipts)
verify_receipts(block.header().receipts_root(), block.header().logs_bloom(), receipts)
{
tracing::debug!(%error, ?receipts, "receipts verification failed");
return Err(error)
}
}

// Validate that the header requests hash matches the calculated requests hash
if chain_spec.is_prague_active_at_timestamp(block.timestamp) {
let Some(header_requests_hash) = block.header.requests_hash else {
if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) {
let Some(header_requests_hash) = block.header().requests_hash() else {
return Err(ConsensusError::RequestsHashMissing)
};
let requests_hash = requests.requests_hash();
Expand Down
4 changes: 3 additions & 1 deletion crates/ethereum/node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use reth_ethereum_engine_primitives::{
};
use reth_evm::execute::BasicBlockExecutorProvider;
use reth_evm_ethereum::execute::EthExecutionStrategyFactory;
use reth_network::{NetworkHandle, PeersInfo};
use reth_network::{EthNetworkPrimitives, NetworkHandle, PeersInfo};
use reth_node_api::{
AddOnsContext, ConfigureEvm, FullNodeComponents, HeaderTy, NodeTypesWithDB, TxTy,
};
Expand Down Expand Up @@ -318,6 +318,8 @@ where
> + Unpin
+ 'static,
{
type Primitives = EthNetworkPrimitives;

async fn build_network(
self,
ctx: &BuilderContext<Node>,
Expand Down
5 changes: 4 additions & 1 deletion crates/net/downloaders/src/bodies/bodies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,10 @@ impl<H, B> OrderedBodiesResponse<H, B> {
}
}

impl<H: BlockHeader, B> OrderedBodiesResponse<H, B> {
impl<H, B> OrderedBodiesResponse<H, B>
where
H: BlockHeader,
{
/// Returns the block number of the first element
///
/// # Panics
Expand Down
Loading

0 comments on commit 37f3933

Please sign in to comment.