feat(primitives-traits): add try_recover_signers for parallel batch recovery (#21103)

This commit is contained in:
Matthias Seitz
2026-01-17 02:24:53 +01:00
committed by GitHub
parent c617d25c36
commit 79b8ffb828
2 changed files with 144 additions and 66 deletions

View File

@@ -1,68 +1,137 @@
//! Helpers for recovering signers from a set of transactions
#[cfg(feature = "rayon")]
pub use rayon::*;
#[cfg(not(feature = "rayon"))]
pub use iter::*;
use crate::{transaction::signed::RecoveryError, Recovered, SignedTransaction};
use alloc::vec::Vec;
use alloy_consensus::transaction::SignerRecoverable;
use alloy_primitives::Address;
#[cfg(feature = "rayon")]
mod rayon {
use crate::{transaction::signed::RecoveryError, SignedTransaction};
use alloc::vec::Vec;
use alloy_primitives::Address;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
/// Recovers a list of signers from a transaction list iterator.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid
pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T>,
{
txes.into_par_iter().map(|tx| tx.recover_signer()).collect()
}
/// Recovers a list of signers from a transaction list iterator _without ensuring that the
/// signature has a low `s` value_.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T>,
{
txes.into_par_iter().map(|tx| tx.recover_signer_unchecked()).collect()
}
/// Recovers a list of signers from a transaction list iterator.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
///
/// When the `rayon` feature is enabled, recovery is performed in parallel.
#[cfg(feature = "rayon")]
pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T>,
{
txes.into_par_iter().map(|tx| tx.recover_signer()).collect()
}
/// Recovers a list of signers from a transaction list iterator.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
#[cfg(not(feature = "rayon"))]
pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoIterator<Item = &'a T>,
{
txes.into_iter().map(|tx| tx.recover_signer()).collect()
}
/// Recovers a list of signers from a transaction list iterator _without ensuring that the
/// signature has a low `s` value_.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
///
/// When the `rayon` feature is enabled, recovery is performed in parallel.
#[cfg(feature = "rayon")]
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T>,
{
txes.into_par_iter().map(|tx| tx.recover_signer_unchecked()).collect()
}
/// Recovers a list of signers from a transaction list iterator _without ensuring that the
/// signature has a low `s` value_.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
#[cfg(not(feature = "rayon"))]
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoIterator<Item = &'a T>,
{
txes.into_iter().map(|tx| tx.recover_signer_unchecked()).collect()
}
/// Trait for items that can be used with [`try_recover_signers`].
#[cfg(feature = "rayon")]
pub trait TryRecoverItems: IntoParallelIterator {}
/// Trait for items that can be used with [`try_recover_signers`].
#[cfg(not(feature = "rayon"))]
pub trait TryRecoverItems: IntoIterator {}
#[cfg(feature = "rayon")]
impl<I: IntoParallelIterator> TryRecoverItems for I {}
#[cfg(not(feature = "rayon"))]
mod iter {
use crate::{transaction::signed::RecoveryError, SignedTransaction};
use alloc::vec::Vec;
use alloy_primitives::Address;
impl<I: IntoIterator> TryRecoverItems for I {}
/// Recovers a list of signers from a transaction list iterator.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid
pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoIterator<Item = &'a T>,
{
txes.into_iter().map(|tx| tx.recover_signer()).collect()
}
/// Trait for decode functions that can be used with [`try_recover_signers`].
#[cfg(feature = "rayon")]
pub trait TryRecoverFn<Item, T>: Fn(Item) -> Result<T, RecoveryError> + Sync {}
/// Recovers a list of signers from a transaction list iterator _without ensuring that the
/// signature has a low `s` value_.
///
/// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where
T: SignedTransaction,
I: IntoIterator<Item = &'a T>,
{
txes.into_iter().map(|tx| tx.recover_signer_unchecked()).collect()
}
/// Trait for decode functions that can be used with [`try_recover_signers`].
#[cfg(not(feature = "rayon"))]
pub trait TryRecoverFn<Item, T>: Fn(Item) -> Result<T, RecoveryError> {}
#[cfg(feature = "rayon")]
impl<Item, T, F: Fn(Item) -> Result<T, RecoveryError> + Sync> TryRecoverFn<Item, T> for F {}
#[cfg(not(feature = "rayon"))]
impl<Item, T, F: Fn(Item) -> Result<T, RecoveryError>> TryRecoverFn<Item, T> for F {}
/// Decodes and recovers a list of [`Recovered`] transactions from an iterator.
///
/// The `decode` closure transforms each item into a [`SignedTransaction`], which is then
/// recovered.
///
/// Returns an error if decoding or signature recovery fails for any transaction.
///
/// When the `rayon` feature is enabled, recovery is performed in parallel.
#[cfg(feature = "rayon")]
pub fn try_recover_signers<I, F, T>(items: I, decode: F) -> Result<Vec<Recovered<T>>, RecoveryError>
where
I: IntoParallelIterator,
F: Fn(I::Item) -> Result<T, RecoveryError> + Sync,
T: SignedTransaction,
{
items
.into_par_iter()
.map(|item| {
let tx = decode(item)?;
SignerRecoverable::try_into_recovered(tx)
})
.collect()
}
/// Decodes and recovers a list of [`Recovered`] transactions from an iterator.
///
/// The `decode` closure transforms each item into a [`SignedTransaction`], which is then
/// recovered.
///
/// Returns an error if decoding or signature recovery fails for any transaction.
#[cfg(not(feature = "rayon"))]
pub fn try_recover_signers<I, F, T>(items: I, decode: F) -> Result<Vec<Recovered<T>>, RecoveryError>
where
I: IntoIterator,
F: Fn(I::Item) -> Result<T, RecoveryError>,
T: SignedTransaction,
{
items
.into_iter()
.map(|item| {
let tx = decode(item)?;
SignerRecoverable::try_into_recovered(tx)
})
.collect()
}

View File

@@ -15,6 +15,7 @@
//! on public-facing RPC endpoints without proper authentication.
use alloy_consensus::{Header, Transaction};
use alloy_eips::eip2718::Decodable2718;
use alloy_evm::Evm;
use alloy_primitives::{map::HashSet, Address, U256};
use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV5;
@@ -24,11 +25,14 @@ use reth_errors::RethError;
use reth_ethereum_engine_primitives::EthBuiltPayload;
use reth_ethereum_primitives::EthPrimitives;
use reth_evm::{execute::BlockBuilder, ConfigureEvm, NextBlockEnvAttributes};
use reth_primitives_traits::{AlloyBlockHeader as BlockTrait, Recovered, TxTy};
use reth_primitives_traits::{
transaction::{recover::try_recover_signers, signed::RecoveryError},
AlloyBlockHeader as BlockTrait, TxTy,
};
use reth_revm::{database::StateProviderDatabase, db::State};
use reth_rpc_api::{TestingApiServer, TestingBuildBlockRequestV1};
use reth_rpc_eth_api::{helpers::Call, FromEthApiError};
use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
use reth_rpc_eth_types::EthApiError;
use reth_storage_api::{BlockReader, HeaderProvider};
use revm::context::Block;
use revm_primitives::map::DefaultHashBuilder;
@@ -106,11 +110,16 @@ where
let mut invalid_senders: HashSet<Address, DefaultHashBuilder> = HashSet::default();
for (idx, tx) in request.transactions.iter().enumerate() {
let tx: Recovered<TxTy<Evm::Primitives>> = recover_raw_transaction(tx)?;
let sender = tx.signer();
// Decode and recover all transactions in parallel
let recovered_txs = try_recover_signers(&request.transactions, |tx| {
TxTy::<Evm::Primitives>::decode_2718_exact(tx.as_ref())
.map_err(RecoveryError::from_source)
})
.or(Err(EthApiError::InvalidTransactionSignature))?;
if skip_invalid_transactions && invalid_senders.contains(&sender) {
for (idx, tx) in recovered_txs.into_iter().enumerate() {
let signer = tx.signer();
if skip_invalid_transactions && invalid_senders.contains(&signer) {
continue;
}
@@ -122,17 +131,17 @@ where
debug!(
target: "rpc::testing",
tx_idx = idx,
?sender,
?signer,
error = ?err,
"Skipping invalid transaction"
);
invalid_senders.insert(sender);
invalid_senders.insert(signer);
continue;
}
debug!(
target: "rpc::testing",
tx_idx = idx,
?sender,
?signer,
error = ?err,
"Transaction execution failed"
);