reth_payload_validator/
lib.rs1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11use alloy_rpc_types::engine::{
12 ExecutionPayload, ExecutionPayloadSidecar, MaybeCancunPayloadFields, PayloadError,
13};
14use reth_chainspec::EthereumHardforks;
15use reth_primitives::{BlockExt, SealedBlock};
16use reth_rpc_types_compat::engine::payload::try_into_block;
17use std::sync::Arc;
18
19#[derive(Clone, Debug)]
21pub struct ExecutionPayloadValidator<ChainSpec> {
22 chain_spec: Arc<ChainSpec>,
24}
25
26impl<ChainSpec> ExecutionPayloadValidator<ChainSpec> {
27 pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
29 Self { chain_spec }
30 }
31
32 #[inline]
34 pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
35 &self.chain_spec
36 }
37}
38
39impl<ChainSpec: EthereumHardforks> ExecutionPayloadValidator<ChainSpec> {
40 #[inline]
42 fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
43 self.chain_spec().is_cancun_active_at_timestamp(timestamp)
44 }
45
46 #[inline]
48 fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
49 self.chain_spec().is_shanghai_active_at_timestamp(timestamp)
50 }
51
52 #[inline]
54 fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
55 self.chain_spec().is_prague_active_at_timestamp(timestamp)
56 }
57
58 fn ensure_matching_blob_versioned_hashes(
63 &self,
64 sealed_block: &SealedBlock,
65 cancun_fields: &MaybeCancunPayloadFields,
66 ) -> Result<(), PayloadError> {
67 let num_blob_versioned_hashes = sealed_block.blob_versioned_hashes_iter().count();
68 if let Some(versioned_hashes) = cancun_fields.versioned_hashes() {
70 if num_blob_versioned_hashes != versioned_hashes.len() {
71 return Err(PayloadError::InvalidVersionedHashes)
73 }
74 for (payload_versioned_hash, block_versioned_hash) in
76 versioned_hashes.iter().zip(sealed_block.blob_versioned_hashes_iter())
77 {
78 if payload_versioned_hash != block_versioned_hash {
79 return Err(PayloadError::InvalidVersionedHashes)
80 }
81 }
82 } else {
83 if num_blob_versioned_hashes > 0 {
85 return Err(PayloadError::InvalidVersionedHashes)
86 }
87 }
88
89 Ok(())
90 }
91
92 pub fn ensure_well_formed_payload(
116 &self,
117 payload: ExecutionPayload,
118 sidecar: ExecutionPayloadSidecar,
119 ) -> Result<SealedBlock, PayloadError> {
120 let expected_hash = payload.block_hash();
121
122 let sealed_block = try_into_block(payload, &sidecar)?.seal_slow();
124
125 if expected_hash != sealed_block.hash() {
127 return Err(PayloadError::BlockHash {
128 execution: sealed_block.hash(),
129 consensus: expected_hash,
130 })
131 }
132
133 if self.is_cancun_active_at_timestamp(sealed_block.timestamp) {
134 if sealed_block.header.blob_gas_used.is_none() {
135 return Err(PayloadError::PostCancunBlockWithoutBlobGasUsed)
137 }
138 if sealed_block.header.excess_blob_gas.is_none() {
139 return Err(PayloadError::PostCancunBlockWithoutExcessBlobGas)
141 }
142 if sidecar.cancun().is_none() {
143 return Err(PayloadError::PostCancunWithoutCancunFields)
145 }
146 } else {
147 if sealed_block.has_eip4844_transactions() {
148 return Err(PayloadError::PreCancunBlockWithBlobTransactions)
150 }
151 if sealed_block.header.blob_gas_used.is_some() {
152 return Err(PayloadError::PreCancunBlockWithBlobGasUsed)
154 }
155 if sealed_block.header.excess_blob_gas.is_some() {
156 return Err(PayloadError::PreCancunBlockWithExcessBlobGas)
158 }
159 if sidecar.cancun().is_some() {
160 return Err(PayloadError::PreCancunWithCancunFields)
162 }
163 }
164
165 let shanghai_active = self.is_shanghai_active_at_timestamp(sealed_block.timestamp);
166 if !shanghai_active && sealed_block.body.withdrawals.is_some() {
167 return Err(PayloadError::PreShanghaiBlockWithWithdrawals)
169 }
170
171 if !self.is_prague_active_at_timestamp(sealed_block.timestamp) &&
172 sealed_block.has_eip7702_transactions()
173 {
174 return Err(PayloadError::PrePragueBlockWithEip7702Transactions)
175 }
176
177 self.ensure_matching_blob_versioned_hashes(
179 &sealed_block,
180 &sidecar.cancun().cloned().into(),
181 )?;
182
183 Ok(sealed_block)
184 }
185}