feat: unify recover fn result type (#13897)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
DevOrbitlabs
2025-01-22 21:58:36 +07:00
committed by GitHub
parent 5170112c1f
commit 926ad2a639
24 changed files with 129 additions and 98 deletions

View File

@ -167,7 +167,7 @@ impl<C: ChainSpecParser<ChainSpec = ChainSpec>> Command<C> {
debug!(target: "reth::cli", bytes = ?tx_bytes, "Decoding transaction"); debug!(target: "reth::cli", bytes = ?tx_bytes, "Decoding transaction");
let transaction = TransactionSigned::decode(&mut &Bytes::from_str(tx_bytes)?[..])? let transaction = TransactionSigned::decode(&mut &Bytes::from_str(tx_bytes)?[..])?
.try_ecrecovered() .try_ecrecovered()
.ok_or_else(|| eyre::eyre!("failed to recover tx"))?; .map_err(|e| eyre::eyre!("failed to recover tx: {e}"))?;
let encoded_length = match &transaction.transaction { let encoded_length = match &transaction.transaction {
Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => { Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {

View File

@ -1,7 +1,6 @@
use alloy_consensus::BlockHeader; use alloy_consensus::BlockHeader;
use alloy_primitives::{keccak256, B256}; use alloy_primitives::{keccak256, B256};
use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_debug::ExecutionWitness;
use eyre::OptionExt;
use pretty_assertions::Comparison; use pretty_assertions::Comparison;
use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_engine_primitives::InvalidBlockHook; use reth_engine_primitives::InvalidBlockHook;
@ -87,7 +86,8 @@ where
// Re-execute all of the transactions in the block to load all touched accounts into // Re-execute all of the transactions in the block to load all touched accounts into
// the cache DB. // the cache DB.
for tx in block.body().transactions() { for tx in block.body().transactions() {
let signer = tx.recover_signer().ok_or_eyre("failed to recover sender")?; let signer =
tx.recover_signer().map_err(|_| eyre::eyre!("failed to recover sender"))?;
evm.transact_commit(self.evm_config.tx_env(tx, signer))?; evm.transact_commit(self.evm_config.tx_env(tx, signer))?;
} }

View File

@ -19,7 +19,7 @@ use once_cell as _;
use once_cell::sync::OnceCell as OnceLock; use once_cell::sync::OnceCell as OnceLock;
use reth_primitives_traits::{ use reth_primitives_traits::{
crypto::secp256k1::{recover_signer, recover_signer_unchecked}, crypto::secp256k1::{recover_signer, recover_signer_unchecked},
transaction::error::TransactionConversionError, transaction::{error::TransactionConversionError, signed::RecoveryError},
FillTxEnv, InMemorySize, SignedTransaction, FillTxEnv, InMemorySize, SignedTransaction,
}; };
use revm_primitives::{AuthorizationList, TxEnv}; use revm_primitives::{AuthorizationList, TxEnv};
@ -719,12 +719,15 @@ impl SignedTransaction for TransactionSigned {
&self.signature &self.signature
} }
fn recover_signer(&self) -> Option<Address> { fn recover_signer(&self) -> Result<Address, RecoveryError> {
let signature_hash = self.signature_hash(); let signature_hash = self.signature_hash();
recover_signer(&self.signature, signature_hash) recover_signer(&self.signature, signature_hash)
} }
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> { fn recover_signer_unchecked_with_buf(
&self,
buf: &mut Vec<u8>,
) -> Result<Address, RecoveryError> {
self.encode_for_signing(buf); self.encode_for_signing(buf);
let signature_hash = keccak256(buf); let signature_hash = keccak256(buf);
recover_signer_unchecked(&self.signature, signature_hash) recover_signer_unchecked(&self.signature, signature_hash)
@ -940,10 +943,10 @@ mod tests {
// recover signer, expect failure // recover signer, expect failure
let hash = *tx.tx_hash(); let hash = *tx.tx_hash();
assert!(recover_signer(signature, hash).is_none()); assert!(recover_signer(signature, hash).is_err());
// use unchecked, ensure it succeeds (the signature is valid if not for EIP-2) // use unchecked, ensure it succeeds (the signature is valid if not for EIP-2)
assert!(recover_signer_unchecked(signature, hash).is_some()); assert!(recover_signer_unchecked(signature, hash).is_ok());
} }
#[test] #[test]
@ -1002,8 +1005,8 @@ mod tests {
let decoded = TransactionSigned::decode_2718(&mut &tx_bytes[..]).unwrap(); let decoded = TransactionSigned::decode_2718(&mut &tx_bytes[..]).unwrap();
assert_eq!( assert_eq!(
decoded.recover_signer(), decoded.recover_signer().unwrap(),
Some(Address::from_str("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5").unwrap()) Address::from_str("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5").unwrap()
); );
} }
@ -1018,8 +1021,10 @@ mod tests {
let decoded = TransactionSigned::decode_2718(&mut raw_tx.as_slice()).unwrap(); let decoded = TransactionSigned::decode_2718(&mut raw_tx.as_slice()).unwrap();
assert!(alloy_consensus::Typed2718::is_eip4844(&decoded)); assert!(alloy_consensus::Typed2718::is_eip4844(&decoded));
let from = decoded.recover_signer(); assert_eq!(
assert_eq!(from, Some(address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2"))); decoded.recover_signer().ok(),
Some(address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2"))
);
let tx = decoded.transaction; let tx = decoded.transaction;
@ -1182,7 +1187,8 @@ mod tests {
let mut pointer = raw.as_ref(); let mut pointer = raw.as_ref();
let tx = TransactionSigned::decode(&mut pointer).unwrap(); let tx = TransactionSigned::decode(&mut pointer).unwrap();
assert_eq!(*tx.tx_hash(), hash, "Expected same hash"); assert_eq!(*tx.tx_hash(), hash, "Expected same hash");
assert_eq!(tx.recover_signer(), Some(signer), "Recovering signer should pass."); let recovered = tx.recover_signer().expect("Recovering signer should pass");
assert_eq!(recovered, signer);
} }
#[test] #[test]
@ -1252,7 +1258,7 @@ mod tests {
let data = hex!("f8ea0c850ba43b7400832dc6c0942935aa0a2d2fbb791622c29eb1c117b65b7a908580b884590528a9000000000000000000000001878ace42092b7f1ae1f28d16c1272b1aa80ca4670000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000d02ab486cedc0000000000000000000000000000000000000000000000000000557fe293cabc08cf1ca05bfaf3fda0a56b49cc78b22125feb5ae6a99d2b4781f00507d8b02c173771c85a0b5da0dbe6c5bc53740d0071fc83eb17ba0f709e49e9ae7df60dee625ef51afc5"); let data = hex!("f8ea0c850ba43b7400832dc6c0942935aa0a2d2fbb791622c29eb1c117b65b7a908580b884590528a9000000000000000000000001878ace42092b7f1ae1f28d16c1272b1aa80ca4670000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000d02ab486cedc0000000000000000000000000000000000000000000000000000557fe293cabc08cf1ca05bfaf3fda0a56b49cc78b22125feb5ae6a99d2b4781f00507d8b02c173771c85a0b5da0dbe6c5bc53740d0071fc83eb17ba0f709e49e9ae7df60dee625ef51afc5");
let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap(); let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap();
let sender = tx.recover_signer(); let sender = tx.recover_signer();
assert!(sender.is_none()); assert!(sender.is_err());
let sender = tx.recover_signer_unchecked().unwrap(); let sender = tx.recover_signer_unchecked().unwrap();
assert_eq!(sender, address!("7e9e359edf0dbacf96a9952fa63092d919b0842b")); assert_eq!(sender, address!("7e9e359edf0dbacf96a9952fa63092d919b0842b"));

View File

@ -26,7 +26,7 @@ use proptest as _;
use reth_primitives_traits::{ use reth_primitives_traits::{
crypto::secp256k1::{recover_signer, recover_signer_unchecked}, crypto::secp256k1::{recover_signer, recover_signer_unchecked},
sync::OnceLock, sync::OnceLock,
transaction::error::TransactionConversionError, transaction::{error::TransactionConversionError, signed::RecoveryError},
InMemorySize, SignedTransaction, InMemorySize, SignedTransaction,
}; };
@ -79,11 +79,11 @@ impl SignedTransaction for OpTransactionSigned {
&self.signature &self.signature
} }
fn recover_signer(&self) -> Option<Address> { fn recover_signer(&self) -> Result<Address, RecoveryError> {
// Optimism's Deposit transaction does not have a signature. Directly return the // Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address. // `from` address.
if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = self.transaction { if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = self.transaction {
return Some(from) return Ok(from)
} }
let Self { transaction, signature, .. } = self; let Self { transaction, signature, .. } = self;
@ -91,11 +91,11 @@ impl SignedTransaction for OpTransactionSigned {
recover_signer(signature, signature_hash) recover_signer(signature, signature_hash)
} }
fn recover_signer_unchecked(&self) -> Option<Address> { fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
// Optimism's Deposit transaction does not have a signature. Directly return the // Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address. // `from` address.
if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = &self.transaction { if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = &self.transaction {
return Some(*from) return Ok(*from)
} }
let Self { transaction, signature, .. } = self; let Self { transaction, signature, .. } = self;
@ -103,11 +103,14 @@ impl SignedTransaction for OpTransactionSigned {
recover_signer_unchecked(signature, signature_hash) recover_signer_unchecked(signature, signature_hash)
} }
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> { fn recover_signer_unchecked_with_buf(
&self,
buf: &mut Vec<u8>,
) -> Result<Address, RecoveryError> {
match &self.transaction { match &self.transaction {
// Optimism's Deposit transaction does not have a signature. Directly return the // Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address. // `from` address.
OpTypedTransaction::Deposit(tx) => return Some(tx.from), OpTypedTransaction::Deposit(tx) => return Ok(tx.from),
OpTypedTransaction::Legacy(tx) => tx.encode_for_signing(buf), OpTypedTransaction::Legacy(tx) => tx.encode_for_signing(buf),
OpTypedTransaction::Eip2930(tx) => tx.encode_for_signing(buf), OpTypedTransaction::Eip2930(tx) => tx.encode_for_signing(buf),
OpTypedTransaction::Eip1559(tx) => tx.encode_for_signing(buf), OpTypedTransaction::Eip1559(tx) => tx.encode_for_signing(buf),

View File

@ -123,11 +123,11 @@ pub trait BlockBody:
} }
/// Recover signer addresses for all transactions in the block body. /// Recover signer addresses for all transactions in the block body.
fn recover_signers(&self) -> Option<Vec<Address>> fn recover_signers(&self) -> Result<Vec<Address>, RecoveryError>
where where
Self::Transaction: SignedTransaction, Self::Transaction: SignedTransaction,
{ {
crate::transaction::recover::recover_signers(self.transactions()) crate::transaction::recover::recover_signers(self.transactions()).map_err(|_| RecoveryError)
} }
/// Recover signer addresses for all transactions in the block body. /// Recover signer addresses for all transactions in the block body.
@ -137,14 +137,14 @@ pub trait BlockBody:
where where
Self::Transaction: SignedTransaction, Self::Transaction: SignedTransaction,
{ {
self.recover_signers().ok_or(RecoveryError) self.recover_signers()
} }
/// Recover signer addresses for all transactions in the block body _without ensuring that the /// Recover signer addresses for all transactions in the block body _without ensuring that the
/// signature has a low `s` value_. /// signature has a low `s` value_.
/// ///
/// Returns `None`, if some transaction's signature is invalid. /// Returns `None`, if some transaction's signature is invalid.
fn recover_signers_unchecked(&self) -> Option<Vec<Address>> fn recover_signers_unchecked(&self) -> Result<Vec<Address>, RecoveryError>
where where
Self::Transaction: SignedTransaction, Self::Transaction: SignedTransaction,
{ {
@ -159,7 +159,7 @@ pub trait BlockBody:
where where
Self::Transaction: SignedTransaction, Self::Transaction: SignedTransaction,
{ {
self.recover_signers_unchecked().ok_or(RecoveryError) self.recover_signers_unchecked()
} }
} }

View File

@ -16,8 +16,8 @@ use alloy_primitives::{Address, B256};
use alloy_rlp::{Decodable, Encodable}; use alloy_rlp::{Decodable, Encodable};
use crate::{ use crate::{
BlockBody, BlockHeader, FullBlockBody, FullBlockHeader, InMemorySize, MaybeSerde, SealedHeader, transaction::signed::RecoveryError, BlockBody, BlockHeader, FullBlockBody, FullBlockHeader,
SignedTransaction, InMemorySize, MaybeSerde, SealedHeader, SignedTransaction,
}; };
/// Bincode-compatible header type serde implementations. /// Bincode-compatible header type serde implementations.
@ -121,7 +121,7 @@ pub trait Block:
} }
/// Expensive operation that recovers transaction signer. /// Expensive operation that recovers transaction signer.
fn senders(&self) -> Option<Vec<Address>> fn senders(&self) -> Result<Vec<Address>, RecoveryError>
where where
<Self::Body as BlockBody>::Transaction: SignedTransaction, <Self::Body as BlockBody>::Transaction: SignedTransaction,
{ {
@ -158,10 +158,10 @@ pub trait Block:
let senders = if self.body().transactions().len() == senders.len() { let senders = if self.body().transactions().len() == senders.len() {
senders senders
} else { } else {
let Some(senders) = self.body().recover_signers_unchecked() else { return Err(self) }; // Fall back to recovery if lengths don't match
let Ok(senders) = self.body().recover_signers_unchecked() else { return Err(self) };
senders senders
}; };
Ok(RecoveredBlock::new_unhashed(self, senders)) Ok(RecoveredBlock::new_unhashed(self, senders))
} }
@ -173,7 +173,7 @@ pub trait Block:
where where
<Self::Body as BlockBody>::Transaction: SignedTransaction, <Self::Body as BlockBody>::Transaction: SignedTransaction,
{ {
let senders = self.senders()?; let senders = self.body().recover_signers().ok()?;
Some(RecoveredBlock::new_unhashed(self, senders)) Some(RecoveredBlock::new_unhashed(self, senders))
} }
} }

View File

@ -2,6 +2,7 @@
use crate::{ use crate::{
block::{error::BlockRecoveryError, RecoveredBlock}, block::{error::BlockRecoveryError, RecoveredBlock},
transaction::signed::RecoveryError,
Block, BlockBody, GotExpected, InMemorySize, SealedHeader, Block, BlockBody, GotExpected, InMemorySize, SealedHeader,
}; };
use alloc::vec::Vec; use alloc::vec::Vec;
@ -179,7 +180,7 @@ impl<B: Block> SealedBlock<B> {
/// Recovers all senders from the transactions in the block. /// Recovers all senders from the transactions in the block.
/// ///
/// Returns `None` if any of the transactions fail to recover the sender. /// Returns `None` if any of the transactions fail to recover the sender.
pub fn senders(&self) -> Option<Vec<Address>> { pub fn senders(&self) -> Result<Vec<Address>, RecoveryError> {
self.body().recover_signers() self.body().recover_signers()
} }

View File

@ -22,6 +22,7 @@ pub mod secp256k1 {
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
use super::impl_secp256k1 as imp; use super::impl_secp256k1 as imp;
use crate::transaction::signed::RecoveryError;
pub use imp::{public_key_to_address, sign_message}; pub use imp::{public_key_to_address, sign_message};
/// Recover signer from message hash, _without ensuring that the signature has a low `s` /// Recover signer from message hash, _without ensuring that the signature has a low `s`
@ -30,7 +31,10 @@ pub mod secp256k1 {
/// Using this for signature validation will succeed, even if the signature is malleable or not /// Using this for signature validation will succeed, even if the signature is malleable or not
/// compliant with EIP-2. This is provided for compatibility with old signatures which have /// compliant with EIP-2. This is provided for compatibility with old signatures which have
/// large `s` values. /// large `s` values.
pub fn recover_signer_unchecked(signature: &Signature, hash: B256) -> Option<Address> { pub fn recover_signer_unchecked(
signature: &Signature,
hash: B256,
) -> Result<Address, RecoveryError> {
let mut sig: [u8; 65] = [0; 65]; let mut sig: [u8; 65] = [0; 65];
sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>()); sig[0..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
@ -39,7 +43,7 @@ pub mod secp256k1 {
// NOTE: we are removing error from underlying crypto library as it will restrain primitive // NOTE: we are removing error from underlying crypto library as it will restrain primitive
// errors and we care only if recovery is passing or not. // errors and we care only if recovery is passing or not.
imp::recover_signer_unchecked(&sig, &hash.0).ok() imp::recover_signer_unchecked(&sig, &hash.0).map_err(|_| RecoveryError)
} }
/// Recover signer address from message hash. This ensures that the signature S value is /// Recover signer address from message hash. This ensures that the signature S value is
@ -47,11 +51,10 @@ pub mod secp256k1 {
/// [EIP-2](https://eips.ethereum.org/EIPS/eip-2). /// [EIP-2](https://eips.ethereum.org/EIPS/eip-2).
/// ///
/// If the S value is too large, then this will return `None` /// If the S value is too large, then this will return `None`
pub fn recover_signer(signature: &Signature, hash: B256) -> Option<Address> { pub fn recover_signer(signature: &Signature, hash: B256) -> Result<Address, RecoveryError> {
if signature.s() > SECP256K1N_HALF { if signature.s() > SECP256K1N_HALF {
return None return Err(RecoveryError)
} }
recover_signer_unchecked(signature, hash) recover_signer_unchecked(signature, hash)
} }
} }

View File

@ -8,7 +8,7 @@ pub use iter::*;
#[cfg(feature = "rayon")] #[cfg(feature = "rayon")]
mod rayon { mod rayon {
use crate::SignedTransaction; use crate::{transaction::signed::RecoveryError, SignedTransaction};
use alloc::vec::Vec; use alloc::vec::Vec;
use alloy_primitives::Address; use alloy_primitives::Address;
use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use rayon::prelude::{IntoParallelIterator, ParallelIterator};
@ -16,7 +16,7 @@ mod rayon {
/// Recovers a list of signers from a transaction list iterator. /// Recovers a list of signers from a transaction list iterator.
/// ///
/// Returns `None`, if some transaction's signature is invalid /// Returns `None`, if some transaction's signature is invalid
pub fn recover_signers<'a, I, T>(txes: I) -> Option<Vec<Address>> pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where where
T: SignedTransaction, T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send, I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send,
@ -28,7 +28,7 @@ mod rayon {
/// signature has a low `s` value_. /// signature has a low `s` value_.
/// ///
/// Returns `None`, if some transaction's signature is invalid. /// Returns `None`, if some transaction's signature is invalid.
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Option<Vec<Address>> pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where where
T: SignedTransaction, T: SignedTransaction,
I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send, I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send,
@ -39,14 +39,14 @@ mod rayon {
#[cfg(not(feature = "rayon"))] #[cfg(not(feature = "rayon"))]
mod iter { mod iter {
use crate::SignedTransaction; use crate::{transaction::signed::RecoveryError, SignedTransaction};
use alloc::vec::Vec; use alloc::vec::Vec;
use alloy_primitives::Address; use alloy_primitives::Address;
/// Recovers a list of signers from a transaction list iterator. /// Recovers a list of signers from a transaction list iterator.
/// ///
/// Returns `None`, if some transaction's signature is invalid /// Returns `Err(RecoveryError)`, if some transaction's signature is invalid
pub fn recover_signers<'a, I, T>(txes: I) -> Option<Vec<Address>> pub fn recover_signers<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where where
T: SignedTransaction, T: SignedTransaction,
I: IntoIterator<Item = &'a T> + IntoIterator<Item = &'a T>, I: IntoIterator<Item = &'a T> + IntoIterator<Item = &'a T>,
@ -57,8 +57,8 @@ mod iter {
/// Recovers a list of signers from a transaction list iterator _without ensuring that the /// Recovers a list of signers from a transaction list iterator _without ensuring that the
/// signature has a low `s` value_. /// signature has a low `s` value_.
/// ///
/// Returns `None`, if some transaction's signature is invalid. /// Returns `Err(RecoveryError)`, if some transaction's signature is invalid.
pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Option<Vec<Address>> pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result<Vec<Address>, RecoveryError>
where where
T: SignedTransaction, T: SignedTransaction,
I: IntoIterator<Item = &'a T> + IntoIterator<Item = &'a T>, I: IntoIterator<Item = &'a T> + IntoIterator<Item = &'a T>,

View File

@ -62,13 +62,13 @@ pub trait SignedTransaction:
/// This can fail for some early ethereum mainnet transactions pre EIP-2, use /// This can fail for some early ethereum mainnet transactions pre EIP-2, use
/// [`Self::recover_signer_unchecked`] if you want to recover the signer without ensuring that /// [`Self::recover_signer_unchecked`] if you want to recover the signer without ensuring that
/// the signature has a low `s` value. /// the signature has a low `s` value.
fn recover_signer(&self) -> Option<Address>; fn recover_signer(&self) -> Result<Address, RecoveryError>;
/// Recover signer from signature and hash. /// Recover signer from signature and hash.
/// ///
/// Returns an error if the transaction's signature is invalid. /// Returns an error if the transaction's signature is invalid.
fn try_recover(&self) -> Result<Address, RecoveryError> { fn try_recover(&self) -> Result<Address, RecoveryError> {
self.recover_signer().ok_or(RecoveryError) self.recover_signer().map_err(|_| RecoveryError)
} }
/// Recover signer from signature and hash _without ensuring that the signature has a low `s` /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
@ -76,8 +76,8 @@ pub trait SignedTransaction:
/// ///
/// Returns `None` if the transaction's signature is invalid, see also /// Returns `None` if the transaction's signature is invalid, see also
/// `reth_primitives::transaction::recover_signer_unchecked`. /// `reth_primitives::transaction::recover_signer_unchecked`.
fn recover_signer_unchecked(&self) -> Option<Address> { fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
self.recover_signer_unchecked_with_buf(&mut Vec::new()) self.recover_signer_unchecked_with_buf(&mut Vec::new()).map_err(|_| RecoveryError)
} }
/// Recover signer from signature and hash _without ensuring that the signature has a low `s` /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
@ -85,12 +85,15 @@ pub trait SignedTransaction:
/// ///
/// Returns an error if the transaction's signature is invalid. /// Returns an error if the transaction's signature is invalid.
fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> { fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> {
self.recover_signer_unchecked().ok_or(RecoveryError) self.recover_signer_unchecked()
} }
/// Same as [`Self::recover_signer_unchecked`] but receives a buffer to operate on. This is used /// Same as [`Self::recover_signer_unchecked`] but receives a buffer to operate on. This is used
/// during batch recovery to avoid allocating a new buffer for each transaction. /// during batch recovery to avoid allocating a new buffer for each transaction.
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address>; fn recover_signer_unchecked_with_buf(
&self,
buf: &mut Vec<u8>,
) -> Result<Address, RecoveryError>;
/// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with
/// tx type. /// tx type.
@ -120,12 +123,15 @@ impl SignedTransaction for PooledTransaction {
} }
} }
fn recover_signer(&self) -> Option<Address> { fn recover_signer(&self) -> Result<Address, RecoveryError> {
let signature_hash = self.signature_hash(); let signature_hash = self.signature_hash();
recover_signer(self.signature(), signature_hash) recover_signer(self.signature(), signature_hash)
} }
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> { fn recover_signer_unchecked_with_buf(
&self,
buf: &mut Vec<u8>,
) -> Result<Address, RecoveryError> {
match self { match self {
Self::Legacy(tx) => tx.tx().encode_for_signing(buf), Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
Self::Eip2930(tx) => tx.tx().encode_for_signing(buf), Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
@ -158,12 +164,15 @@ impl SignedTransaction for op_alloy_consensus::OpPooledTransaction {
} }
} }
fn recover_signer(&self) -> Option<Address> { fn recover_signer(&self) -> Result<Address, RecoveryError> {
let signature_hash = self.signature_hash(); let signature_hash = self.signature_hash();
recover_signer(self.signature(), signature_hash) recover_signer(self.signature(), signature_hash)
} }
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> { fn recover_signer_unchecked_with_buf(
&self,
buf: &mut Vec<u8>,
) -> Result<Address, RecoveryError> {
match self { match self {
Self::Legacy(tx) => tx.tx().encode_for_signing(buf), Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
Self::Eip2930(tx) => tx.tx().encode_for_signing(buf), Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
@ -178,9 +187,8 @@ impl SignedTransaction for op_alloy_consensus::OpPooledTransaction {
/// Extension trait for [`SignedTransaction`] to convert it into [`Recovered`]. /// Extension trait for [`SignedTransaction`] to convert it into [`Recovered`].
pub trait SignedTransactionIntoRecoveredExt: SignedTransaction { pub trait SignedTransactionIntoRecoveredExt: SignedTransaction {
/// Tries to recover signer and return [`Recovered`] by cloning the type. /// Tries to recover signer and return [`Recovered`] by cloning the type.
fn try_ecrecovered(&self) -> Option<Recovered<Self>> { fn try_ecrecovered(&self) -> Result<Recovered<Self>, RecoveryError> {
let signer = self.recover_signer()?; self.recover_signer().map(|signer| Recovered::new_unchecked(self.clone(), signer))
Some(Recovered::new_unchecked(self.clone(), signer))
} }
/// Tries to recover signer and return [`Recovered`]. /// Tries to recover signer and return [`Recovered`].
@ -189,8 +197,8 @@ pub trait SignedTransactionIntoRecoveredExt: SignedTransaction {
/// [`SignedTransaction::recover_signer`]. /// [`SignedTransaction::recover_signer`].
fn try_into_ecrecovered(self) -> Result<Recovered<Self>, Self> { fn try_into_ecrecovered(self) -> Result<Recovered<Self>, Self> {
match self.recover_signer() { match self.recover_signer() {
None => Err(self), Ok(signer) => Ok(Recovered::new_unchecked(self, signer)),
Some(signer) => Ok(Recovered::new_unchecked(self, signer)), Err(_) => Err(self),
} }
} }
@ -198,9 +206,8 @@ pub trait SignedTransactionIntoRecoveredExt: SignedTransaction {
/// ensuring that the signature has a low `s` value_ (EIP-2). /// ensuring that the signature has a low `s` value_ (EIP-2).
/// ///
/// Returns `None` if the transaction's signature is invalid. /// Returns `None` if the transaction's signature is invalid.
fn into_ecrecovered_unchecked(self) -> Option<Recovered<Self>> { fn into_ecrecovered_unchecked(self) -> Result<Recovered<Self>, RecoveryError> {
let signer = self.recover_signer_unchecked()?; self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self, signer))
Some(Recovered::new_unchecked(self, signer))
} }
/// Returns the [`Recovered`] transaction with the given sender. /// Returns the [`Recovered`] transaction with the given sender.

View File

@ -13,7 +13,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let raw =hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2"); let raw =hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2");
let mut pointer = raw.as_ref(); let mut pointer = raw.as_ref();
let tx = TransactionSigned::decode(&mut pointer).unwrap(); let tx = TransactionSigned::decode(&mut pointer).unwrap();
tx.recover_signer(); tx.recover_signer().unwrap();
} }
) )
}); });

View File

@ -486,7 +486,7 @@ pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
// check for pre EIP-2 because this transaction could be pre-EIP-2. // check for pre EIP-2 because this transaction could be pre-EIP-2.
let transaction = tx let transaction = tx
.into_ecrecovered_unchecked() .into_ecrecovered_unchecked()
.ok_or(EthApiError::InvalidTransactionSignature)?; .map_err(|_| EthApiError::InvalidTransactionSignature)?;
let tx = TransactionSource::Block { let tx = TransactionSource::Block {
transaction, transaction,

View File

@ -10,6 +10,7 @@ use alloy_primitives::{Address, Bytes, U256};
use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError}; use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError};
use alloy_sol_types::decode_revert_reason; use alloy_sol_types::decode_revert_reason;
use reth_errors::RethError; use reth_errors::RethError;
use reth_primitives_traits::transaction::signed::RecoveryError;
use reth_rpc_server_types::result::{ use reth_rpc_server_types::result::{
block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code, block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
}; };
@ -285,6 +286,12 @@ where
} }
} }
impl From<RecoveryError> for EthApiError {
fn from(_: RecoveryError) -> Self {
Self::InvalidTransactionSignature
}
}
/// An error due to invalid transaction. /// An error due to invalid transaction.
/// ///
/// The only reason this exists is to maintain compatibility with other clients de-facto standard /// The only reason this exists is to maintain compatibility with other clients de-facto standard

View File

@ -251,7 +251,7 @@ where
} }
// check if the sender was the coinbase, if so, ignore // check if the sender was the coinbase, if so, ignore
if let Some(sender) = tx.recover_signer() { if let Ok(sender) = tx.recover_signer() {
if sender == block.beneficiary() { if sender == block.beneficiary() {
continue continue
} }

View File

@ -1,6 +1,6 @@
//! RPC receipt response builder, extends a layer one receipt with layer two data. //! RPC receipt response builder, extends a layer one receipt with layer two data.
use super::{EthApiError, EthResult}; use super::EthResult;
use alloy_consensus::{transaction::TransactionMeta, ReceiptEnvelope, TxReceipt}; use alloy_consensus::{transaction::TransactionMeta, ReceiptEnvelope, TxReceipt};
use alloy_primitives::{Address, TxKind}; use alloy_primitives::{Address, TxKind};
use alloy_rpc_types_eth::{Log, ReceiptWithBloom, TransactionReceipt}; use alloy_rpc_types_eth::{Log, ReceiptWithBloom, TransactionReceipt};
@ -21,8 +21,7 @@ where
{ {
// Note: we assume this transaction is valid, because it's mined (or part of pending block) // Note: we assume this transaction is valid, because it's mined (or part of pending block)
// and we don't need to check for pre EIP-2 // and we don't need to check for pre EIP-2
let from = let from = transaction.recover_signer_unchecked()?;
transaction.recover_signer_unchecked().ok_or(EthApiError::InvalidTransactionSignature)?;
// get the previous transaction cumulative gas used // get the previous transaction cumulative gas used
let gas_used = if meta.index == 0 { let gas_used = if meta.index == 0 {

View File

@ -166,23 +166,19 @@ where
.body() .body()
.transactions() .transactions()
.iter() .iter()
.map(|tx| { .map(|tx| tx.recover_signer().map_err(Eth::Error::from_eth_err))
tx.recover_signer() .collect::<Result<Vec<_>, _>>()?
.ok_or(EthApiError::InvalidTransactionSignature) .into_iter()
.map_err(Eth::Error::from_eth_err) .collect()
})
.collect::<Result<Vec<_>, Eth::Error>>()?
} else { } else {
block block
.body() .body()
.transactions() .transactions()
.iter() .iter()
.map(|tx| { .map(|tx| tx.recover_signer_unchecked().map_err(Eth::Error::from_eth_err))
tx.recover_signer_unchecked() .collect::<Result<Vec<_>, _>>()?
.ok_or(EthApiError::InvalidTransactionSignature) .into_iter()
.map_err(Eth::Error::from_eth_err) .collect()
})
.collect::<Result<Vec<_>, Eth::Error>>()?
}; };
self.trace_block(Arc::new(block.with_senders_unchecked(senders)), evm_env, opts).await self.trace_block(Arc::new(block.with_senders_unchecked(senders)), evm_env, opts).await

View File

@ -313,9 +313,9 @@ fn recover_sender<T: SignedTransaction>(
// value is greater than `secp256k1n / 2` if past EIP-2. There are transactions // value is greater than `secp256k1n / 2` if past EIP-2. There are transactions
// pre-homestead which have large `s` values, so using [Signature::recover_signer] here // pre-homestead which have large `s` values, so using [Signature::recover_signer] here
// would not be backwards-compatible. // would not be backwards-compatible.
let sender = tx let sender = tx.recover_signer_unchecked_with_buf(rlp_buf).map_err(|_| {
.recover_signer_unchecked_with_buf(rlp_buf) SenderRecoveryStageError::FailedRecovery(FailedSenderRecoveryError { tx: tx_id })
.ok_or(SenderRecoveryStageError::FailedRecovery(FailedSenderRecoveryError { tx: tx_id }))?; })?;
Ok((tx_id, sender)) Ok((tx_id, sender))
} }

View File

@ -3,7 +3,7 @@ use alloc::{boxed::Box, string::String};
use alloy_eips::{BlockHashOrNumber, HashOrNumber}; use alloy_eips::{BlockHashOrNumber, HashOrNumber};
use alloy_primitives::{Address, BlockHash, BlockNumber, TxNumber, B256}; use alloy_primitives::{Address, BlockHash, BlockNumber, TxNumber, B256};
use derive_more::Display; use derive_more::Display;
use reth_primitives_traits::GotExpected; use reth_primitives_traits::{transaction::signed::RecoveryError, GotExpected};
use reth_prune_types::PruneSegmentError; use reth_prune_types::PruneSegmentError;
use reth_static_file_types::StaticFileSegment; use reth_static_file_types::StaticFileSegment;
@ -151,6 +151,12 @@ impl From<alloy_rlp::Error> for ProviderError {
} }
} }
impl From<RecoveryError> for ProviderError {
fn from(_: RecoveryError) -> Self {
Self::SenderRecoveryError
}
}
/// A root mismatch error at a given block height. /// A root mismatch error at a given block height.
#[derive(Clone, Debug, PartialEq, Eq, Display)] #[derive(Clone, Debug, PartialEq, Eq, Display)]
#[display("root mismatch at #{block_number} ({block_hash}): {root}")] #[display("root mismatch at #{block_number} ({block_hash}): {root}")]

View File

@ -2640,7 +2640,7 @@ mod tests {
transaction_sender, transaction_sender,
|block: &SealedBlock, tx_num: TxNumber, _: B256, _: &Vec<Vec<Receipt>>| ( |block: &SealedBlock, tx_num: TxNumber, _: B256, _: &Vec<Vec<Receipt>>| (
tx_num, tx_num,
block.body().transactions[test_tx_index].recover_signer() block.body().transactions[test_tx_index].recover_signer().ok()
), ),
u64::MAX u64::MAX
), ),

View File

@ -693,9 +693,7 @@ impl<TX: DbTx + 'static, N: NodeTypesForProvider> DatabaseProvider<TX, N> {
match known_senders.get(&tx_num) { match known_senders.get(&tx_num) {
None => { None => {
// recover the sender from the transaction if not found // recover the sender from the transaction if not found
let sender = tx let sender = tx.recover_signer_unchecked()?;
.recover_signer_unchecked()
.ok_or(ProviderError::SenderRecoveryError)?;
senders.push(sender); senders.push(sender);
} }
Some(sender) => senders.push(*sender), Some(sender) => senders.push(*sender),

View File

@ -299,15 +299,14 @@ impl<N: NodePrimitives<SignedTx: Decompress + SignedTransaction>> TransactionsPr
range: impl RangeBounds<TxNumber>, range: impl RangeBounds<TxNumber>,
) -> ProviderResult<Vec<Address>> { ) -> ProviderResult<Vec<Address>> {
let txs = self.transactions_by_tx_range(range)?; let txs = self.transactions_by_tx_range(range)?;
reth_primitives_traits::transaction::recover::recover_signers(&txs) Ok(reth_primitives_traits::transaction::recover::recover_signers(&txs)?)
.ok_or(ProviderError::SenderRecoveryError)
} }
fn transaction_sender(&self, num: TxNumber) -> ProviderResult<Option<Address>> { fn transaction_sender(&self, num: TxNumber) -> ProviderResult<Option<Address>> {
Ok(self Ok(self
.cursor()? .cursor()?
.get_one::<TransactionMask<Self::Transaction>>(num.into())? .get_one::<TransactionMask<Self::Transaction>>(num.into())?
.and_then(|tx| tx.recover_signer())) .and_then(|tx| tx.recover_signer().ok()))
} }
} }

View File

@ -1568,12 +1568,14 @@ impl<N: NodePrimitives<SignedTx: Decompress + SignedTransaction>> TransactionsPr
range: impl RangeBounds<TxNumber>, range: impl RangeBounds<TxNumber>,
) -> ProviderResult<Vec<Address>> { ) -> ProviderResult<Vec<Address>> {
let txes = self.transactions_by_tx_range(range)?; let txes = self.transactions_by_tx_range(range)?;
reth_primitives_traits::transaction::recover::recover_signers(&txes) Ok(reth_primitives_traits::transaction::recover::recover_signers(&txes)?)
.ok_or(ProviderError::SenderRecoveryError)
} }
fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> { fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
Ok(self.transaction_by_id_unhashed(id)?.and_then(|tx| tx.recover_signer())) match self.transaction_by_id_unhashed(id)? {
Some(tx) => Ok(tx.recover_signer().ok()),
None => Ok(None),
}
} }
} }

View File

@ -368,7 +368,11 @@ impl TransactionsProvider for MockEthProvider {
.flat_map(|block| &block.body.transactions) .flat_map(|block| &block.body.transactions)
.enumerate() .enumerate()
.filter_map(|(tx_number, tx)| { .filter_map(|(tx_number, tx)| {
range.contains(&(tx_number as TxNumber)).then(|| tx.recover_signer()).flatten() if range.contains(&(tx_number as TxNumber)) {
tx.recover_signer().ok()
} else {
None
}
}) })
.collect(); .collect();

View File

@ -577,7 +577,7 @@ where
let pool_transactions = txs_signed let pool_transactions = txs_signed
.into_iter() .into_iter()
.filter_map(|tx| tx.try_ecrecovered()) .filter_map(|tx| tx.try_ecrecovered().ok())
.filter_map(|tx| { .filter_map(|tx| {
// Filter out errors // Filter out errors
<P::Transaction as PoolTransaction>::try_from_consensus(tx).ok() <P::Transaction as PoolTransaction>::try_from_consensus(tx).ok()