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}