From 02513ecf3b3d4155a5b07b4fee8cd68d2c4a921a Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 16 Feb 2026 15:46:45 -0800 Subject: [PATCH] perf(engine): overlap block conversion with execution in payload validation (#21957) Co-authored-by: Amp Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --- .../engine/tree/src/tree/payload_validator.rs | 46 ++++++++++++++++--- crates/node/builder/src/rpc.rs | 6 +-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 388c254686..dfeca52839 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -326,9 +326,41 @@ where mut ctx: TreeCtx<'_, N>, ) -> ValidationOutcome> where - V: PayloadValidator, + V: PayloadValidator + Clone, Evm: ConfigureEngineEvm, { + // Spawn block conversion on a background thread so it runs concurrently with the + // rest of the function (setup + execution). For payloads this overlaps the cost of + // RLP decoding + header hashing; for already-converted blocks this is a no-op. + let convert_to_block = match &input { + BlockOrPayload::Payload(_) => { + let payload_clone = input.clone(); + let validator = self.validator.clone(); + let (tx, rx) = tokio::sync::oneshot::channel(); + self.payload_processor.executor().spawn_blocking(move || { + let BlockOrPayload::Payload(payload) = payload_clone else { unreachable!() }; + let _ = tx.send(validator.convert_payload_to_block(payload)); + }); + Either::Left(rx) + } + BlockOrPayload::Block(_) => Either::Right(()), + }; + + // Returns the sealed block, either by awaiting the background conversion task (for + // payloads) or by extracting the already-converted block directly. + let convert_to_block = + move |input: BlockOrPayload| -> Result, NewPayloadError> { + match convert_to_block { + Either::Left(rx) => rx.blocking_recv().map_err(|_| { + NewPayloadError::Other("block conversion task panicked".into()) + })?, + Either::Right(()) => { + let BlockOrPayload::Block(block) = input else { unreachable!() }; + Ok(block) + } + } + }; + /// A helper macro that returns the block in case there was an error /// This macro is used for early returns before block conversion macro_rules! ensure_ok { @@ -336,7 +368,7 @@ where match $expr { Ok(val) => val, Err(e) => { - let block = self.convert_to_block(input)?; + let block = convert_to_block(input)?; return Err(InsertBlockError::new(block, e.into()).into()) } } @@ -367,7 +399,7 @@ where else { // this is pre-validated in the tree return Err(InsertBlockError::new( - self.convert_to_block(input)?, + convert_to_block(input)?, ProviderError::HeaderNotFound(parent_hash.into()).into(), ) .into()) @@ -380,7 +412,7 @@ where let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state())) else { return Err(InsertBlockError::new( - self.convert_to_block(input)?, + convert_to_block(input)?, ProviderError::HeaderNotFound(parent_hash.into()).into(), ) .into()) @@ -473,7 +505,7 @@ where // needed. This frees up resources while state root computation continues. let valid_block_tx = handle.terminate_caching(Some(output.clone())); - let block = self.convert_to_block(input)?.with_senders(senders); + let block = convert_to_block(input)?.with_senders(senders); // Wait for the receipt root computation to complete. let receipt_root_bloom = receipt_root_rx @@ -1541,7 +1573,7 @@ where + Clone + 'static, N: NodePrimitives, - V: PayloadValidator, + V: PayloadValidator + Clone, Evm: ConfigureEngineEvm + 'static, Types: PayloadTypes>, { @@ -1595,7 +1627,7 @@ where } /// Enum representing either block or payload being validated. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum BlockOrPayload { /// Payload. Payload(T::ExecutionData), diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 9015e567fa..dc6c4a64fa 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1327,9 +1327,9 @@ where >, EV: PayloadValidatorBuilder, EV::Validator: reth_engine_primitives::PayloadValidator< - ::Payload, - Block = BlockTy, - >, + ::Payload, + Block = BlockTy, + > + Clone, { type EngineValidator = BasicEngineValidator;