//! Payload Validation support. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod cancun; pub mod prague; pub mod shanghai; use alloy_rpc_types_engine::{ExecutionData, PayloadError}; use reth_chainspec::EthereumHardforks; use reth_primitives::SealedBlock; use reth_primitives_traits::transaction::signed::is_impersonated_tx; use reth_primitives_traits::{Block, SignedTransaction}; use std::sync::Arc; /// Execution payload validator. #[derive(Clone, Debug)] pub struct ExecutionPayloadValidator { /// Chain spec to validate against. chain_spec: Arc, } impl ExecutionPayloadValidator { /// Create a new validator. pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } /// Returns the chain spec used by the validator. #[inline] pub const fn chain_spec(&self) -> &Arc { &self.chain_spec } } impl ExecutionPayloadValidator { /// Returns true if the Cancun hardfork is active at the given timestamp. #[inline] fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool { self.chain_spec().is_cancun_active_at_timestamp(timestamp) } /// Returns true if the Shanghai hardfork is active at the given timestamp. #[inline] fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool { self.chain_spec().is_shanghai_active_at_timestamp(timestamp) } /// Returns true if the Prague hardfork is active at the given timestamp. #[inline] fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool { self.chain_spec().is_prague_active_at_timestamp(timestamp) } /// Ensures that the given payload does not violate any consensus rules that concern the block's /// layout, like: /// - missing or invalid base fee /// - invalid extra data /// - invalid transactions /// - incorrect hash /// - the versioned hashes passed with the payload do not exactly match transaction versioned /// hashes /// - the block does not contain blob transactions if it is pre-cancun /// /// The checks are done in the order that conforms with the engine-API specification. /// /// This is intended to be invoked after receiving the payload from the CLI. /// 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 /// /// If the cancun fields are provided this also validates that the versioned hashes in the block /// match the versioned hashes passed in the /// [`CancunPayloadFields`](alloy_rpc_types_engine::CancunPayloadFields), if the cancun payload /// fields are provided. If the payload fields are not provided, but versioned hashes exist /// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`]. /// /// This validates versioned hashes according to the Engine API Cancun spec: /// pub fn ensure_well_formed_payload( &self, payload: ExecutionData, ) -> Result>, PayloadError> { let ExecutionData { mut payload, sidecar } = payload; let expected_hash = payload.block_hash(); // First parse the block let transactions = payload.as_v1().transactions.clone(); let (normal, system) = transactions.into_iter().partition(|tx| { let tx = T::decode_2718(&mut tx.iter().as_slice()); match tx { Ok(tx) => is_impersonated_tx(tx.signature(), tx.gas_price()).is_none(), Err(_) => true, } }); payload.as_v1_mut().transactions = normal; let mut block = payload.try_into_block_with_sidecar(&sidecar)?; block.body.transactions = system .iter() .map(|tx| { T::decode_2718(&mut tx.iter().as_slice()).expect("transaction should be valid") }) .chain(block.body.transactions) .collect(); let sealed_block = block.seal_slow(); // Ensure the hash included in the payload matches the block hash if expected_hash != sealed_block.hash() { return Err(PayloadError::BlockHash { execution: sealed_block.hash(), consensus: expected_hash, }); } shanghai::ensure_well_formed_fields( sealed_block.body(), self.is_shanghai_active_at_timestamp(sealed_block.timestamp), )?; cancun::ensure_well_formed_fields( &sealed_block, sidecar.cancun(), self.is_cancun_active_at_timestamp(sealed_block.timestamp), )?; prague::ensure_well_formed_fields( sealed_block.body(), sidecar.prague(), self.is_prague_active_at_timestamp(sealed_block.timestamp), )?; Ok(sealed_block) } }