diff --git a/src/node/consensus.rs b/src/node/consensus.rs index da183fdd1..824ed3309 100644 --- a/src/node/consensus.rs +++ b/src/node/consensus.rs @@ -3,18 +3,22 @@ use crate::{ node::HlNode, {HlBlock, HlBlockBody, HlPrimitives}, }; +use alloy_consensus::BlockHeader as _; +use alloy_eips::eip7685::Requests; use reth::{ api::FullNodeTypes, beacon_consensus::EthBeaconConsensus, builder::{components::ConsensusBuilder, BuilderContext}, consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator}, consensus_common::validation::{ - validate_against_parent_4844, validate_against_parent_eip1559_base_fee, - validate_against_parent_hash_number, validate_against_parent_timestamp, + validate_against_parent_4844, validate_against_parent_hash_number, }, }; -use reth_chainspec::EthChainSpec; -use reth_primitives::{Receipt, RecoveredBlock, SealedBlock, SealedHeader}; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_primitives::{ + gas_spent_by_transactions, GotExpected, Receipt, RecoveredBlock, SealedBlock, SealedHeader, +}; +use reth_primitives_traits::{Block, BlockHeader, Receipt as ReceiptTrait}; use reth_provider::BlockExecutionResult; use std::sync::Arc; @@ -53,6 +57,22 @@ impl HlConsensus { } } +/// Validates the timestamp against the parent to make sure it is in the past. +#[inline] +pub fn validate_against_parent_timestamp( + header: &H, + parent: &H, +) -> Result<(), ConsensusError> { + // NOTE: HyperEVM allows the timestamp to be the same as the parent (big and small blocks) + if header.timestamp() < parent.timestamp() { + return Err(ConsensusError::TimestampIsInPast { + parent_timestamp: parent.timestamp(), + timestamp: header.timestamp(), + }); + } + Ok(()) +} + impl HeaderValidator for HlConsensus { fn validate_header(&self, _header: &SealedHeader) -> Result<(), ConsensusError> { // TODO: doesn't work because of extradata check @@ -127,12 +147,81 @@ impl Consensus for HlConsensus( + block: &RecoveredBlock, + chain_spec: &ChainSpec, + receipts: &[R], + requests: &Requests, +) -> Result<(), ConsensusError> +where + B: Block, + R: ReceiptTrait, + ChainSpec: EthereumHardforks, +{ + use reth_copy::verify_receipts; + // Copy of reth's validate_block_post_execution + // Differences: + // - Filter out system transactions for receipts check + + // 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.header().gas_used() != cumulative_gas_used { + return Err(ConsensusError::BlockGasUsed { + gas: GotExpected { + got: cumulative_gas_used, + expected: block.header().gas_used(), + }, + gas_spent_by_tx: gas_spent_by_transactions(receipts), + }); + } + + // Before Byzantium, receipts contained state root that would mean that expensive + // 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()) { + let receipts_for_root = receipts + .iter() + .cloned() + .filter(|r| r.cumulative_gas_used() != 0) + .collect::>(); + if let Err(error) = verify_receipts( + block.header().receipts_root(), + block.header().logs_bloom(), + &receipts_for_root, + ) { + 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.header().timestamp()) { + let Some(header_requests_hash) = block.header().requests_hash() else { + return Err(ConsensusError::RequestsHashMissing); + }; + let requests_hash = requests.requests_hash(); + if requests_hash != header_requests_hash { + return Err(ConsensusError::BodyRequestsHashDiff( + GotExpected::new(requests_hash, header_requests_hash).into(), + )); + } + } + + Ok(()) +} + impl FullConsensus for HlConsensus { fn validate_block_post_execution( &self, block: &RecoveredBlock, result: &BlockExecutionResult, ) -> Result<(), ConsensusError> { - FullConsensus::::validate_block_post_execution(&self.inner, block, result) + validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests) } } diff --git a/src/node/consensus/reth_copy.rs b/src/node/consensus/reth_copy.rs new file mode 100644 index 000000000..a3d408983 --- /dev/null +++ b/src/node/consensus/reth_copy.rs @@ -0,0 +1,67 @@ +//! Copy of reth codebase. + +use alloy_consensus::{proofs::calculate_receipt_root, TxReceipt}; +use alloy_primitives::{Bloom, B256}; +use reth::consensus::ConsensusError; +use reth_primitives::GotExpected; +use reth_primitives_traits::Receipt; + +/// Calculate the receipts root, and compare it against the expected receipts root and logs +/// bloom. +pub(super) fn verify_receipts( + expected_receipts_root: B256, + expected_logs_bloom: Bloom, + receipts: &[R], +) -> Result<(), ConsensusError> { + // Calculate receipts root. + let receipts_with_bloom = receipts + .iter() + .map(TxReceipt::with_bloom_ref) + .collect::>(); + let receipts_root = calculate_receipt_root(&receipts_with_bloom); + + // Calculate header logs bloom. + let logs_bloom = receipts_with_bloom + .iter() + .fold(Bloom::ZERO, |bloom, r| bloom | r.bloom_ref()); + + compare_receipts_root_and_logs_bloom( + receipts_root, + logs_bloom, + expected_receipts_root, + expected_logs_bloom, + )?; + + Ok(()) +} + +/// Compare the calculated receipts root with the expected receipts root, also compare +/// the calculated logs bloom with the expected logs bloom. +pub(super) fn compare_receipts_root_and_logs_bloom( + calculated_receipts_root: B256, + calculated_logs_bloom: Bloom, + expected_receipts_root: B256, + expected_logs_bloom: Bloom, +) -> Result<(), ConsensusError> { + if calculated_receipts_root != expected_receipts_root { + return Err(ConsensusError::BodyReceiptRootDiff( + GotExpected { + got: calculated_receipts_root, + expected: expected_receipts_root, + } + .into(), + )); + } + + if calculated_logs_bloom != expected_logs_bloom { + return Err(ConsensusError::BodyBloomLogDiff( + GotExpected { + got: calculated_logs_bloom, + expected: expected_logs_bloom, + } + .into(), + )); + } + + Ok(()) +}