Repository: paradigmxyz/reth (Rust)

Issue:
Extract Header-only methods of `Consensus` to a separate trait
### Describe the feature

With #12451 `Consensus` is now generic over header and body
https://github.com/paradigmxyz/reth/blob/e686fccb99103ff6a0abb8ba0dcc631db687b1e3/crates/consensus/consensus/src/lib.rs#L47

However, in components which only know the header type we can't provide Consensus with Body generic, e.g https://github.com/paradigmxyz/reth/blob/e686fccb99103ff6a0abb8ba0dcc631db687b1e3/crates/net/downloaders/src/headers/reverse_headers.rs#L70

To address this, we'll need a separate trait which is alike `Consensus` but only operates on headers. 

Steps:
1. Inroduce trait `trait HeaderValidator<H = Header>` and move `Consensus` methods which only need header to it. Those include `validate_header`, `validate_header_against_parent`, `validate_header_range`, `validate_header_with_total_difficulty`
2. Inroduce bound `Consensus<H = Header, B = Body>: HeaderValidator<H>`
3. Fix trait impls and import `HeaderValidator` where needed

### Additional context

_No response_


Discussion:
Can i work on this?
hey @0xriazaka do you need any pointers for this?
> hey [@0xriazaka](https://github.com/0xriazaka) do you need any pointers for this?

check #12648



Patch:
diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs
index ec296f3ed49..e059305911f 100644
--- a/crates/consensus/consensus/src/lib.rs
+++ b/crates/consensus/consensus/src/lib.rs
@@ -46,7 +46,42 @@ impl<'a> PostExecutionInput<'a> {
 
 /// Consensus is a protocol that chooses canonical chain.
 #[auto_impl::auto_impl(&, Arc)]
-pub trait Consensus<H = Header, B = BlockBody>: Debug + Send + Sync {
+pub trait Consensus<H = Header, B = BlockBody>: HeaderValidator<H> + Debug + Send + Sync {
+    /// Ensures that body field values match the header.
+    fn validate_body_against_header(
+        &self,
+        body: &B,
+        header: &SealedHeader<H>,
+    ) -> Result<(), ConsensusError>;
+
+    /// Validate a block disregarding world state, i.e. things that can be checked before sender
+    /// recovery and execution.
+    ///
+    /// See the Yellow Paper sections 4.3.2 "Holistic Validity", 4.3.4 "Block Header Validity", and
+    /// 11.1 "Ommer Validation".
+    ///
+    /// **This should not be called for the genesis block**.
+    ///
+    /// Note: validating blocks does not include other validations of the Consensus
+    fn validate_block_pre_execution(&self, block: &SealedBlock<H, B>)
+        -> Result<(), ConsensusError>;
+
+    /// Validate a block considering world state, i.e. things that can not be checked before
+    /// execution.
+    ///
+    /// See the Yellow Paper sections 4.3.2 "Holistic Validity".
+    ///
+    /// Note: validating blocks does not include other validations of the Consensus
+    fn validate_block_post_execution(
+        &self,
+        block: &BlockWithSenders,
+        input: PostExecutionInput<'_>,
+    ) -> Result<(), ConsensusError>;
+}
+
+/// HeaderValidator is a protocol that validates headers and their relationships.
+#[auto_impl::auto_impl(&, Arc)]
+pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
     /// Validate if header is correct and follows consensus specification.
     ///
     /// This is called on standalone header to check if all hashes are correct.
@@ -60,7 +95,8 @@ pub trait Consensus<H = Header, B = BlockBody>: Debug + Send + Sync {
     ///
     /// **This should not be called for the genesis block**.
     ///
-    /// Note: Validating header against its parent does not include other Consensus validations.
+    /// Note: Validating header against its parent does not include other HeaderValidator
+    /// validations.
     fn validate_header_against_parent(
         &self,
         header: &SealedHeader<H>,
@@ -99,43 +135,12 @@ pub trait Consensus<H = Header, B = BlockBody>: Debug + Send + Sync {
     ///
     /// Some consensus engines may want to do additional checks here.
     ///
-    /// Note: validating headers with TD does not include other Consensus validation.
+    /// Note: validating headers with TD does not include other HeaderValidator validation.
     fn validate_header_with_total_difficulty(
         &self,
         header: &H,
         total_difficulty: U256,
     ) -> Result<(), ConsensusError>;
-
-    /// Ensures that body field values match the header.
-    fn validate_body_against_header(
-        &self,
-        body: &B,
-        header: &SealedHeader<H>,
-    ) -> Result<(), ConsensusError>;
-
-    /// Validate a block disregarding world state, i.e. things that can be checked before sender
-    /// recovery and execution.
-    ///
-    /// See the Yellow Paper sections 4.3.2 "Holistic Validity", 4.3.4 "Block Header Validity", and
-    /// 11.1 "Ommer Validation".
-    ///
-    /// **This should not be called for the genesis block**.
-    ///
-    /// Note: validating blocks does not include other validations of the Consensus
-    fn validate_block_pre_execution(&self, block: &SealedBlock<H, B>)
-        -> Result<(), ConsensusError>;
-
-    /// Validate a block considering world state, i.e. things that can not be checked before
-    /// execution.
-    ///
-    /// See the Yellow Paper sections 4.3.2 "Holistic Validity".
-    ///
-    /// Note: validating blocks does not include other validations of the Consensus
-    fn validate_block_post_execution(
-        &self,
-        block: &BlockWithSenders,
-        input: PostExecutionInput<'_>,
-    ) -> Result<(), ConsensusError>;
 }
 
 /// Consensus Errors
diff --git a/crates/consensus/consensus/src/noop.rs b/crates/consensus/consensus/src/noop.rs
index 9b72f89b176..6d12af08d51 100644
--- a/crates/consensus/consensus/src/noop.rs
+++ b/crates/consensus/consensus/src/noop.rs
@@ -1,4 +1,4 @@
-use crate::{Consensus, ConsensusError, PostExecutionInput};
+use crate::{Consensus, ConsensusError, HeaderValidator, PostExecutionInput};
 use alloy_primitives::U256;
 use reth_primitives::{BlockWithSenders, SealedBlock, SealedHeader};
 
@@ -7,7 +7,7 @@ use reth_primitives::{BlockWithSenders, SealedBlock, SealedHeader};
 #[non_exhaustive]
 pub struct NoopConsensus;
 
-impl<H, B> Consensus<H, B> for NoopConsensus {
+impl<H> HeaderValidator<H> for NoopConsensus {
     fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> {
         Ok(())
     }
@@ -27,7 +27,9 @@ impl<H, B> Consensus<H, B> for NoopConsensus {
     ) -> Result<(), ConsensusError> {
         Ok(())
     }
+}
 
+impl<H, B> Consensus<H, B> for NoopConsensus {
     fn validate_body_against_header(
         &self,
         _body: &B,
diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs
index 7198a703672..ffabe5b1952 100644
--- a/crates/ethereum/consensus/src/lib.rs
+++ b/crates/ethereum/consensus/src/lib.rs
@@ -11,7 +11,7 @@
 use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH};
 use alloy_primitives::U256;
 use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
-use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
+use reth_consensus::{Consensus, ConsensusError, HeaderValidator, PostExecutionInput};
 use reth_consensus_common::validation::{
     validate_4844_header_standalone, validate_against_parent_4844,
     validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number,
@@ -92,6 +92,30 @@ impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec>
 
 impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensus
     for EthBeaconConsensus<ChainSpec>
+{
+    fn validate_body_against_header(
+        &self,
+        body: &BlockBody,
+        header: &SealedHeader,
+    ) -> Result<(), ConsensusError> {
+        validate_body_against_header(body, header)
+    }
+
+    fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError> {
+        validate_block_pre_execution(block, &self.chain_spec)
+    }
+
+    fn validate_block_post_execution(
+        &self,
+        block: &BlockWithSenders,
+        input: PostExecutionInput<'_>,
+    ) -> Result<(), ConsensusError> {
+        validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
+    }
+}
+
+impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderValidator
+    for EthBeaconConsensus<ChainSpec>
 {
     fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
         validate_header_gas(header)?;
@@ -210,26 +234,6 @@ impl<ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensu
 
         Ok(())
     }
-
-    fn validate_body_against_header(
-        &self,
-        body: &BlockBody,
-        header: &SealedHeader,
-    ) -> Result<(), ConsensusError> {
-        validate_body_against_header(body, header)
-    }
-
-    fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError> {
-        validate_block_pre_execution(block, &self.chain_spec)
-    }
-
-    fn validate_block_post_execution(
-        &self,
-        block: &BlockWithSenders,
-        input: PostExecutionInput<'_>,
-    ) -> Result<(), ConsensusError> {
-        validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
-    }
 }
 
 #[cfg(test)]
diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs
index 0f8111e4395..2d79e0a7af6 100644
--- a/crates/net/downloaders/src/headers/reverse_headers.rs
+++ b/crates/net/downloaders/src/headers/reverse_headers.rs
@@ -9,7 +9,7 @@ use futures::{stream::Stream, FutureExt};
 use futures_util::{stream::FuturesUnordered, StreamExt};
 use rayon::prelude::*;
 use reth_config::config::HeadersConfig;
-use reth_consensus::Consensus;
+use reth_consensus::{Consensus, HeaderValidator};
 use reth_network_p2p::{
     error::{DownloadError, DownloadResult, PeerRequestResult},
     headers::{
diff --git a/crates/net/p2p/src/headers/downloader.rs b/crates/net/p2p/src/headers/downloader.rs
index f02d9461fc1..03ab467bafb 100644
--- a/crates/net/p2p/src/headers/downloader.rs
+++ b/crates/net/p2p/src/headers/downloader.rs
@@ -4,7 +4,7 @@ use alloy_consensus::BlockHeader;
 use alloy_eips::BlockHashOrNumber;
 use alloy_primitives::B256;
 use futures::Stream;
-use reth_consensus::Consensus;
+use reth_consensus::HeaderValidator;
 use reth_primitives::SealedHeader;
 use reth_primitives_traits::BlockWithParent;
 /// A downloader capable of fetching and yielding block headers.
@@ -83,7 +83,7 @@ impl SyncTarget {
 ///
 /// Returns Ok(false) if the
 pub fn validate_header_download<H: BlockHeader>(
-    consensus: &dyn Consensus<H>,
+    consensus: &dyn HeaderValidator<H>,
     header: &SealedHeader<H>,
     parent: &SealedHeader<H>,
 ) -> DownloadResult<()> {
diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs
index 72f67dcb450..e8b7959dd27 100644
--- a/crates/optimism/consensus/src/lib.rs
+++ b/crates/optimism/consensus/src/lib.rs
@@ -12,7 +12,7 @@
 use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH};
 use alloy_primitives::{B64, U256};
 use reth_chainspec::EthereumHardforks;
-use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
+use reth_consensus::{Consensus, ConsensusError, HeaderValidator, PostExecutionInput};
 use reth_consensus_common::validation::{
     validate_against_parent_4844, validate_against_parent_eip1559_base_fee,
     validate_against_parent_hash_number, validate_against_parent_timestamp,
@@ -47,6 +47,50 @@ impl OpBeaconConsensus {
 }
 
 impl Consensus for OpBeaconConsensus {
+    fn validate_body_against_header(
+        &self,
+        body: &BlockBody,
+        header: &SealedHeader,
+    ) -> Result<(), ConsensusError> {
+        validate_body_against_header(body, header)
+    }
+
+    fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError> {
+        // Check ommers hash
+        let ommers_hash = reth_primitives::proofs::calculate_ommers_root(&block.body.ommers);
+        if block.header.ommers_hash != ommers_hash {
+            return Err(ConsensusError::BodyOmmersHashDiff(
+                GotExpected { got: ommers_hash, expected: block.header.ommers_hash }.into(),
+            ))
+        }
+
+        // Check transaction root
+        if let Err(error) = block.ensure_transaction_root_valid() {
+            return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
+        }
+
+        // EIP-4895: Beacon chain push withdrawals as operations
+        if self.chain_spec.is_shanghai_active_at_timestamp(block.timestamp) {
+            validate_shanghai_withdrawals(block)?;
+        }
+
+        if self.chain_spec.is_cancun_active_at_timestamp(block.timestamp) {
+            validate_cancun_gas(block)?;
+        }
+
+        Ok(())
+    }
+
+    fn validate_block_post_execution(
+        &self,
+        block: &BlockWithSenders,
+        input: PostExecutionInput<'_>,
+    ) -> Result<(), ConsensusError> {
+        validate_block_post_execution(block, &self.chain_spec, input.receipts)
+    }
+}
+
+impl HeaderValidator for OpBeaconConsensus {
     fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
         validate_header_gas(header)?;
         validate_header_base_fee(header, &self.chain_spec)
@@ -118,46 +162,4 @@ impl Consensus for OpBeaconConsensus {
 
         Ok(())
     }
-
-    fn validate_body_against_header(
-        &self,
-        body: &BlockBody,
-        header: &SealedHeader,
-    ) -> Result<(), ConsensusError> {
-        validate_body_against_header(body, header)
-    }
-
-    fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError> {
-        // Check ommers hash
-        let ommers_hash = reth_primitives::proofs::calculate_ommers_root(&block.body.ommers);
-        if block.header.ommers_hash != ommers_hash {
-            return Err(ConsensusError::BodyOmmersHashDiff(
-                GotExpected { got: ommers_hash, expected: block.header.ommers_hash }.into(),
-            ))
-        }
-
-        // Check transaction root
-        if let Err(error) = block.ensure_transaction_root_valid() {
-            return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
-        }
-
-        // EIP-4895: Beacon chain push withdrawals as operations
-        if self.chain_spec.is_shanghai_active_at_timestamp(block.timestamp) {
-            validate_shanghai_withdrawals(block)?;
-        }
-
-        if self.chain_spec.is_cancun_active_at_timestamp(block.timestamp) {
-            validate_cancun_gas(block)?;
-        }
-
-        Ok(())
-    }
-
-    fn validate_block_post_execution(
-        &self,
-        block: &BlockWithSenders,
-        input: PostExecutionInput<'_>,
-    ) -> Result<(), ConsensusError> {
-        validate_block_post_execution(block, &self.chain_spec, input.receipts)
-    }
 }


Test patch:
diff --git a/crates/consensus/consensus/src/test_utils.rs b/crates/consensus/consensus/src/test_utils.rs
index 52926ec323e..ba683dd255f 100644
--- a/crates/consensus/consensus/src/test_utils.rs
+++ b/crates/consensus/consensus/src/test_utils.rs
@@ -1,4 +1,4 @@
-use crate::{Consensus, ConsensusError, PostExecutionInput};
+use crate::{Consensus, ConsensusError, HeaderValidator, PostExecutionInput};
 use alloy_primitives::U256;
 use core::sync::atomic::{AtomicBool, Ordering};
 use reth_primitives::{BlockWithSenders, SealedBlock, SealedHeader};
@@ -47,18 +47,21 @@ impl TestConsensus {
 }
 
 impl<H, B> Consensus<H, B> for TestConsensus {
-    fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> {
-        if self.fail_validation() {
+    fn validate_body_against_header(
+        &self,
+        _body: &B,
+        _header: &SealedHeader<H>,
+    ) -> Result<(), ConsensusError> {
+        if self.fail_body_against_header() {
             Err(ConsensusError::BaseFeeMissing)
         } else {
             Ok(())
         }
     }
 
-    fn validate_header_against_parent(
+    fn validate_block_pre_execution(
         &self,
-        _header: &SealedHeader<H>,
-        _parent: &SealedHeader<H>,
+        _block: &SealedBlock<H, B>,
     ) -> Result<(), ConsensusError> {
         if self.fail_validation() {
             Err(ConsensusError::BaseFeeMissing)
@@ -67,10 +70,10 @@ impl<H, B> Consensus<H, B> for TestConsensus {
         }
     }
 
-    fn validate_header_with_total_difficulty(
+    fn validate_block_post_execution(
         &self,
-        _header: &H,
-        _total_difficulty: U256,
+        _block: &BlockWithSenders,
+        _input: PostExecutionInput<'_>,
     ) -> Result<(), ConsensusError> {
         if self.fail_validation() {
             Err(ConsensusError::BaseFeeMissing)
@@ -78,22 +81,21 @@ impl<H, B> Consensus<H, B> for TestConsensus {
             Ok(())
         }
     }
+}
 
-    fn validate_body_against_header(
-        &self,
-        _body: &B,
-        _header: &SealedHeader<H>,
-    ) -> Result<(), ConsensusError> {
-        if self.fail_body_against_header() {
+impl<H> HeaderValidator<H> for TestConsensus {
+    fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> {
+        if self.fail_validation() {
             Err(ConsensusError::BaseFeeMissing)
         } else {
             Ok(())
         }
     }
 
-    fn validate_block_pre_execution(
+    fn validate_header_against_parent(
         &self,
-        _block: &SealedBlock<H, B>,
+        _header: &SealedHeader<H>,
+        _parent: &SealedHeader<H>,
     ) -> Result<(), ConsensusError> {
         if self.fail_validation() {
             Err(ConsensusError::BaseFeeMissing)
@@ -102,10 +104,10 @@ impl<H, B> Consensus<H, B> for TestConsensus {
         }
     }
 
-    fn validate_block_post_execution(
+    fn validate_header_with_total_difficulty(
         &self,
-        _block: &BlockWithSenders,
-        _input: PostExecutionInput<'_>,
+        _header: &H,
+        _total_difficulty: U256,
     ) -> Result<(), ConsensusError> {
         if self.fail_validation() {
             Err(ConsensusError::BaseFeeMissing)
diff --git a/crates/net/p2p/src/test_utils/headers.rs b/crates/net/p2p/src/test_utils/headers.rs
index bc5262abef4..5809ad6bdd4 100644
--- a/crates/net/p2p/src/test_utils/headers.rs
+++ b/crates/net/p2p/src/test_utils/headers.rs
@@ -147,7 +147,7 @@ impl Stream for TestDownload {
 
             let empty: SealedHeader = SealedHeader::default();
             if let Err(error) =
-                Consensus::<_>::validate_header_against_parent(&this.consensus, &empty, &empty)
+                <dyn Consensus<_>>::validate_header_against_parent(&this.consensus, &empty, &empty)
             {
                 this.done = true;
                 return Poll::Ready(Some(Err(DownloadError::HeaderValidation {