From ac120ffd31b002a51b28895a34d48cc040f06bf3 Mon Sep 17 00:00:00 2001 From: stevencartavia <112043913+stevencartavia@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:26:57 -0600 Subject: [PATCH] feat: introduce PayloadValidator::payload_to_block (#19953) --- crates/engine/primitives/src/lib.rs | 22 ++++++++++++++-- crates/engine/tree/src/tree/tests.rs | 9 +++---- crates/ethereum/node/src/engine.rs | 9 +++---- crates/optimism/node/src/engine.rs | 16 +++++------- examples/custom-engine-types/src/main.rs | 9 +++---- examples/custom-node/src/engine.rs | 33 +++++++++++------------- 6 files changed, 53 insertions(+), 45 deletions(-) diff --git a/crates/engine/primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs index 196a3baa18..0c5dcc6c22 100644 --- a/crates/engine/primitives/src/lib.rs +++ b/crates/engine/primitives/src/lib.rs @@ -17,7 +17,7 @@ use reth_payload_primitives::{ EngineApiMessageVersion, EngineObjectValidationError, InvalidPayloadAttributesError, NewPayloadError, PayloadAttributes, PayloadOrAttributes, PayloadTypes, }; -use reth_primitives_traits::{Block, RecoveredBlock}; +use reth_primitives_traits::{Block, RecoveredBlock, SealedBlock}; use reth_trie_common::HashedPostState; use serde::{de::DeserializeOwned, Serialize}; @@ -131,6 +131,21 @@ pub trait PayloadValidator: Send + Sync + Unpin + 'static { /// The block type used by the engine. type Block: Block; + /// Converts the given payload into a sealed block without recovering signatures. + /// + /// This function validates the payload and converts it into a [`SealedBlock`] which contains + /// the block hash but does not perform signature recovery on transactions. + /// + /// This is more efficient than [`Self::ensure_well_formed_payload`] when signature recovery + /// is not needed immediately or will be performed later. + /// + /// Implementers should ensure that the checks are done in the order that conforms with the + /// engine-API specification. + fn convert_payload_to_block( + &self, + payload: Types::ExecutionData, + ) -> Result, NewPayloadError>; + /// Ensures that the given payload does not violate any consensus rules that concern the block's /// layout. /// @@ -142,7 +157,10 @@ pub trait PayloadValidator: Send + Sync + Unpin + 'static { fn ensure_well_formed_payload( &self, payload: Types::ExecutionData, - ) -> Result, NewPayloadError>; + ) -> Result, NewPayloadError> { + let sealed_block = self.convert_payload_to_block(payload)?; + sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into())) + } /// Verifies payload post-execution w.r.t. hashed state updates. fn validate_block_post_execution_with_hashed_state( diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 5c187c9085..1e3802d3f7 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -45,20 +45,17 @@ struct MockEngineValidator; impl reth_engine_primitives::PayloadValidator for MockEngineValidator { type Block = Block; - fn ensure_well_formed_payload( + fn convert_payload_to_block( &self, payload: ExecutionData, ) -> Result< - reth_primitives_traits::RecoveredBlock, + reth_primitives_traits::SealedBlock, reth_payload_primitives::NewPayloadError, > { - // For tests, convert the execution payload to a block let block = reth_ethereum_primitives::Block::try_from(payload.payload).map_err(|e| { reth_payload_primitives::NewPayloadError::Other(format!("{e:?}").into()) })?; - let sealed = block.seal_slow(); - - sealed.try_recover().map_err(|e| reth_payload_primitives::NewPayloadError::Other(e.into())) + Ok(block.seal_slow()) } } diff --git a/crates/ethereum/node/src/engine.rs b/crates/ethereum/node/src/engine.rs index 441e05d1cc..f1b880ab25 100644 --- a/crates/ethereum/node/src/engine.rs +++ b/crates/ethereum/node/src/engine.rs @@ -14,7 +14,7 @@ use reth_payload_primitives::{ validate_execution_requests, validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, NewPayloadError, PayloadOrAttributes, }; -use reth_primitives_traits::RecoveredBlock; +use reth_primitives_traits::SealedBlock; use std::sync::Arc; /// Validator for the ethereum engine API. @@ -43,12 +43,11 @@ where { type Block = Block; - fn ensure_well_formed_payload( + fn convert_payload_to_block( &self, payload: ExecutionData, - ) -> Result, NewPayloadError> { - let sealed_block = self.inner.ensure_well_formed_payload(payload)?; - sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into())) + ) -> Result, NewPayloadError> { + self.inner.ensure_well_formed_payload(payload).map_err(Into::into) } } diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index af018d6f27..11737f65ce 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -121,15 +121,6 @@ where { type Block = alloy_consensus::Block; - fn ensure_well_formed_payload( - &self, - payload: OpExecutionData, - ) -> Result, NewPayloadError> { - let sealed_block = - self.inner.ensure_well_formed_payload(payload).map_err(NewPayloadError::other)?; - sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into())) - } - fn validate_block_post_execution_with_hashed_state( &self, state_updates: &HashedPostState, @@ -159,6 +150,13 @@ where Ok(()) } + + fn convert_payload_to_block( + &self, + payload: OpExecutionData, + ) -> Result, NewPayloadError> { + self.inner.ensure_well_formed_payload(payload).map_err(NewPayloadError::other) + } } impl EngineApiValidator for OpEngineValidator diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index ca724e52af..0eef796291 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -51,7 +51,7 @@ use reth_ethereum::{ EthEvmConfig, EthereumEthApiBuilder, }, pool::{PoolTransaction, TransactionPool}, - primitives::{Block, RecoveredBlock, SealedBlock}, + primitives::{Block, SealedBlock}, provider::{EthStorage, StateProviderFactory}, rpc::types::engine::ExecutionPayload, tasks::TaskManager, @@ -193,12 +193,11 @@ impl CustomEngineValidator { impl PayloadValidator for CustomEngineValidator { type Block = reth_ethereum::Block; - fn ensure_well_formed_payload( + fn convert_payload_to_block( &self, payload: ExecutionData, - ) -> Result, NewPayloadError> { - let sealed_block = self.inner.ensure_well_formed_payload(payload)?; - sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into())) + ) -> Result, NewPayloadError> { + self.inner.ensure_well_formed_payload(payload).map_err(Into::into) } fn validate_payload_attributes_against_header( diff --git a/examples/custom-node/src/engine.rs b/examples/custom-node/src/engine.rs index 91dd5be663..fceace2d2e 100644 --- a/examples/custom-node/src/engine.rs +++ b/examples/custom-node/src/engine.rs @@ -14,7 +14,7 @@ use reth_ethereum::{ NewPayloadError, NodePrimitives, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, PayloadTypes, PayloadValidator, }, - primitives::{RecoveredBlock, SealedBlock}, + primitives::SealedBlock, storage::StateProviderFactory, trie::{KeccakKeyHasher, KeyHasher}, }; @@ -241,23 +241,6 @@ where { type Block = crate::primitives::block::Block; - fn ensure_well_formed_payload( - &self, - payload: CustomExecutionData, - ) -> Result, NewPayloadError> { - let sealed_block = PayloadValidator::::ensure_well_formed_payload( - &self.inner, - payload.inner, - )?; - let (block, senders) = sealed_block.split_sealed(); - let (header, body) = block.split_sealed_header_body(); - let header = CustomHeader { inner: header.into_header(), extension: payload.extension }; - let body = body.map_ommers(|_| CustomHeader::default()); - let block = SealedBlock::::from_parts_unhashed(header, body); - - Ok(block.with_senders(senders)) - } - fn validate_payload_attributes_against_header( &self, _attr: &CustomPayloadAttributes, @@ -266,6 +249,20 @@ where // skip default timestamp validation Ok(()) } + + fn convert_payload_to_block( + &self, + payload: CustomExecutionData, + ) -> Result, NewPayloadError> { + let sealed_block = PayloadValidator::::convert_payload_to_block( + &self.inner, + payload.inner, + )?; + let (header, body) = sealed_block.split_sealed_header_body(); + let header = CustomHeader { inner: header.into_header(), extension: payload.extension }; + let body = body.map_ommers(|_| CustomHeader::default()); + Ok(SealedBlock::::from_parts_unhashed(header, body)) + } } impl

EngineApiValidator for CustomEngineValidator