From a0b60b7e6413f08d0bf41658bffb1ba2d37df96d Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 11 Feb 2026 18:48:17 -0500 Subject: [PATCH] feat(evm): impl ExecutableTxTuple for Either via EitherTxIterator (#22102) Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Amp --- crates/engine/primitives/src/lib.rs | 2 +- .../tree/src/tree/payload_processor/mod.rs | 8 +- .../engine/tree/src/tree/payload_validator.rs | 32 ++---- crates/evm/evm/src/engine.rs | 101 +++++++++++++++++- crates/evm/evm/src/lib.rs | 2 +- 5 files changed, 112 insertions(+), 33 deletions(-) diff --git a/crates/engine/primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs index e97cb3b104..38e7733bf1 100644 --- a/crates/engine/primitives/src/lib.rs +++ b/crates/engine/primitives/src/lib.rs @@ -23,7 +23,7 @@ use serde::{de::DeserializeOwned, Serialize}; // Re-export [`ExecutionPayload`] moved to `reth_payload_primitives` #[cfg(feature = "std")] -pub use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator, ExecutableTxTuple}; +pub use reth_evm::{ConfigureEngineEvm, ConvertTx, ExecutableTxIterator, ExecutableTxTuple}; pub use reth_payload_primitives::ExecutionPayload; mod error; diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 761f89d2f5..5d25f67383 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -23,8 +23,8 @@ use rayon::prelude::*; use reth_evm::{ block::ExecutableTxParts, execute::{ExecutableTxFor, WithTxEnv}, - ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, SpecFor, - TxEnvFor, + ConfigureEvm, ConvertTx, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, + SpecFor, TxEnvFor, }; use reth_metrics::Metrics; use reth_primitives_traits::NodePrimitives; @@ -370,9 +370,9 @@ where // Spawn a task that `convert`s all transactions in parallel and sends them out-of-order. rayon::spawn(move || { - let (transactions, convert) = transactions.into(); + let (transactions, convert) = transactions.into_parts(); transactions.into_par_iter().enumerate().for_each_with(ooo_tx, |ooo_tx, (idx, tx)| { - let tx = convert(tx); + let tx = convert.convert(tx); let tx = tx.map(|tx| { let (tx_env, tx) = tx.into_parts(); WithTxEnv { tx_env, tx: Arc::new(tx) } diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index e0dcec4205..72f4a56292 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -17,7 +17,6 @@ use alloy_evm::Evm; use alloy_primitives::B256; use crate::tree::payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle}; -use rayon::prelude::*; use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock, LazyOverlay}; use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom}; use reth_engine_primitives::{ @@ -232,35 +231,20 @@ where V: PayloadValidator, Evm: ConfigureEngineEvm, { - match input { + Ok(match input { BlockOrPayload::Payload(payload) => { - let (iter, convert) = self + let iter = self .evm_config .tx_iterator_for_payload(payload) - .map_err(NewPayloadError::other)? - .into(); - - let iter = Either::Left(iter.into_par_iter().map(Either::Left)); - let convert = move |tx| { - let Either::Left(tx) = tx else { unreachable!() }; - convert(tx).map(Either::Left).map_err(Either::Left) - }; - - // Box the closure to satisfy the `Fn` bound both here and in the branch below - Ok((iter, Box::new(convert) as Box _ + Send + Sync + 'static>)) + .map_err(NewPayloadError::other)?; + Either::Left(iter) } BlockOrPayload::Block(block) => { - let iter = Either::Right( - block.body().clone_transactions().into_par_iter().map(Either::Right), - ); - let convert = move |tx: Either<_, N::SignedTx>| { - let Either::Right(tx) = tx else { unreachable!() }; - tx.try_into_recovered().map(Either::Right).map_err(Either::Right) - }; - - Ok((iter, Box::new(convert))) + let txs = block.body().clone_transactions(); + let convert = |tx: N::SignedTx| tx.try_into_recovered(); + Either::Right((txs, convert)) } - } + }) } /// Returns a [`ExecutionCtxFor`] for the given payload or block. diff --git a/crates/evm/evm/src/engine.rs b/crates/evm/evm/src/engine.rs index 13f802c27a..761a2eea07 100644 --- a/crates/evm/evm/src/engine.rs +++ b/crates/evm/evm/src/engine.rs @@ -1,4 +1,5 @@ use crate::{execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor, TxEnvFor}; +use alloy_consensus::transaction::Either; use alloy_evm::{block::ExecutableTxParts, RecoveredTx}; use rayon::prelude::*; use reth_primitives_traits::TxTy; @@ -21,10 +22,55 @@ pub trait ConfigureEngineEvm: ConfigureEvm { ) -> Result, Self::Error>; } +/// Converts a raw transaction into an executable transaction. +/// +/// This trait abstracts the conversion logic (e.g., decoding, signature recovery) that is +/// parallelized in the engine. +pub trait ConvertTx: Send + Sync + 'static { + /// The executable transaction type. + type Tx; + /// Errors that may occur during conversion. + type Error; + /// Converts a raw transaction. + fn convert(&self, raw: RawTx) -> Result; +} + +// Blanket impl so closures still work. +impl ConvertTx for F +where + F: Fn(RawTx) -> Result + Send + Sync + 'static, +{ + type Tx = Tx; + type Error = Err; + fn convert(&self, raw: RawTx) -> Result { + self(raw) + } +} + +impl ConvertTx> for Either +where + A: ConvertTx, + B: ConvertTx, +{ + type Tx = Either; + type Error = Either; + fn convert(&self, raw: Either) -> Result { + match (self, raw) { + (Self::Left(a), Either::Left(raw)) => { + a.convert(raw).map(Either::Left).map_err(Either::Left) + } + (Self::Right(b), Either::Right(raw)) => { + b.convert(raw).map(Either::Right).map_err(Either::Right) + } + _ => unreachable!(), + } + } +} + /// A helper trait representing a pair of a "raw" transactions iterator and a closure that can be /// used to convert them to an executable transaction. This tuple is used in the engine to /// parallelize heavy work like decoding or recovery. -pub trait ExecutableTxTuple: Into<(Self::IntoIter, Self::Convert)> + Send + 'static { +pub trait ExecutableTxTuple: Send + 'static { /// Raw transaction that can be converted to an [`ExecutableTxTuple::Tx`] /// /// This can be any type that can be converted to an [`ExecutableTxTuple::Tx`]. For example, @@ -39,10 +85,13 @@ pub trait ExecutableTxTuple: Into<(Self::IntoIter, Self::Convert)> + Send + 'sta type IntoIter: IntoParallelIterator + Send + 'static; - /// Closure that can be used to convert a [`ExecutableTxTuple::RawTx`] to a + /// Converter that can be used to convert a [`ExecutableTxTuple::RawTx`] to a /// [`ExecutableTxTuple::Tx`]. This might involve heavy work like decoding or recovery /// and will be parallelized in the engine. - type Convert: Fn(Self::RawTx) -> Result + Send + Sync + 'static; + type Convert: ConvertTx; + + /// Decomposes into the raw transaction iterator and converter. + fn into_parts(self) -> (Self::IntoIter, Self::Convert); } impl ExecutableTxTuple for (I, F) @@ -59,6 +108,10 @@ where type IntoIter = I; type Convert = F; + + fn into_parts(self) -> (I, F) { + self + } } /// Iterator over executable transactions. @@ -76,3 +129,45 @@ where { type Recovered = , TxTy>>::Recovered; } + +impl ExecutableTxTuple for Either { + type RawTx = Either; + type Tx = Either; + type Error = Either; + type IntoIter = Either< + rayon::iter::Map< + ::Iter, + fn(A::RawTx) -> Either, + >, + rayon::iter::Map< + ::Iter, + fn(B::RawTx) -> Either, + >, + >; + type Convert = Either; + + fn into_parts(self) -> (Self::IntoIter, Self::Convert) { + match self { + Self::Left(a) => { + let (iter, convert) = a.into_parts(); + ( + Either::Left( + iter.into_par_iter() + .map(Either::Left as fn(A::RawTx) -> Either), + ), + Either::Left(convert), + ) + } + Self::Right(b) => { + let (iter, convert) = b.into_parts(); + ( + Either::Right( + iter.into_par_iter() + .map(Either::Right as fn(B::RawTx) -> Either), + ), + Either::Right(convert), + ) + } + } + } +} diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index bf5ca7ae1d..f6acb1d5c3 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -47,7 +47,7 @@ pub use aliases::*; #[cfg(feature = "std")] mod engine; #[cfg(feature = "std")] -pub use engine::{ConfigureEngineEvm, ExecutableTxIterator, ExecutableTxTuple}; +pub use engine::{ConfigureEngineEvm, ConvertTx, ExecutableTxIterator, ExecutableTxTuple}; #[cfg(feature = "metrics")] pub mod metrics;