reth_primitives_traits/transaction/
signed.rs

1//! API of a signed transaction.
2
3use crate::{
4    crypto::secp256k1::{recover_signer, recover_signer_unchecked},
5    InMemorySize, MaybeCompact, MaybeSerde, MaybeSerdeBincodeCompat,
6};
7use alloc::{fmt, vec::Vec};
8use alloy_consensus::{
9    transaction::{PooledTransaction, Recovered},
10    SignableTransaction,
11};
12use alloy_eips::eip2718::{Decodable2718, Encodable2718};
13use alloy_primitives::{keccak256, Address, PrimitiveSignature as Signature, TxHash, B256};
14use core::hash::Hash;
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#[auto_impl::auto_impl(&, Arc)]
22pub trait SignedTransaction:
23    Send
24    + Sync
25    + Unpin
26    + Clone
27    + fmt::Debug
28    + PartialEq
29    + Eq
30    + Hash
31    + alloy_rlp::Encodable
32    + alloy_rlp::Decodable
33    + Encodable2718
34    + Decodable2718
35    + alloy_consensus::Transaction
36    + MaybeSerde
37    + InMemorySize
38{
39    /// Returns reference to transaction hash.
40    fn tx_hash(&self) -> &TxHash;
41
42    /// Returns reference to signature.
43    fn signature(&self) -> &Signature;
44
45    /// Returns whether this transaction type can be __broadcasted__ as full transaction over the
46    /// network.
47    ///
48    /// Some transactions are not broadcastable as objects and only allowed to be broadcasted as
49    /// hashes, e.g. because they missing context (e.g. blob sidecar).
50    fn is_broadcastable_in_full(&self) -> bool {
51        // EIP-4844 transactions are not broadcastable in full, only hashes are allowed.
52        !self.is_eip4844()
53    }
54
55    /// Recover signer from signature and hash.
56    ///
57    /// Returns `RecoveryError` if the transaction's signature is invalid following [EIP-2](https://eips.ethereum.org/EIPS/eip-2), see also `reth_primitives::transaction::recover_signer`.
58    ///
59    /// Note:
60    ///
61    /// This can fail for some early ethereum mainnet transactions pre EIP-2, use
62    /// [`Self::recover_signer_unchecked`] if you want to recover the signer without ensuring that
63    /// the signature has a low `s` value.
64    fn recover_signer(&self) -> Result<Address, RecoveryError>;
65
66    /// Recover signer from signature and hash.
67    ///
68    /// Returns an error if the transaction's signature is invalid.
69    fn try_recover(&self) -> Result<Address, RecoveryError> {
70        self.recover_signer()
71    }
72
73    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
74    /// value_.
75    ///
76    /// Returns `None` if the transaction's signature is invalid, see also
77    /// `reth_primitives::transaction::recover_signer_unchecked`.
78    fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
79        self.recover_signer_unchecked_with_buf(&mut Vec::new())
80    }
81
82    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
83    /// value_.
84    ///
85    /// Returns an error if the transaction's signature is invalid.
86    fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> {
87        self.recover_signer_unchecked()
88    }
89
90    /// Same as [`Self::recover_signer_unchecked`] but receives a buffer to operate on. This is used
91    /// during batch recovery to avoid allocating a new buffer for each transaction.
92    fn recover_signer_unchecked_with_buf(
93        &self,
94        buf: &mut Vec<u8>,
95    ) -> Result<Address, RecoveryError>;
96
97    /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with
98    /// tx type.
99    fn recalculate_hash(&self) -> B256 {
100        keccak256(self.encoded_2718())
101    }
102
103    /// Tries to recover signer and return [`Recovered`] by cloning the type.
104    #[auto_impl(keep_default_for(&, Arc))]
105    fn try_clone_into_recovered(&self) -> Result<Recovered<Self>, RecoveryError> {
106        self.recover_signer().map(|signer| Recovered::new_unchecked(self.clone(), signer))
107    }
108
109    /// Tries to recover signer and return [`Recovered`].
110    ///
111    /// Returns `Err(Self)` if the transaction's signature is invalid, see also
112    /// [`SignedTransaction::recover_signer`].
113    #[auto_impl(keep_default_for(&, Arc))]
114    fn try_into_recovered(self) -> Result<Recovered<Self>, Self> {
115        match self.recover_signer() {
116            Ok(signer) => Ok(Recovered::new_unchecked(self, signer)),
117            Err(_) => Err(self),
118        }
119    }
120
121    /// Consumes the type, recover signer and return [`Recovered`] _without
122    /// ensuring that the signature has a low `s` value_ (EIP-2).
123    ///
124    /// Returns `None` if the transaction's signature is invalid.
125    #[auto_impl(keep_default_for(&, Arc))]
126    fn into_recovered_unchecked(self) -> Result<Recovered<Self>, RecoveryError> {
127        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self, signer))
128    }
129
130    /// Returns the [`Recovered`] transaction with the given sender.
131    ///
132    /// Note: assumes the given signer is the signer of this transaction.
133    #[auto_impl(keep_default_for(&, Arc))]
134    fn with_signer(self, signer: Address) -> Recovered<Self> {
135        Recovered::new_unchecked(self, signer)
136    }
137}
138
139impl SignedTransaction for PooledTransaction {
140    fn tx_hash(&self) -> &TxHash {
141        match self {
142            Self::Legacy(tx) => tx.hash(),
143            Self::Eip2930(tx) => tx.hash(),
144            Self::Eip1559(tx) => tx.hash(),
145            Self::Eip7702(tx) => tx.hash(),
146            Self::Eip4844(tx) => tx.hash(),
147        }
148    }
149
150    fn signature(&self) -> &Signature {
151        match self {
152            Self::Legacy(tx) => tx.signature(),
153            Self::Eip2930(tx) => tx.signature(),
154            Self::Eip1559(tx) => tx.signature(),
155            Self::Eip7702(tx) => tx.signature(),
156            Self::Eip4844(tx) => tx.signature(),
157        }
158    }
159
160    fn recover_signer(&self) -> Result<Address, RecoveryError> {
161        let signature_hash = self.signature_hash();
162        recover_signer(self.signature(), signature_hash)
163    }
164
165    fn recover_signer_unchecked_with_buf(
166        &self,
167        buf: &mut Vec<u8>,
168    ) -> Result<Address, RecoveryError> {
169        match self {
170            Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
171            Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
172            Self::Eip1559(tx) => tx.tx().encode_for_signing(buf),
173            Self::Eip7702(tx) => tx.tx().encode_for_signing(buf),
174            Self::Eip4844(tx) => tx.tx().encode_for_signing(buf),
175        }
176        let signature_hash = keccak256(buf);
177        recover_signer_unchecked(self.signature(), signature_hash)
178    }
179}
180
181#[cfg(feature = "op")]
182impl SignedTransaction for op_alloy_consensus::OpPooledTransaction {
183    fn tx_hash(&self) -> &TxHash {
184        match self {
185            Self::Legacy(tx) => tx.hash(),
186            Self::Eip2930(tx) => tx.hash(),
187            Self::Eip1559(tx) => tx.hash(),
188            Self::Eip7702(tx) => tx.hash(),
189        }
190    }
191
192    fn signature(&self) -> &Signature {
193        match self {
194            Self::Legacy(tx) => tx.signature(),
195            Self::Eip2930(tx) => tx.signature(),
196            Self::Eip1559(tx) => tx.signature(),
197            Self::Eip7702(tx) => tx.signature(),
198        }
199    }
200
201    fn recover_signer(&self) -> Result<Address, RecoveryError> {
202        let signature_hash = self.signature_hash();
203        recover_signer(self.signature(), signature_hash)
204    }
205
206    fn recover_signer_unchecked_with_buf(
207        &self,
208        buf: &mut Vec<u8>,
209    ) -> Result<Address, RecoveryError> {
210        match self {
211            Self::Legacy(tx) => tx.tx().encode_for_signing(buf),
212            Self::Eip2930(tx) => tx.tx().encode_for_signing(buf),
213            Self::Eip1559(tx) => tx.tx().encode_for_signing(buf),
214            Self::Eip7702(tx) => tx.tx().encode_for_signing(buf),
215        }
216        let signature_hash = keccak256(buf);
217        recover_signer_unchecked(self.signature(), signature_hash)
218    }
219}
220
221/// Opaque error type for sender recovery.
222#[derive(Debug, Default, thiserror::Error)]
223#[error("Failed to recover the signer")]
224pub struct RecoveryError;