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    /// Ensures that the given payload does not violate any consensus rules that concern the block's
32    /// layout,
33    ///
34    /// See also [`ensure_well_formed_payload`]
35    pub fn ensure_well_formed_payload<T: SignedTransaction>(
36        &self,
37        payload: ExecutionData,
38    ) -> Result<SealedBlock<Block<T>>, PayloadError> {
39        ensure_well_formed_payload(&self.chain_spec, payload)
40    }
41}
42
43/// Ensures that the given payload does not violate any consensus rules that concern the block's
44/// layout, like:
45///    - missing or invalid base fee
46///    - invalid extra data
47///    - invalid transactions
48///    - incorrect hash
49///    - the versioned hashes passed with the payload do not exactly match transaction versioned
50///      hashes
51///    - the block does not contain blob transactions if it is pre-cancun
52///
53/// The checks are done in the order that conforms with the engine-API specification.
54///
55/// This is intended to be invoked after receiving the payload from the CLI.
56/// 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>
57///
58/// If the cancun fields are provided this also validates that the versioned hashes in the block
59/// match the versioned hashes passed in the
60/// [`CancunPayloadFields`](alloy_rpc_types_engine::CancunPayloadFields), if the cancun payload
61/// fields are provided. If the payload fields are not provided, but versioned hashes exist
62/// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`].
63///
64/// This validates versioned hashes according to the Engine API Cancun spec:
65/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
66pub fn ensure_well_formed_payload<ChainSpec, T>(
67    chain_spec: ChainSpec,
68    payload: ExecutionData,
69) -> Result<SealedBlock<Block<T>>, PayloadError>
70where
71    ChainSpec: EthereumHardforks,
72    T: SignedTransaction,
73{
74    let ExecutionData { payload, sidecar } = payload;
75
76    let expected_hash = payload.block_hash();
77
78    // First parse the block
79    let sealed_block = payload.try_into_block_with_sidecar(&sidecar)?.seal_slow();
80
81    // Ensure the hash included in the payload matches the block hash
82    if expected_hash != sealed_block.hash() {
83        return Err(PayloadError::BlockHash {
84            execution: sealed_block.hash(),
85            consensus: expected_hash,
86        })
87    }
88
89    shanghai::ensure_well_formed_fields(
90        sealed_block.body(),
91        chain_spec.is_shanghai_active_at_timestamp(sealed_block.timestamp),
92    )?;
93
94    cancun::ensure_well_formed_fields(
95        &sealed_block,
96        sidecar.cancun(),
97        chain_spec.is_cancun_active_at_timestamp(sealed_block.timestamp),
98    )?;
99
100    prague::ensure_well_formed_fields(
101        sealed_block.body(),
102        sidecar.prague(),
103        chain_spec.is_prague_active_at_timestamp(sealed_block.timestamp),
104    )?;
105
106    Ok(sealed_block)
107}