Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/consensus/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ pub enum ConsensusError {
#[error("unexpected requests hash")]
RequestsHashUnexpected,

/// Error when deposit event data layout is invalid
#[error("failed to decode deposit requests from receipts")]
InvalidDepositEventLayout,

/// Error when withdrawals are missing.
#[error("missing withdrawals")]
BodyWithdrawalsMissing,
Expand Down
7 changes: 4 additions & 3 deletions crates/ethereum/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use reth_primitives_traits::{
};

mod validation;
pub use validation::DepositContractProvider;
pub use validation::validate_block_post_execution;

/// Ethereum beacon consensus
Expand All @@ -52,17 +53,17 @@ impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec>
}
}

impl<ChainSpec, N> FullConsensus<N> for EthBeaconConsensus<ChainSpec>
impl<CS, N> FullConsensus<N> for EthBeaconConsensus<CS>
where
ChainSpec: Send + Sync + EthChainSpec<Header = N::BlockHeader> + EthereumHardforks + Debug,
CS: Send + Sync + EthChainSpec<Header = N::BlockHeader> + EthereumHardforks + DepositContractProvider + Debug,
N: NodePrimitives,
{
fn validate_block_post_execution(
&self,
block: &RecoveredBlock<N::Block>,
result: &BlockExecutionResult<N::Receipt>,
) -> Result<(), ConsensusError> {
validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests)
validate_block_post_execution(block, self.chain_spec.as_ref(), &result.receipts, &result.requests)
}
}

Expand Down
136 changes: 131 additions & 5 deletions crates/ethereum/consensus/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,39 @@ use alloc::vec::Vec;
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
use alloy_eips::{eip7685::Requests, Encodable2718};
use alloy_primitives::{Bloom, Bytes, B256};
use reth_chainspec::EthereumHardforks;
use reth_chainspec::{DepositContract, EthereumHardforks};
use reth_consensus::ConsensusError;
use reth_primitives_traits::{
receipt::gas_spent_by_transactions, Block, GotExpected, Receipt, RecoveredBlock,
};

/// Temp for proof of concept
pub trait DepositContractProvider {
/// Womp
fn get_deposit_contract(&self) -> Option<DepositContract>;
}

/// Temp for proof of concept
impl DepositContractProvider for reth_chainspec::ChainSpec {
fn get_deposit_contract(&self) -> Option<DepositContract> {
self.deposit_contract
}
}

/// 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<B, R, ChainSpec>(
pub fn validate_block_post_execution<B, R, CS>(
block: &RecoveredBlock<B>,
chain_spec: &ChainSpec,
chain_spec: &CS,
receipts: &[R],
requests: &Requests,
) -> Result<(), ConsensusError>
where
B: Block,
R: Receipt,
ChainSpec: EthereumHardforks,
CS: EthereumHardforks + DepositContractProvider,
{
// Check if gas used matches the value set in header.
let cumulative_gas_used =
Expand Down Expand Up @@ -50,8 +63,24 @@ where
}
}

// Validate that the header requests hash matches the calculated requests hash
// Validate deposit event data layout and requests hash
if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) {
// Validate deposit event data layout
if let Some(deposit_contract) = chain_spec.get_deposit_contract() {
for receipt in receipts {
for log in receipt.logs() {
if log.address == deposit_contract.address
&& log.topics().first() == Some(&deposit_contract.topic)
{
if !verify_deposit_event_data(&log.data.data) {
return Err(ConsensusError::InvalidDepositEventLayout);
}
}
}
}
}

// Validate requests hash
let Some(header_requests_hash) = block.header().requests_hash() else {
return Err(ConsensusError::RequestsHashMissing)
};
Expand Down Expand Up @@ -113,6 +142,103 @@ fn compare_receipts_root_and_logs_bloom(
Ok(())
}

/// Validates deposit event data according to EIP-6110 specification
fn verify_deposit_event_data(deposit_event_data: &[u8]) -> bool {
// Check if the event data length is exactly 576 bytes
if deposit_event_data.len() != 576 {
return false;
}

// Extract offsets from the first 160 bytes
let pubkey_offset = u32::from_be_bytes([
deposit_event_data[28], deposit_event_data[29],
deposit_event_data[30], deposit_event_data[31]
]) as usize;

let withdrawal_credentials_offset = u32::from_be_bytes([
deposit_event_data[60], deposit_event_data[61],
deposit_event_data[62], deposit_event_data[63]
]) as usize;

let amount_offset = u32::from_be_bytes([
deposit_event_data[92], deposit_event_data[93],
deposit_event_data[94], deposit_event_data[95]
]) as usize;

let signature_offset = u32::from_be_bytes([
deposit_event_data[124], deposit_event_data[125],
deposit_event_data[126], deposit_event_data[127]
]) as usize;

let index_offset = u32::from_be_bytes([
deposit_event_data[156], deposit_event_data[157],
deposit_event_data[158], deposit_event_data[159]
]) as usize;

// Validate the expected offset values
if pubkey_offset != 160
|| withdrawal_credentials_offset != 256
|| amount_offset != 320
|| signature_offset != 384
|| index_offset != 512
{
return false;
}

// Check bounds for all offsets
if pubkey_offset + 32 > deposit_event_data.len()
|| withdrawal_credentials_offset + 32 > deposit_event_data.len()
|| amount_offset + 32 > deposit_event_data.len()
|| signature_offset + 32 > deposit_event_data.len()
|| index_offset + 32 > deposit_event_data.len()
{
return false;
}

// Extract sizes from the offset locations
let pubkey_size = u32::from_be_bytes([
deposit_event_data[pubkey_offset + 28],
deposit_event_data[pubkey_offset + 29],
deposit_event_data[pubkey_offset + 30],
deposit_event_data[pubkey_offset + 31]
]);

let withdrawal_credentials_size = u32::from_be_bytes([
deposit_event_data[withdrawal_credentials_offset + 28],
deposit_event_data[withdrawal_credentials_offset + 29],
deposit_event_data[withdrawal_credentials_offset + 30],
deposit_event_data[withdrawal_credentials_offset + 31]
]);

let amount_size = u32::from_be_bytes([
deposit_event_data[amount_offset + 28],
deposit_event_data[amount_offset + 29],
deposit_event_data[amount_offset + 30],
deposit_event_data[amount_offset + 31]
]);

let signature_size = u32::from_be_bytes([
deposit_event_data[signature_offset + 28],
deposit_event_data[signature_offset + 29],
deposit_event_data[signature_offset + 30],
deposit_event_data[signature_offset + 31]
]);

let index_size = u32::from_be_bytes([
deposit_event_data[index_offset + 28],
deposit_event_data[index_offset + 29],
deposit_event_data[index_offset + 30],
deposit_event_data[index_offset + 31]
]);

// Validate the expected sizes
pubkey_size == 48
&& withdrawal_credentials_size == 32
&& amount_size == 8
&& signature_size == 96
&& index_size == 8
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 3 additions & 5 deletions crates/ethereum/node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use reth_transaction_pool::{
};
use revm::context::TxEnv;
use std::{default::Default, marker::PhantomData, sync::Arc, time::SystemTime};
use reth_ethereum_consensus::DepositContractProvider;

/// Type configuration for a regular Ethereum node.
#[derive(Debug, Default, Clone, Copy)]
Expand All @@ -78,7 +79,7 @@ impl EthereumNode {
where
Node: FullNodeTypes<
Types: NodeTypes<
ChainSpec: Hardforks + EthereumHardforks + EthExecutorSpec,
ChainSpec: Hardforks + EthereumHardforks + EthExecutorSpec + DepositContractProvider,
Primitives = EthPrimitives,
>,
>,
Expand Down Expand Up @@ -544,12 +545,9 @@ pub struct EthereumConsensusBuilder {

impl<Node> ConsensusBuilder<Node> for EthereumConsensusBuilder
where
Node: FullNodeTypes<
Types: NodeTypes<ChainSpec: EthChainSpec + EthereumHardforks, Primitives = EthPrimitives>,
>,
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: EthChainSpec + EthereumHardforks + DepositContractProvider, Primitives = EthPrimitives>>,
{
type Consensus = Arc<EthBeaconConsensus<<Node::Types as NodeTypes>::ChainSpec>>;

async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
Ok(Arc::new(EthBeaconConsensus::new(ctx.chain_spec())))
}
Expand Down
Loading