1use 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#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests)]
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35pub enum PooledTransactionsElement {
36 Legacy(Signed<TxLegacy>),
38 Eip2930(Signed<TxEip2930>),
40 Eip1559(Signed<TxEip1559>),
42 Eip7702(Signed<TxEip7702>),
44 BlobTransaction(BlobTransaction),
46 Seismic(Signed<TxSeismic>),
48}
49
50impl PooledTransactionsElement {
51 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 TransactionSigned { transaction: Transaction::Eip4844(tx), signature, .. } => {
64 Self::BlobTransaction(BlobTransaction(Signed::new_unchecked(
66 TxEip4844WithSidecar { tx, sidecar },
67 signature,
68 hash,
69 )))
70 }
71 _ => return Err(tx),
74 })
75 }
76
77 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 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 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 pub fn recover_signer(&self) -> Option<Address> {
118 recover_signer(self.signature(), self.signature_hash())
119 }
120
121 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 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 pub fn into_ecrecovered_transaction(self, signer: Address) -> RecoveredTx {
148 RecoveredTx::from_signed_transaction(self.into_transaction(), signer)
149 }
150
151 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 #[inline]
165 pub const fn is_eip4844(&self) -> bool {
166 matches!(self, Self::BlobTransaction(_))
167 }
168
169 pub const fn as_legacy(&self) -> Option<&TxLegacy> {
171 match self {
172 Self::Legacy(tx) => Some(tx.tx()),
173 _ => None,
174 }
175 }
176
177 pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
179 match self {
180 Self::Eip2930(tx) => Some(tx.tx()),
181 _ => None,
182 }
183 }
184
185 pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
187 match self {
188 Self::Eip1559(tx) => Some(tx.tx()),
189 _ => None,
190 }
191 }
192
193 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 pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
203 match self {
204 Self::Eip7702(tx) => Some(tx.tx()),
205 _ => None,
206 }
207 }
208
209 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 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 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
253 if buf.is_empty() {
265 return Err(RlpError::InputTooShort)
266 }
267
268 let mut original_encoding = *buf;
270
271 let header = Header::decode(buf)?;
274
275 if header.list {
277 let tx = Self::fallback_decode(&mut original_encoding)?;
279
280 *buf = original_encoding;
283
284 Ok(tx)
285 } else {
286 let tx_type = *buf.first().ok_or(RlpError::InputTooShort)?;
288 let remaining_len = buf.len();
289
290 buf.advance(1);
292
293 let tx = Self::typed_decode(tx_type, buf).map_err(RlpError::from)?;
294
295 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 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 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 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 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
710 let tx_signed = TransactionSigned::arbitrary(u)?;
712 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
733pub type PooledTransactionsElementEcRecovered<T = PooledTransactionsElement> = RecoveredTx<T>;
735
736impl PooledTransactionsElementEcRecovered {
737 pub fn into_ecrecovered_transaction(self) -> RecoveredTx {
739 let (tx, signer) = self.to_components();
740 tx.into_ecrecovered_transaction(signer)
741 }
742
743 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
759impl 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 &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
786 &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
792 &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
793 &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 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 #[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 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 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 let res = PooledTransactionsElement::decode_2718(&mut &data[..]);
862 assert_matches!(res, Ok(_tx));
863 }
864}