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}