reth_primitives/transaction/
pooled.rs

1//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
2//! response to `GetPooledTransactions`.
3
4use super::{
5    recover_signer_unchecked, signature::recover_signer, TransactionConversionError, TxEip7702,
6};
7use crate::{BlobTransaction, RecoveredTx, Transaction, TransactionSigned};
8use alloc::vec::Vec;
9use alloy_consensus::{
10    constants::EIP4844_TX_TYPE_ID,
11    transaction::{TxEip1559, TxEip2930, TxEip4844, TxLegacy, TxSeismic},
12    SignableTransaction, Signed, TxEip4844WithSidecar, Typed2718,
13};
14use alloy_eips::{
15    eip2718::{Decodable2718, Eip2718Result, Encodable2718},
16    eip2930::AccessList,
17    eip4844::BlobTransactionSidecar,
18    eip712::{Decodable712, Eip712Error, Eip712Result, TypedDataRequest},
19    eip7702::SignedAuthorization,
20};
21use alloy_primitives::{
22    Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
23};
24use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
25use bytes::Buf;
26use core::hash::{Hash, Hasher};
27use reth_primitives_traits::{InMemorySize, SignedTransaction};
28use revm_primitives::keccak256;
29use serde::{Deserialize, Serialize};
30
31/// A response to `GetPooledTransactions`. This can include either a blob transaction, or a
32/// non-4844 signed transaction.
33#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests)]
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35pub enum PooledTransactionsElement {
36    /// An untagged [`TxLegacy`].
37    Legacy(Signed<TxLegacy>),
38    /// A [`TxEip2930`] tagged with type 1.
39    Eip2930(Signed<TxEip2930>),
40    /// A [`TxEip1559`] tagged with type 2.
41    Eip1559(Signed<TxEip1559>),
42    /// A [`TxEip7702`] tagged with type 4.
43    Eip7702(Signed<TxEip7702>),
44    /// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
45    BlobTransaction(BlobTransaction),
46    /// A seismic transaction
47    Seismic(Signed<TxSeismic>),
48}
49
50impl PooledTransactionsElement {
51    /// Converts from an EIP-4844 [`RecoveredTx`] to a
52    /// [`PooledTransactionsElementEcRecovered`] with the given sidecar.
53    ///
54    /// Returns an `Err` containing the original `TransactionSigned` if the transaction is not
55    /// EIP-4844.
56    pub fn try_from_blob_transaction(
57        tx: TransactionSigned,
58        sidecar: BlobTransactionSidecar,
59    ) -> Result<Self, TransactionSigned> {
60        let hash = tx.hash();
61        Ok(match tx {
62            // If the transaction is an EIP-4844 transaction...
63            TransactionSigned { transaction: Transaction::Eip4844(tx), signature, .. } => {
64                // Construct a `PooledTransactionsElement::BlobTransaction` with provided sidecar.
65                Self::BlobTransaction(BlobTransaction(Signed::new_unchecked(
66                    TxEip4844WithSidecar { tx, sidecar },
67                    signature,
68                    hash,
69                )))
70            }
71            // If the transaction is not EIP-4844, return an error with the original
72            // transaction.
73            _ => return Err(tx),
74        })
75    }
76
77    /// Heavy operation that return signature hash over rlp encoded transaction.
78    /// It is only for signature signing or signer recovery.
79    pub fn signature_hash(&self) -> B256 {
80        match self {
81            Self::Legacy(tx) => tx.signature_hash(),
82            Self::Seismic(tx) => tx.signature_hash(),
83            Self::Eip2930(tx) => tx.signature_hash(),
84            Self::Eip1559(tx) => tx.signature_hash(),
85            Self::Eip7702(tx) => tx.signature_hash(),
86            Self::BlobTransaction(tx) => tx.signature_hash(),
87        }
88    }
89
90    /// Reference to transaction hash. Used to identify transaction.
91    pub const fn hash(&self) -> &TxHash {
92        match self {
93            Self::Legacy(tx) => tx.hash(),
94            Self::Seismic(tx) => tx.hash(),
95            Self::Eip2930(tx) => tx.hash(),
96            Self::Eip1559(tx) => tx.hash(),
97            Self::Eip7702(tx) => tx.hash(),
98            Self::BlobTransaction(tx) => tx.0.hash(),
99        }
100    }
101
102    /// Returns the signature of the transaction.
103    pub const fn signature(&self) -> &Signature {
104        match self {
105            Self::Legacy(tx) => tx.signature(),
106            Self::Seismic(tx) => tx.signature(),
107            Self::Eip2930(tx) => tx.signature(),
108            Self::Eip1559(tx) => tx.signature(),
109            Self::Eip7702(tx) => tx.signature(),
110            Self::BlobTransaction(tx) => tx.0.signature(),
111        }
112    }
113
114    /// Recover signer from signature and hash.
115    ///
116    /// Returns `None` if the transaction's signature is invalid, see also [`Self::recover_signer`].
117    pub fn recover_signer(&self) -> Option<Address> {
118        recover_signer(self.signature(), self.signature_hash())
119    }
120
121    /// Tries to recover signer and return [`PooledTransactionsElementEcRecovered`].
122    ///
123    /// Returns `Err(Self)` if the transaction's signature is invalid, see also
124    /// [`Self::recover_signer`].
125    pub fn try_into_ecrecovered(self) -> Result<PooledTransactionsElementEcRecovered, Self> {
126        match self.recover_signer() {
127            None => Err(self),
128            Some(signer) => Ok(RecoveredTx { signed_transaction: self, signer }),
129        }
130    }
131
132    /// This encodes the transaction _without_ the signature, and is only suitable for creating a
133    /// hash intended for signing.
134    pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
135        match self {
136            Self::Legacy(tx) => tx.tx().encode_for_signing(out),
137            Self::Seismic(tx) => tx.tx().encode_for_signing(out),
138            Self::Eip2930(tx) => tx.tx().encode_for_signing(out),
139            Self::Eip1559(tx) => tx.tx().encode_for_signing(out),
140            Self::BlobTransaction(tx) => tx.tx().encode_for_signing(out),
141            Self::Eip7702(tx) => tx.tx().encode_for_signing(out),
142        }
143    }
144
145    /// Create [`RecoveredTx`] by converting this transaction into
146    /// [`TransactionSigned`] and [`Address`] of the signer.
147    pub fn into_ecrecovered_transaction(self, signer: Address) -> RecoveredTx {
148        RecoveredTx::from_signed_transaction(self.into_transaction(), signer)
149    }
150
151    /// Returns the inner [`TransactionSigned`].
152    pub fn into_transaction(self) -> TransactionSigned {
153        match self {
154            Self::Legacy(tx) => tx.into(),
155            Self::Seismic(tx) => tx.into(),
156            Self::Eip2930(tx) => tx.into(),
157            Self::Eip1559(tx) => tx.into(),
158            Self::Eip7702(tx) => tx.into(),
159            Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
160        }
161    }
162
163    /// Returns true if the transaction is an EIP-4844 transaction.
164    #[inline]
165    pub const fn is_eip4844(&self) -> bool {
166        matches!(self, Self::BlobTransaction(_))
167    }
168
169    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
170    pub const fn as_legacy(&self) -> Option<&TxLegacy> {
171        match self {
172            Self::Legacy(tx) => Some(tx.tx()),
173            _ => None,
174        }
175    }
176
177    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
178    pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
179        match self {
180            Self::Eip2930(tx) => Some(tx.tx()),
181            _ => None,
182        }
183    }
184
185    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
186    pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
187        match self {
188            Self::Eip1559(tx) => Some(tx.tx()),
189            _ => None,
190        }
191    }
192
193    /// Returns the [`TxEip4844`] variant if the transaction is an EIP-4844 transaction.
194    pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
195        match self {
196            Self::BlobTransaction(tx) => Some(tx.0.tx().tx()),
197            _ => None,
198        }
199    }
200
201    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
202    pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
203        match self {
204            Self::Eip7702(tx) => Some(tx.tx()),
205            _ => None,
206        }
207    }
208
209    /// Returns the blob gas used for all blobs of the EIP-4844 transaction if it is an EIP-4844
210    /// transaction.
211    ///
212    /// This is the number of blobs times the
213    /// [`DATA_GAS_PER_BLOB`](alloy_eips::eip4844::DATA_GAS_PER_BLOB) a single blob consumes.
214    pub fn blob_gas_used(&self) -> Option<u64> {
215        self.as_eip4844().map(TxEip4844::blob_gas)
216    }
217}
218
219impl Hash for PooledTransactionsElement {
220    fn hash<H: Hasher>(&self, state: &mut H) {
221        self.trie_hash().hash(state);
222    }
223}
224
225impl Encodable for PooledTransactionsElement {
226    /// This encodes the transaction _with_ the signature, and an rlp header.
227    ///
228    /// For legacy transactions, it encodes the transaction data:
229    /// `rlp(tx-data)`
230    ///
231    /// For EIP-2718 typed transactions, it encodes the transaction type followed by the rlp of the
232    /// transaction:
233    /// `rlp(tx-type || rlp(tx-data))`
234    fn encode(&self, out: &mut dyn bytes::BufMut) {
235        self.network_encode(out);
236    }
237
238    fn length(&self) -> usize {
239        let mut payload_length = self.encode_2718_len();
240        if !Encodable2718::is_legacy(self) {
241            payload_length += Header { list: false, payload_length }.length();
242        }
243
244        payload_length
245    }
246}
247
248impl Decodable for PooledTransactionsElement {
249    /// Decodes an enveloped post EIP-4844 [`PooledTransactionsElement`].
250    ///
251    /// CAUTION: this expects that `buf` is `rlp(tx_type || rlp(tx-data))`
252    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
253        // From the EIP-4844 spec:
254        // Blob transactions have two network representations. During transaction gossip responses
255        // (`PooledTransactions`), the EIP-2718 `TransactionPayload` of the blob transaction is
256        // wrapped to become:
257        //
258        // `rlp([tx_payload_body, blobs, commitments, proofs])`
259        //
260        // This means the full wire encoding is:
261        // `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
262        //
263        // First, we check whether or not the transaction is a legacy transaction.
264        if buf.is_empty() {
265            return Err(RlpError::InputTooShort)
266        }
267
268        // keep the original buf around for legacy decoding
269        let mut original_encoding = *buf;
270
271        // If the header is a list header, it is a legacy transaction. Otherwise, it is a typed
272        // transaction
273        let header = Header::decode(buf)?;
274
275        // Check if the tx is a list
276        if header.list {
277            // decode as legacy transaction
278            let tx = Self::fallback_decode(&mut original_encoding)?;
279
280            // advance the buffer by however long the legacy transaction decoding advanced the
281            // buffer
282            *buf = original_encoding;
283
284            Ok(tx)
285        } else {
286            // decode the type byte, only decode BlobTransaction if it is a 4844 transaction
287            let tx_type = *buf.first().ok_or(RlpError::InputTooShort)?;
288            let remaining_len = buf.len();
289
290            // Advance the buffer past the type byte
291            buf.advance(1);
292
293            let tx = Self::typed_decode(tx_type, buf).map_err(RlpError::from)?;
294
295            // check that the bytes consumed match the payload length
296            let bytes_consumed = remaining_len - buf.len();
297            if bytes_consumed != header.payload_length {
298                return Err(RlpError::UnexpectedLength)
299            }
300
301            Ok(tx)
302        }
303    }
304}
305
306impl Encodable2718 for PooledTransactionsElement {
307    fn type_flag(&self) -> Option<u8> {
308        match self {
309            Self::Legacy(_) => None,
310            Self::Seismic(_) => None,
311            Self::Eip2930(_) => Some(0x01),
312            Self::Eip1559(_) => Some(0x02),
313            Self::BlobTransaction(_) => Some(0x03),
314            Self::Eip7702(_) => Some(0x04),
315        }
316    }
317
318    fn encode_2718_len(&self) -> usize {
319        match self {
320            Self::Legacy(tx) => tx.eip2718_encoded_length(),
321            Self::Seismic(tx) => tx.eip2718_encoded_length(),
322            Self::Eip2930(tx) => tx.eip2718_encoded_length(),
323            Self::Eip1559(tx) => tx.eip2718_encoded_length(),
324            Self::Eip7702(tx) => tx.eip2718_encoded_length(),
325            Self::BlobTransaction(tx) => tx.eip2718_encoded_length(),
326        }
327    }
328
329    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
330        match self {
331            Self::Legacy(tx) => tx.eip2718_encode(out),
332            Self::Seismic(tx) => tx.eip2718_encode(out),
333            Self::Eip2930(tx) => tx.eip2718_encode(out),
334            Self::Eip1559(tx) => tx.eip2718_encode(out),
335            Self::Eip7702(tx) => tx.eip2718_encode(out),
336            Self::BlobTransaction(tx) => tx.eip2718_encode(out),
337        }
338    }
339
340    fn trie_hash(&self) -> B256 {
341        *self.hash()
342    }
343}
344
345impl Decodable2718 for PooledTransactionsElement {
346    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
347        match ty {
348            EIP4844_TX_TYPE_ID => {
349                // Recall that the blob transaction response `TransactionPayload` is encoded like
350                // this: `rlp([tx_payload_body, blobs, commitments, proofs])`
351                //
352                // Note that `tx_payload_body` is a list:
353                // `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
354                //
355                // This makes the full encoding:
356                // `tx_type (0x03) || rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
357
358                // Now, we decode the inner blob transaction:
359                // `rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
360                let blob_tx = BlobTransaction::decode_inner(buf)?;
361                Ok(Self::BlobTransaction(blob_tx))
362            }
363            tx_type => {
364                let typed_tx = TransactionSigned::typed_decode(tx_type, buf)?;
365                let hash = typed_tx.hash();
366                match typed_tx.transaction {
367                    Transaction::Legacy(_) => Err(RlpError::Custom(
368                        "legacy transactions should not be a result of typed decoding",
369                    ).into()),
370                    // because we checked the tx type, we can be sure that the transaction is not a
371                    // blob transaction
372                    Transaction::Eip4844(_) => Err(RlpError::Custom(
373                        "EIP-4844 transactions can only be decoded with transaction type 0x03",
374                    ).into()),
375                    Transaction::Eip2930(tx) => Ok(Self::Eip2930 (
376                        Signed::new_unchecked(tx, typed_tx.signature, hash)
377                    )),
378                    Transaction::Eip1559(tx) => Ok(Self::Eip1559( Signed::new_unchecked(tx, typed_tx.signature, hash))),
379                    Transaction::Eip7702(tx) => Ok(Self::Eip7702( Signed::new_unchecked(tx, typed_tx.signature, hash))),
380                    Transaction::Seismic(tx) => Ok(Self::Seismic( Signed::new_unchecked(tx, typed_tx.signature, hash))),
381                    #[cfg(feature = "optimism")]
382                    Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement").into())
383                }
384            }
385        }
386    }
387
388    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
389        // decode as legacy transaction
390        let (transaction, hash, signature) =
391            TransactionSigned::decode_rlp_legacy_transaction_tuple(buf)?;
392
393        Ok(Self::Legacy(Signed::new_unchecked(transaction, signature, hash)))
394    }
395}
396
397impl Typed2718 for PooledTransactionsElement {
398    fn ty(&self) -> u8 {
399        match self {
400            Self::Legacy(tx) => tx.tx().ty(),
401            Self::Seismic(tx) => tx.tx().ty(),
402            Self::Eip2930(tx) => tx.tx().ty(),
403            Self::Eip1559(tx) => tx.tx().ty(),
404            Self::BlobTransaction(tx) => tx.tx().ty(),
405            Self::Eip7702(tx) => tx.tx().ty(),
406        }
407    }
408}
409
410impl Decodable712 for PooledTransactionsElement {
411    fn decode_712(typed_data: &TypedDataRequest) -> Eip712Result<Self> {
412        let tx_signed = TransactionSigned::decode_712(typed_data)?;
413        let sig = tx_signed.signature;
414        let hash = tx_signed.hash();
415        match tx_signed.transaction {
416            Transaction::Seismic(tx) => Ok(Self::Seismic(Signed::new_unchecked(tx, sig, hash))),
417            _ => Err(Eip712Error::InvalidType),
418        }
419    }
420}
421
422impl alloy_consensus::transaction::ShieldableTransaction for PooledTransactionsElement {
423    fn shield_input(&mut self) {
424        match self {
425            Self::Seismic(tx) => tx.shield_input(),
426            _ => {}
427        }
428    }
429}
430impl alloy_consensus::Transaction for PooledTransactionsElement {
431    fn chain_id(&self) -> Option<ChainId> {
432        match self {
433            Self::Legacy(tx) => tx.tx().chain_id(),
434            Self::Seismic(tx) => tx.tx().chain_id(),
435            Self::Eip2930(tx) => tx.tx().chain_id(),
436            Self::Eip1559(tx) => tx.tx().chain_id(),
437            Self::Eip7702(tx) => tx.tx().chain_id(),
438            Self::BlobTransaction(tx) => tx.tx().chain_id(),
439        }
440    }
441
442    fn nonce(&self) -> u64 {
443        match self {
444            Self::Legacy(tx) => tx.tx().nonce(),
445            Self::Seismic(tx) => tx.tx().nonce(),
446            Self::Eip2930(tx) => tx.tx().nonce(),
447            Self::Eip1559(tx) => tx.tx().nonce(),
448            Self::Eip7702(tx) => tx.tx().nonce(),
449            Self::BlobTransaction(tx) => tx.tx().nonce(),
450        }
451    }
452
453    fn gas_limit(&self) -> u64 {
454        match self {
455            Self::Legacy(tx) => tx.tx().gas_limit(),
456            Self::Seismic(tx) => tx.tx().gas_limit(),
457            Self::Eip2930(tx) => tx.tx().gas_limit(),
458            Self::Eip1559(tx) => tx.tx().gas_limit(),
459            Self::Eip7702(tx) => tx.tx().gas_limit(),
460            Self::BlobTransaction(tx) => tx.tx().gas_limit(),
461        }
462    }
463
464    fn gas_price(&self) -> Option<u128> {
465        match self {
466            Self::Legacy(tx) => tx.tx().gas_price(),
467            Self::Seismic(tx) => tx.tx().gas_price(),
468            Self::Eip2930(tx) => tx.tx().gas_price(),
469            Self::Eip1559(tx) => tx.tx().gas_price(),
470            Self::Eip7702(tx) => tx.tx().gas_price(),
471            Self::BlobTransaction(tx) => tx.tx().gas_price(),
472        }
473    }
474
475    fn max_fee_per_gas(&self) -> u128 {
476        match self {
477            Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
478            Self::Seismic(tx) => tx.tx().max_fee_per_gas(),
479            Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
480            Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
481            Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
482            Self::BlobTransaction(tx) => tx.tx().max_fee_per_gas(),
483        }
484    }
485
486    fn max_priority_fee_per_gas(&self) -> Option<u128> {
487        match self {
488            Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
489            Self::Seismic(tx) => tx.tx().max_priority_fee_per_gas(),
490            Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
491            Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
492            Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
493            Self::BlobTransaction(tx) => tx.tx().max_priority_fee_per_gas(),
494        }
495    }
496
497    fn max_fee_per_blob_gas(&self) -> Option<u128> {
498        match self {
499            Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
500            Self::Seismic(tx) => tx.tx().max_fee_per_blob_gas(),
501            Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
502            Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
503            Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
504            Self::BlobTransaction(tx) => tx.tx().max_fee_per_blob_gas(),
505        }
506    }
507
508    fn priority_fee_or_price(&self) -> u128 {
509        match self {
510            Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
511            Self::Seismic(tx) => tx.tx().priority_fee_or_price(),
512            Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
513            Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
514            Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
515            Self::BlobTransaction(tx) => tx.tx().priority_fee_or_price(),
516        }
517    }
518
519    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
520        match self {
521            Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
522            Self::Seismic(tx) => tx.tx().effective_gas_price(base_fee),
523            Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
524            Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
525            Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
526            Self::BlobTransaction(tx) => tx.tx().effective_gas_price(base_fee),
527        }
528    }
529
530    fn is_dynamic_fee(&self) -> bool {
531        match self {
532            Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
533            Self::Seismic(tx) => tx.tx().is_dynamic_fee(),
534            Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
535            Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
536            Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
537            Self::BlobTransaction(tx) => tx.tx().is_dynamic_fee(),
538        }
539    }
540
541    fn kind(&self) -> TxKind {
542        match self {
543            Self::Legacy(tx) => tx.tx().kind(),
544            Self::Seismic(tx) => tx.tx().kind(),
545            Self::Eip2930(tx) => tx.tx().kind(),
546            Self::Eip1559(tx) => tx.tx().kind(),
547            Self::Eip7702(tx) => tx.tx().kind(),
548            Self::BlobTransaction(tx) => tx.tx().kind(),
549        }
550    }
551
552    fn is_create(&self) -> bool {
553        match self {
554            Self::Legacy(tx) => tx.tx().is_create(),
555            Self::Seismic(tx) => tx.tx().is_create(),
556            Self::Eip2930(tx) => tx.tx().is_create(),
557            Self::Eip1559(tx) => tx.tx().is_create(),
558            Self::Eip7702(tx) => tx.tx().is_create(),
559            Self::BlobTransaction(tx) => tx.tx().is_create(),
560        }
561    }
562
563    fn value(&self) -> U256 {
564        match self {
565            Self::Legacy(tx) => tx.tx().value(),
566            Self::Seismic(tx) => tx.tx().value(),
567            Self::Eip2930(tx) => tx.tx().value(),
568            Self::Eip1559(tx) => tx.tx().value(),
569            Self::Eip7702(tx) => tx.tx().value(),
570            Self::BlobTransaction(tx) => tx.tx().value(),
571        }
572    }
573
574    fn input(&self) -> &Bytes {
575        match self {
576            Self::Legacy(tx) => tx.tx().input(),
577            Self::Seismic(tx) => tx.tx().input(),
578            Self::Eip2930(tx) => tx.tx().input(),
579            Self::Eip1559(tx) => tx.tx().input(),
580            Self::Eip7702(tx) => tx.tx().input(),
581            Self::BlobTransaction(tx) => tx.tx().input(),
582        }
583    }
584
585    fn access_list(&self) -> Option<&AccessList> {
586        match self {
587            Self::Legacy(tx) => tx.tx().access_list(),
588            Self::Seismic(tx) => tx.tx().access_list(),
589            Self::Eip2930(tx) => tx.tx().access_list(),
590            Self::Eip1559(tx) => tx.tx().access_list(),
591            Self::Eip7702(tx) => tx.tx().access_list(),
592            Self::BlobTransaction(tx) => tx.tx().access_list(),
593        }
594    }
595
596    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
597        match self {
598            Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
599            Self::Seismic(tx) => tx.tx().blob_versioned_hashes(),
600            Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
601            Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
602            Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
603            Self::BlobTransaction(tx) => tx.tx().blob_versioned_hashes(),
604        }
605    }
606
607    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
608        match self {
609            Self::Legacy(tx) => tx.tx().authorization_list(),
610            Self::Seismic(tx) => tx.tx().authorization_list(),
611            Self::Eip2930(tx) => tx.tx().authorization_list(),
612            Self::Eip1559(tx) => tx.tx().authorization_list(),
613            Self::Eip7702(tx) => tx.tx().authorization_list(),
614            Self::BlobTransaction(tx) => tx.tx().authorization_list(),
615        }
616    }
617    fn seismic_elements(&self) -> Option<&alloy_consensus::transaction::TxSeismicElements> {
618        match self {
619            Self::Seismic(tx) => tx.tx().seismic_elements(),
620            _ => None,
621        }
622    }
623}
624
625impl SignedTransaction for PooledTransactionsElement {
626    fn tx_hash(&self) -> &TxHash {
627        match self {
628            Self::Legacy(tx) => tx.hash(),
629            Self::Seismic(tx) => tx.hash(),
630            Self::Eip2930(tx) => tx.hash(),
631            Self::Eip1559(tx) => tx.hash(),
632            Self::Eip7702(tx) => tx.hash(),
633            Self::BlobTransaction(tx) => tx.hash(),
634        }
635    }
636
637    fn signature(&self) -> &Signature {
638        match self {
639            Self::Legacy(tx) => tx.signature(),
640            Self::Seismic(tx) => tx.signature(),
641            Self::Eip2930(tx) => tx.signature(),
642            Self::Eip1559(tx) => tx.signature(),
643            Self::Eip7702(tx) => tx.signature(),
644            Self::BlobTransaction(tx) => tx.signature(),
645        }
646    }
647
648    fn recover_signer(&self) -> Option<Address> {
649        let signature_hash = self.signature_hash();
650        recover_signer(self.signature(), signature_hash)
651    }
652
653    fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> {
654        self.encode_for_signing(buf);
655        let signature_hash = keccak256(buf);
656        recover_signer_unchecked(self.signature(), signature_hash)
657    }
658}
659
660impl InMemorySize for PooledTransactionsElement {
661    fn size(&self) -> usize {
662        match self {
663            Self::Legacy(tx) => tx.size(),
664            Self::Seismic(tx) => tx.size(),
665            Self::Eip2930(tx) => tx.size(),
666            Self::Eip1559(tx) => tx.size(),
667            Self::Eip7702(tx) => tx.size(),
668            Self::BlobTransaction(tx) => tx.size(),
669        }
670    }
671}
672
673impl From<PooledTransactionsElementEcRecovered> for PooledTransactionsElement {
674    fn from(recovered: PooledTransactionsElementEcRecovered) -> Self {
675        recovered.into_signed()
676    }
677}
678
679impl TryFrom<TransactionSigned> for PooledTransactionsElement {
680    type Error = TransactionConversionError;
681
682    fn try_from(tx: TransactionSigned) -> Result<Self, Self::Error> {
683        tx.try_into_pooled().map_err(|_| TransactionConversionError::UnsupportedForP2P)
684    }
685}
686
687impl From<PooledTransactionsElement> for TransactionSigned {
688    fn from(element: PooledTransactionsElement) -> Self {
689        match element {
690            PooledTransactionsElement::Legacy(tx) => tx.into(),
691            PooledTransactionsElement::Seismic(tx) => tx.into(),
692            PooledTransactionsElement::Eip2930(tx) => tx.into(),
693            PooledTransactionsElement::Eip1559(tx) => tx.into(),
694            PooledTransactionsElement::Eip7702(tx) => tx.into(),
695            PooledTransactionsElement::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
696        }
697    }
698}
699
700#[cfg(any(test, feature = "arbitrary"))]
701impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement {
702    /// Generates an arbitrary `PooledTransactionsElement`.
703    ///
704    /// This function generates an arbitrary `PooledTransactionsElement` by creating a transaction
705    /// and, if applicable, generating a sidecar for blob transactions.
706    ///
707    /// It handles the generation of sidecars and constructs the resulting
708    /// `PooledTransactionsElement`.
709    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
710        // Attempt to create a `TransactionSigned` with arbitrary data.
711        let tx_signed = TransactionSigned::arbitrary(u)?;
712        // Attempt to create a `PooledTransactionsElement` with arbitrary data, handling the Result.
713        match tx_signed.try_into_pooled() {
714            Ok(tx) => Ok(tx),
715            Err(tx) => {
716                let (tx, sig, hash) = tx.into_parts();
717                match tx {
718                    Transaction::Eip4844(tx) => {
719                        let sidecar = BlobTransactionSidecar::arbitrary(u)?;
720                        Ok(Self::BlobTransaction(BlobTransaction(Signed::new_unchecked(
721                            TxEip4844WithSidecar { tx, sidecar },
722                            sig,
723                            hash,
724                        ))))
725                    }
726                    _ => Err(arbitrary::Error::IncorrectFormat),
727                }
728            }
729        }
730    }
731}
732
733/// A signed pooled transaction with recovered signer.
734pub type PooledTransactionsElementEcRecovered<T = PooledTransactionsElement> = RecoveredTx<T>;
735
736impl PooledTransactionsElementEcRecovered {
737    /// Transform back to [`RecoveredTx`]
738    pub fn into_ecrecovered_transaction(self) -> RecoveredTx {
739        let (tx, signer) = self.to_components();
740        tx.into_ecrecovered_transaction(signer)
741    }
742
743    /// Converts from an EIP-4844 [`RecoveredTx`] to a
744    /// [`PooledTransactionsElementEcRecovered`] with the given sidecar.
745    ///
746    /// Returns the transaction is not an EIP-4844 transaction.
747    pub fn try_from_blob_transaction(
748        tx: RecoveredTx,
749        sidecar: BlobTransactionSidecar,
750    ) -> Result<Self, RecoveredTx> {
751        let RecoveredTx { signer, signed_transaction } = tx;
752        let transaction =
753            PooledTransactionsElement::try_from_blob_transaction(signed_transaction, sidecar)
754                .map_err(|tx| RecoveredTx { signer, signed_transaction: tx })?;
755        Ok(Self::from_signed_transaction(transaction, signer))
756    }
757}
758
759/// Converts a `Recovered` into a `PooledTransactionsElementEcRecovered`.
760impl TryFrom<RecoveredTx> for PooledTransactionsElementEcRecovered {
761    type Error = TransactionConversionError;
762
763    fn try_from(tx: RecoveredTx) -> Result<Self, Self::Error> {
764        match PooledTransactionsElement::try_from(tx.signed_transaction) {
765            Ok(pooled_transaction) => {
766                Ok(Self::from_signed_transaction(pooled_transaction, tx.signer))
767            }
768            Err(_) => Err(TransactionConversionError::UnsupportedForP2P),
769        }
770    }
771}
772
773#[cfg(test)]
774mod tests {
775    use super::*;
776    use alloy_consensus::Transaction as _;
777    use alloy_primitives::{address, hex};
778    use assert_matches::assert_matches;
779    use bytes::Bytes;
780
781    #[test]
782    fn invalid_legacy_pooled_decoding_input_too_short() {
783        let input_too_short = [
784            // this should fail because the payload length is longer than expected
785            &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
786            // these should fail decoding
787            //
788            // The `c1` at the beginning is a list header, and the rest is a valid legacy
789            // transaction, BUT the payload length of the list header is 1, and the payload is
790            // obviously longer than one byte.
791            &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
792            &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
793            // this one is 19 bytes, and the buf is long enough, but the transaction will not
794            // consume that many bytes.
795            &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
796            &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
797        ];
798
799        for hex_data in &input_too_short {
800            let input_rlp = &mut &hex_data[..];
801            let res = PooledTransactionsElement::decode(input_rlp);
802
803            assert!(
804                res.is_err(),
805                "expected err after decoding rlp input: {:x?}",
806                Bytes::copy_from_slice(hex_data)
807            );
808
809            // this is a legacy tx so we can attempt the same test with decode_enveloped
810            let input_rlp = &mut &hex_data[..];
811            let res = PooledTransactionsElement::decode_2718(input_rlp);
812
813            assert!(
814                res.is_err(),
815                "expected err after decoding enveloped rlp input: {:x?}",
816                Bytes::copy_from_slice(hex_data)
817            );
818        }
819    }
820
821    // <https://holesky.etherscan.io/tx/0x7f60faf8a410a80d95f7ffda301d5ab983545913d3d789615df3346579f6c849>
822    #[test]
823    fn decode_eip1559_enveloped() {
824        let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
825);
826
827        let res = PooledTransactionsElement::decode_2718(&mut &data[..]).unwrap();
828        assert_eq!(
829            res.into_transaction().to(),
830            Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c"))
831        );
832    }
833
834    #[test]
835    fn legacy_valid_pooled_decoding() {
836        // d3 <- payload length, d3 - c0 = 0x13 = 19
837        // 0b <- nonce
838        // 02 <- gas_price
839        // 80 <- gas_limit
840        // 80 <- to (Create)
841        // 83 c5cdeb <- value
842        // 87 83c5acfd9e407c <- input
843        // 56 <- v (eip155, so modified with a chain id)
844        // 56 <- r
845        // 56 <- s
846        let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
847
848        let input_rlp = &mut &data[..];
849        let res = PooledTransactionsElement::decode(input_rlp);
850        assert_matches!(res, Ok(_tx));
851        assert!(input_rlp.is_empty());
852
853        // this is a legacy tx so we can attempt the same test with
854        // decode_rlp_legacy_transaction_tuple
855        let input_rlp = &mut &data[..];
856        let res = TransactionSigned::decode_rlp_legacy_transaction_tuple(input_rlp);
857        assert_matches!(res, Ok(_tx));
858        assert!(input_rlp.is_empty());
859
860        // we can also decode_enveloped
861        let res = PooledTransactionsElement::decode_2718(&mut &data[..]);
862        assert_matches!(res, Ok(_tx));
863    }
864}