reth_primitives_traits/transaction/
signed.rs

1//! API of a signed transaction.
2
3use crate::{InMemorySize, MaybeCompact, MaybeSerde, MaybeSerdeBincodeCompat};
4use alloc::fmt;
5use alloy_consensus::{
6    transaction::{Recovered, RlpEcdsaEncodableTx, SignerRecoverable},
7    EthereumTxEnvelope, SignableTransaction,
8};
9use alloy_eips::eip2718::{Decodable2718, Encodable2718};
10use alloy_primitives::{keccak256, Address, Signature, TxHash, B256};
11use alloy_rlp::{Decodable, Encodable};
12use core::hash::Hash;
13
14pub use alloy_consensus::crypto::RecoveryError;
15
16/// Helper trait that unifies all behaviour required by block to support full node operations.
17pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {}
18impl<T> FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {}
19
20/// A signed transaction.
21///
22/// # Recovery Methods
23///
24/// This trait provides two types of recovery methods:
25/// - Standard methods (e.g., `try_recover`) - enforce EIP-2 low-s signature requirement
26/// - Unchecked methods (e.g., `try_recover_unchecked`) - skip EIP-2 validation for pre-EIP-2
27///   transactions
28///
29/// Use unchecked methods only when dealing with historical pre-EIP-2 transactions.
30#[auto_impl::auto_impl(&, Arc)]
31pub trait SignedTransaction:
32    Send
33    + Sync
34    + Unpin
35    + Clone
36    + fmt::Debug
37    + PartialEq
38    + Eq
39    + Hash
40    + Encodable
41    + Decodable
42    + Encodable2718
43    + Decodable2718
44    + alloy_consensus::Transaction
45    + MaybeSerde
46    + InMemorySize
47    + SignerRecoverable
48{
49    /// Returns reference to transaction hash.
50    fn tx_hash(&self) -> &TxHash;
51
52    /// Returns whether this transaction type can be __broadcasted__ as full transaction over the
53    /// network.
54    ///
55    /// Some transactions are not broadcastable as objects and only allowed to be broadcasted as
56    /// hashes, e.g. because they missing context (e.g. blob sidecar).
57    fn is_broadcastable_in_full(&self) -> bool {
58        // EIP-4844 transactions are not broadcastable in full, only hashes are allowed.
59        !self.is_eip4844()
60    }
61
62    /// Recover signer from signature and hash.
63    ///
64    /// Returns an error if the transaction's signature is invalid.
65    fn try_recover(&self) -> Result<Address, RecoveryError> {
66        self.recover_signer()
67    }
68
69    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
70    /// value_.
71    ///
72    /// Returns an error if the transaction's signature is invalid.
73    fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> {
74        self.recover_signer_unchecked()
75    }
76
77    /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with
78    /// tx type.
79    fn recalculate_hash(&self) -> B256 {
80        keccak256(self.encoded_2718())
81    }
82
83    /// Tries to recover signer and return [`Recovered`] by cloning the type.
84    #[auto_impl(keep_default_for(&, Arc))]
85    fn try_clone_into_recovered(&self) -> Result<Recovered<Self>, RecoveryError> {
86        self.recover_signer().map(|signer| Recovered::new_unchecked(self.clone(), signer))
87    }
88
89    /// Tries to recover signer and return [`Recovered`] by cloning the type.
90    #[auto_impl(keep_default_for(&, Arc))]
91    fn try_clone_into_recovered_unchecked(&self) -> Result<Recovered<Self>, RecoveryError> {
92        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self.clone(), signer))
93    }
94
95    /// Tries to recover signer and return [`Recovered`].
96    ///
97    /// Returns `Err(Self)` if the transaction's signature is invalid, see also
98    /// [`SignerRecoverable::recover_signer`].
99    #[auto_impl(keep_default_for(&, Arc))]
100    fn try_into_recovered(self) -> Result<Recovered<Self>, Self> {
101        match self.recover_signer() {
102            Ok(signer) => Ok(Recovered::new_unchecked(self, signer)),
103            Err(_) => Err(self),
104        }
105    }
106
107    /// Consumes the type, recover signer and return [`Recovered`] _without
108    /// ensuring that the signature has a low `s` value_ (EIP-2).
109    ///
110    /// Returns `RecoveryError` if the transaction's signature is invalid.
111    #[deprecated(note = "Use try_into_recovered_unchecked instead")]
112    #[auto_impl(keep_default_for(&, Arc))]
113    fn into_recovered_unchecked(self) -> Result<Recovered<Self>, RecoveryError> {
114        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self, signer))
115    }
116
117    /// Returns the [`Recovered`] transaction with the given sender.
118    ///
119    /// Note: assumes the given signer is the signer of this transaction.
120    #[auto_impl(keep_default_for(&, Arc))]
121    fn with_signer(self, signer: Address) -> Recovered<Self> {
122        Recovered::new_unchecked(self, signer)
123    }
124
125    /// Returns the [`Recovered`] transaction with the given signer, using a reference to self.
126    ///
127    /// Note: assumes the given signer is the signer of this transaction.
128    #[auto_impl(keep_default_for(&, Arc))]
129    fn with_signer_ref(&self, signer: Address) -> Recovered<&Self> {
130        Recovered::new_unchecked(self, signer)
131    }
132}
133
134impl<T> SignedTransaction for EthereumTxEnvelope<T>
135where
136    T: RlpEcdsaEncodableTx + SignableTransaction<Signature> + Unpin,
137    Self: Clone + PartialEq + Eq + Decodable + Decodable2718 + MaybeSerde + InMemorySize,
138{
139    fn tx_hash(&self) -> &TxHash {
140        match self {
141            Self::Legacy(tx) => tx.hash(),
142            Self::Eip2930(tx) => tx.hash(),
143            Self::Eip1559(tx) => tx.hash(),
144            Self::Eip7702(tx) => tx.hash(),
145            Self::Eip4844(tx) => tx.hash(),
146        }
147    }
148}
149
150#[cfg(feature = "op")]
151mod op {
152    use super::*;
153    use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope};
154
155    impl SignedTransaction for OpPooledTransaction {
156        fn tx_hash(&self) -> &TxHash {
157            match self {
158                Self::Legacy(tx) => tx.hash(),
159                Self::Eip2930(tx) => tx.hash(),
160                Self::Eip1559(tx) => tx.hash(),
161                Self::Eip7702(tx) => tx.hash(),
162            }
163        }
164    }
165
166    impl SignedTransaction for OpTxEnvelope {
167        fn tx_hash(&self) -> &TxHash {
168            match self {
169                Self::Legacy(tx) => tx.hash(),
170                Self::Eip2930(tx) => tx.hash(),
171                Self::Eip1559(tx) => tx.hash(),
172                Self::Eip7702(tx) => tx.hash(),
173                Self::Deposit(tx) => tx.hash_ref(),
174            }
175        }
176    }
177}