reth_ethereum_payload_builder/
validator.rs

1//! Validates execution payload wrt Ethereum consensus rules
2
3use alloy_consensus::Block;
4use alloy_rpc_types_engine::{ExecutionData, PayloadError};
5use reth_chainspec::EthereumHardforks;
6use reth_payload_validator::{cancun, prague, shanghai};
7use reth_primitives_traits::{Block as _, SealedBlock, SignedTransaction};
8use std::sync::Arc;
9
10/// Execution payload validator.
11#[derive(Clone, Debug)]
12pub struct EthereumExecutionPayloadValidator<ChainSpec> {
13    /// Chain spec to validate against.
14    chain_spec: Arc<ChainSpec>,
15}
16
17impl<ChainSpec> EthereumExecutionPayloadValidator<ChainSpec> {
18    /// Create a new validator.
19    pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
20        Self { chain_spec }
21    }
22
23    /// Returns the chain spec used by the validator.
24    #[inline]
25    pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
26        &self.chain_spec
27    }
28}
29
30impl<ChainSpec: EthereumHardforks> EthereumExecutionPayloadValidator<ChainSpec> {
31    /// Returns true if the Cancun hardfork is active at the given timestamp.
32    #[inline]
33    fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
34        self.chain_spec().is_cancun_active_at_timestamp(timestamp)
35    }
36
37    /// Returns true if the Shanghai hardfork is active at the given timestamp.
38    #[inline]
39    fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
40        self.chain_spec().is_shanghai_active_at_timestamp(timestamp)
41    }
42
43    /// Returns true if the Prague hardfork is active at the given timestamp.
44    #[inline]
45    fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
46        self.chain_spec().is_prague_active_at_timestamp(timestamp)
47    }
48
49    /// Ensures that the given payload does not violate any consensus rules that concern the block's
50    /// layout, like:
51    ///    - missing or invalid base fee
52    ///    - invalid extra data
53    ///    - invalid transactions
54    ///    - incorrect hash
55    ///    - the versioned hashes passed with the payload do not exactly match transaction versioned
56    ///      hashes
57    ///    - the block does not contain blob transactions if it is pre-cancun
58    ///
59    /// The checks are done in the order that conforms with the engine-API specification.
60    ///
61    /// This is intended to be invoked after receiving the payload from the CLI.
62    /// The additional [`MaybeCancunPayloadFields`](alloy_rpc_types_engine::MaybeCancunPayloadFields) are not part of the payload, but are additional fields in the `engine_newPayloadV3` RPC call, See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
63    ///
64    /// If the cancun fields are provided this also validates that the versioned hashes in the block
65    /// match the versioned hashes passed in the
66    /// [`CancunPayloadFields`](alloy_rpc_types_engine::CancunPayloadFields), if the cancun payload
67    /// fields are provided. If the payload fields are not provided, but versioned hashes exist
68    /// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`].
69    ///
70    /// This validates versioned hashes according to the Engine API Cancun spec:
71    /// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
72    pub fn ensure_well_formed_payload<T: SignedTransaction>(
73        &self,
74        payload: ExecutionData,
75    ) -> Result<SealedBlock<Block<T>>, PayloadError> {
76        let ExecutionData { payload, sidecar } = payload;
77
78        let expected_hash = payload.block_hash();
79
80        // First parse the block
81        let sealed_block = payload.try_into_block_with_sidecar(&sidecar)?.seal_slow();
82
83        // Ensure the hash included in the payload matches the block hash
84        if expected_hash != sealed_block.hash() {
85            return Err(PayloadError::BlockHash {
86                execution: sealed_block.hash(),
87                consensus: expected_hash,
88            })
89        }
90
91        shanghai::ensure_well_formed_fields(
92            sealed_block.body(),
93            self.is_shanghai_active_at_timestamp(sealed_block.timestamp),
94        )?;
95
96        cancun::ensure_well_formed_fields(
97            &sealed_block,
98            sidecar.cancun(),
99            self.is_cancun_active_at_timestamp(sealed_block.timestamp),
100        )?;
101
102        prague::ensure_well_formed_fields(
103            sealed_block.body(),
104            sidecar.prague(),
105            self.is_prague_active_at_timestamp(sealed_block.timestamp),
106        )?;
107
108        Ok(sealed_block)
109    }
110}