1use alloc::vec::Vec;
2pub use alloy_consensus::{transaction::PooledTransaction, TxType};
3use alloy_consensus::{
4 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
5 BlobTransactionSidecar, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844,
6 TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
7 TypedTransaction,
8};
9use alloy_eips::{
10 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
11 eip2930::AccessList,
12 eip7702::SignedAuthorization,
13};
14use alloy_evm::FromRecoveredTx;
15use alloy_primitives::{
16 bytes::BufMut, keccak256, Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash,
17 TxKind, B256, U256,
18};
19use alloy_rlp::{Decodable, Encodable};
20use core::hash::{Hash, Hasher};
21use reth_primitives_traits::{
22 crypto::secp256k1::{recover_signer, recover_signer_unchecked},
23 sync::OnceLock,
24 transaction::{error::TransactionConversionError, signed::RecoveryError},
25 InMemorySize, SignedTransaction,
26};
27use revm_context::TxEnv;
28use serde::{Deserialize, Serialize};
29
30macro_rules! delegate {
31 ($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
32 match $self {
33 Transaction::Legacy($tx) => $tx.$method($($arg),*),
34 Transaction::Eip2930($tx) => $tx.$method($($arg),*),
35 Transaction::Eip1559($tx) => $tx.$method($($arg),*),
36 Transaction::Eip4844($tx) => $tx.$method($($arg),*),
37 Transaction::Eip7702($tx) => $tx.$method($($arg),*),
38 }
39 };
40}
41
42macro_rules! impl_from_signed {
43 ($($tx:ident),*) => {
44 $(
45 impl From<Signed<$tx>> for TransactionSigned {
46 fn from(value: Signed<$tx>) -> Self {
47 let(tx,sig,hash) = value.into_parts();
48 Self::new(tx.into(), sig, hash)
49 }
50 }
51 )*
52 };
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, Serialize, Deserialize)]
59#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
60#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
61pub enum Transaction {
62 Legacy(TxLegacy),
70 Eip2930(TxEip2930),
76 Eip1559(TxEip1559),
89 Eip4844(TxEip4844),
101 Eip7702(TxEip7702),
107}
108
109impl Transaction {
110 pub const fn tx_type(&self) -> TxType {
112 match self {
113 Self::Legacy(_) => TxType::Legacy,
114 Self::Eip2930(_) => TxType::Eip2930,
115 Self::Eip1559(_) => TxType::Eip1559,
116 Self::Eip4844(_) => TxType::Eip4844,
117 Self::Eip7702(_) => TxType::Eip7702,
118 }
119 }
120
121 pub fn set_nonce(&mut self, nonce: u64) {
123 match self {
124 Self::Legacy(tx) => tx.nonce = nonce,
125 Self::Eip2930(tx) => tx.nonce = nonce,
126 Self::Eip1559(tx) => tx.nonce = nonce,
127 Self::Eip4844(tx) => tx.nonce = nonce,
128 Self::Eip7702(tx) => tx.nonce = nonce,
129 }
130 }
131}
132
133impl Typed2718 for Transaction {
134 fn ty(&self) -> u8 {
135 delegate!(self => tx.ty())
136 }
137}
138
139impl alloy_consensus::Transaction for Transaction {
140 fn chain_id(&self) -> Option<ChainId> {
141 delegate!(self => tx.chain_id())
142 }
143
144 fn nonce(&self) -> u64 {
145 delegate!(self => tx.nonce())
146 }
147
148 fn gas_limit(&self) -> u64 {
149 delegate!(self => tx.gas_limit())
150 }
151
152 fn gas_price(&self) -> Option<u128> {
153 delegate!(self => tx.gas_price())
154 }
155
156 fn max_fee_per_gas(&self) -> u128 {
157 delegate!(self => tx.max_fee_per_gas())
158 }
159
160 fn max_priority_fee_per_gas(&self) -> Option<u128> {
161 delegate!(self => tx.max_priority_fee_per_gas())
162 }
163
164 fn max_fee_per_blob_gas(&self) -> Option<u128> {
165 delegate!(self => tx.max_fee_per_blob_gas())
166 }
167
168 fn priority_fee_or_price(&self) -> u128 {
169 delegate!(self => tx.priority_fee_or_price())
170 }
171
172 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
173 delegate!(self => tx.effective_gas_price(base_fee))
174 }
175
176 fn is_dynamic_fee(&self) -> bool {
177 delegate!(self => tx.is_dynamic_fee())
178 }
179
180 fn kind(&self) -> alloy_primitives::TxKind {
181 delegate!(self => tx.kind())
182 }
183
184 fn is_create(&self) -> bool {
185 delegate!(self => tx.is_create())
186 }
187
188 fn value(&self) -> alloy_primitives::U256 {
189 delegate!(self => tx.value())
190 }
191
192 fn input(&self) -> &alloy_primitives::Bytes {
193 delegate!(self => tx.input())
194 }
195
196 fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> {
197 delegate!(self => tx.access_list())
198 }
199
200 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
201 delegate!(self => tx.blob_versioned_hashes())
202 }
203
204 fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
205 delegate!(self => tx.authorization_list())
206 }
207}
208
209impl SignableTransaction<Signature> for Transaction {
210 fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) {
211 delegate!(self => tx.set_chain_id(chain_id))
212 }
213
214 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
215 delegate!(self => tx.encode_for_signing(out))
216 }
217
218 fn payload_len_for_signature(&self) -> usize {
219 delegate!(self => tx.payload_len_for_signature())
220 }
221
222 fn into_signed(self, signature: Signature) -> Signed<Self> {
223 let tx_hash = delegate!(&self => tx.tx_hash(&signature));
224 Signed::new_unchecked(self, signature, tx_hash)
225 }
226}
227
228impl InMemorySize for Transaction {
229 fn size(&self) -> usize {
230 delegate!(self => tx.size())
231 }
232}
233
234#[cfg(any(test, feature = "reth-codec"))]
235impl reth_codecs::Compact for Transaction {
236 fn to_compact<B>(&self, buf: &mut B) -> usize
239 where
240 B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
241 {
242 let identifier = self.tx_type().to_compact(buf);
243 delegate!(self => tx.to_compact(buf));
244 identifier
245 }
246
247 fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
256 let (tx_type, buf) = TxType::from_compact(buf, identifier);
257
258 match tx_type {
259 TxType::Legacy => {
260 let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
261 (Self::Legacy(tx), buf)
262 }
263 TxType::Eip2930 => {
264 let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
265 (Self::Eip2930(tx), buf)
266 }
267 TxType::Eip1559 => {
268 let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
269 (Self::Eip1559(tx), buf)
270 }
271 TxType::Eip4844 => {
272 let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
273 (Self::Eip4844(tx), buf)
274 }
275 TxType::Eip7702 => {
276 let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
277 (Self::Eip7702(tx), buf)
278 }
279 }
280 }
281}
282
283impl From<TypedTransaction> for Transaction {
284 fn from(value: TypedTransaction) -> Self {
285 match value {
286 TypedTransaction::Legacy(tx) => Self::Legacy(tx),
287 TypedTransaction::Eip2930(tx) => Self::Eip2930(tx),
288 TypedTransaction::Eip1559(tx) => Self::Eip1559(tx),
289 TypedTransaction::Eip4844(tx) => Self::Eip4844(tx.into()),
290 TypedTransaction::Eip7702(tx) => Self::Eip7702(tx),
291 }
292 }
293}
294
295impl RlpEcdsaEncodableTx for Transaction {
296 fn rlp_encoded_fields_length(&self) -> usize {
297 delegate!(self => tx.rlp_encoded_fields_length())
298 }
299
300 fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
301 delegate!(self => tx.rlp_encode_fields(out))
302 }
303
304 fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
305 delegate!(self => tx.eip2718_encode_with_type(signature, tx.ty(), out))
306 }
307
308 fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
309 delegate!(self => tx.eip2718_encode(signature, out))
310 }
311
312 fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
313 delegate!(self => tx.network_encode_with_type(signature, tx.ty(), out))
314 }
315
316 fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
317 delegate!(self => tx.network_encode(signature, out))
318 }
319
320 fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
321 delegate!(self => tx.tx_hash_with_type(signature, tx.ty()))
322 }
323
324 fn tx_hash(&self, signature: &Signature) -> TxHash {
325 delegate!(self => tx.tx_hash(signature))
326 }
327}
328
329#[derive(Debug, Clone, Eq, Serialize, Deserialize, derive_more::AsRef, derive_more::Deref)]
331#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
332#[cfg_attr(feature = "test-utils", derive(derive_more::DerefMut))]
333#[serde(rename_all = "camelCase")]
334pub struct TransactionSigned {
335 #[serde(skip)]
337 hash: OnceLock<TxHash>,
338 signature: Signature,
340 #[deref]
342 #[as_ref]
343 #[cfg_attr(feature = "test-utils", deref_mut)]
344 transaction: Transaction,
345}
346
347impl Default for TransactionSigned {
348 fn default() -> Self {
349 Self::new_unhashed(Transaction::Legacy(Default::default()), Signature::test_signature())
350 }
351}
352
353impl TransactionSigned {
354 fn recalculate_hash(&self) -> B256 {
355 keccak256(self.encoded_2718())
356 }
357}
358
359impl Hash for TransactionSigned {
360 fn hash<H: Hasher>(&self, state: &mut H) {
361 self.signature.hash(state);
362 self.transaction.hash(state);
363 }
364}
365
366impl PartialEq for TransactionSigned {
367 fn eq(&self, other: &Self) -> bool {
368 self.signature == other.signature &&
369 self.transaction == other.transaction &&
370 self.tx_hash() == other.tx_hash()
371 }
372}
373
374impl TransactionSigned {
375 pub fn new(transaction: Transaction, signature: Signature, hash: B256) -> Self {
377 Self { hash: hash.into(), signature, transaction }
378 }
379
380 #[inline]
382 pub fn into_transaction(self) -> Transaction {
383 self.transaction
384 }
385
386 #[inline]
388 pub const fn transaction(&self) -> &Transaction {
389 &self.transaction
390 }
391
392 #[inline]
394 pub fn hash(&self) -> &B256 {
395 self.hash.get_or_init(|| self.recalculate_hash())
396 }
397
398 #[inline]
400 pub const fn signature(&self) -> &Signature {
401 &self.signature
402 }
403
404 pub fn new_unhashed(transaction: Transaction, signature: Signature) -> Self {
408 Self { hash: Default::default(), signature, transaction }
409 }
410
411 pub fn split(self) -> (Transaction, Signature) {
413 (self.transaction, self.signature)
414 }
415
416 pub fn try_into_pooled_eip4844(
421 self,
422 sidecar: BlobTransactionSidecar,
423 ) -> Result<PooledTransaction, Self> {
424 let hash = *self.tx_hash();
425 Ok(match self {
426 Self { transaction: Transaction::Eip4844(tx), signature, .. } => {
428 PooledTransaction::Eip4844(Signed::new_unchecked(
430 TxEip4844WithSidecar { tx, sidecar },
431 signature,
432 hash,
433 ))
434 }
435 _ => return Err(self),
438 })
439 }
440
441 pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
443 match &self.transaction {
444 Transaction::Eip4844(tx) => Some(tx),
445 _ => None,
446 }
447 }
448
449 #[cfg(feature = "test-utils")]
451 pub fn transaction_mut(&mut self) -> &mut Transaction {
452 &mut self.transaction
453 }
454
455 pub fn into_parts(self) -> (Transaction, Signature, B256) {
457 let hash = *self.hash.get_or_init(|| self.recalculate_hash());
458 (self.transaction, self.signature, hash)
459 }
460}
461
462impl Typed2718 for TransactionSigned {
463 fn ty(&self) -> u8 {
464 self.transaction.ty()
465 }
466}
467
468impl alloy_consensus::Transaction for TransactionSigned {
469 fn chain_id(&self) -> Option<ChainId> {
470 self.transaction.chain_id()
471 }
472
473 fn nonce(&self) -> u64 {
474 self.transaction.nonce()
475 }
476
477 fn gas_limit(&self) -> u64 {
478 self.transaction.gas_limit()
479 }
480
481 fn gas_price(&self) -> Option<u128> {
482 self.transaction.gas_price()
483 }
484
485 fn max_fee_per_gas(&self) -> u128 {
486 self.transaction.max_fee_per_gas()
487 }
488
489 fn max_priority_fee_per_gas(&self) -> Option<u128> {
490 self.transaction.max_priority_fee_per_gas()
491 }
492
493 fn max_fee_per_blob_gas(&self) -> Option<u128> {
494 self.transaction.max_fee_per_blob_gas()
495 }
496
497 fn priority_fee_or_price(&self) -> u128 {
498 self.transaction.priority_fee_or_price()
499 }
500
501 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
502 self.transaction.effective_gas_price(base_fee)
503 }
504
505 fn is_dynamic_fee(&self) -> bool {
506 self.transaction.is_dynamic_fee()
507 }
508
509 fn kind(&self) -> TxKind {
510 self.transaction.kind()
511 }
512
513 fn is_create(&self) -> bool {
514 self.transaction.is_create()
515 }
516
517 fn value(&self) -> U256 {
518 self.transaction.value()
519 }
520
521 fn input(&self) -> &Bytes {
522 self.transaction.input()
523 }
524
525 fn access_list(&self) -> Option<&AccessList> {
526 self.transaction.access_list()
527 }
528
529 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
530 self.transaction.blob_versioned_hashes()
531 }
532
533 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
534 self.transaction.authorization_list()
535 }
536}
537
538impl_from_signed!(TxLegacy, TxEip2930, TxEip1559, TxEip7702, TxEip4844, TypedTransaction);
539
540impl From<Signed<Transaction>> for TransactionSigned {
541 fn from(value: Signed<Transaction>) -> Self {
542 let (tx, sig, hash) = value.into_parts();
543 Self::new(tx, sig, hash)
544 }
545}
546
547impl From<Signed<TxEip4844WithSidecar>> for TransactionSigned {
548 fn from(value: Signed<TxEip4844WithSidecar>) -> Self {
549 let (tx, sig, hash) = value.into_parts();
550 Self::new(tx.tx.into(), sig, hash)
551 }
552}
553
554impl From<TxEip4844Variant> for Transaction {
555 fn from(variant: TxEip4844Variant) -> Self {
556 match variant {
557 TxEip4844Variant::TxEip4844(tx) => Self::Eip4844(tx),
558 TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => {
559 Self::Eip4844(tx_with_sidecar.tx)
560 }
561 }
562 }
563}
564
565impl From<Signed<TxEip4844Variant>> for TransactionSigned {
566 fn from(value: Signed<TxEip4844Variant>) -> Self {
567 let (tx, sig, hash) = value.into_parts();
568 Self::new(tx.into(), sig, hash)
569 }
570}
571
572impl From<TxEnvelope> for TransactionSigned {
573 fn from(value: TxEnvelope) -> Self {
574 match value {
575 TxEnvelope::Legacy(tx) => tx.into(),
576 TxEnvelope::Eip2930(tx) => tx.into(),
577 TxEnvelope::Eip1559(tx) => tx.into(),
578 TxEnvelope::Eip4844(tx) => tx.into(),
579 TxEnvelope::Eip7702(tx) => tx.into(),
580 }
581 }
582}
583
584impl From<TransactionSigned> for TxEnvelope {
585 fn from(value: TransactionSigned) -> Self {
586 let (tx, signature, hash) = value.into_parts();
587 match tx {
588 Transaction::Legacy(tx) => Signed::new_unchecked(tx, signature, hash).into(),
589 Transaction::Eip2930(tx) => Signed::new_unchecked(tx, signature, hash).into(),
590 Transaction::Eip1559(tx) => Signed::new_unchecked(tx, signature, hash).into(),
591 Transaction::Eip4844(tx) => Signed::new_unchecked(tx, signature, hash).into(),
592 Transaction::Eip7702(tx) => Signed::new_unchecked(tx, signature, hash).into(),
593 }
594 }
595}
596
597impl From<TransactionSigned> for Signed<Transaction> {
598 fn from(value: TransactionSigned) -> Self {
599 let (tx, sig, hash) = value.into_parts();
600 Self::new_unchecked(tx, sig, hash)
601 }
602}
603
604#[cfg(any(test, feature = "arbitrary"))]
605impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
606 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
607 #[allow(unused_mut)]
608 let mut transaction = Transaction::arbitrary(u)?;
609
610 let secp = secp256k1::Secp256k1::new();
611 let key_pair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
612 let signature = reth_primitives_traits::crypto::secp256k1::sign_message(
613 B256::from_slice(&key_pair.secret_bytes()[..]),
614 transaction.signature_hash(),
615 )
616 .unwrap();
617
618 Ok(Self { transaction, signature, hash: Default::default() })
619 }
620}
621
622impl InMemorySize for TransactionSigned {
623 fn size(&self) -> usize {
624 let Self { hash: _, signature, transaction } = self;
625 self.tx_hash().size() + signature.size() + transaction.size()
626 }
627}
628
629impl Encodable2718 for TransactionSigned {
630 fn type_flag(&self) -> Option<u8> {
631 (!self.transaction.is_legacy()).then(|| self.ty())
632 }
633
634 fn encode_2718_len(&self) -> usize {
635 delegate!(&self.transaction => tx.eip2718_encoded_length(&self.signature))
636 }
637
638 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
639 delegate!(&self.transaction => tx.eip2718_encode(&self.signature, out))
640 }
641
642 fn trie_hash(&self) -> B256 {
643 *self.tx_hash()
644 }
645}
646
647impl Decodable2718 for TransactionSigned {
648 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
649 match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
650 TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
651 TxType::Eip2930 => {
652 let (tx, signature) = TxEip2930::rlp_decode_with_signature(buf)?;
653 Ok(Self {
654 transaction: Transaction::Eip2930(tx),
655 signature,
656 hash: Default::default(),
657 })
658 }
659 TxType::Eip1559 => {
660 let (tx, signature) = TxEip1559::rlp_decode_with_signature(buf)?;
661 Ok(Self {
662 transaction: Transaction::Eip1559(tx),
663 signature,
664 hash: Default::default(),
665 })
666 }
667 TxType::Eip4844 => {
668 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
669 Ok(Self {
670 transaction: Transaction::Eip4844(tx),
671 signature,
672 hash: Default::default(),
673 })
674 }
675 TxType::Eip7702 => {
676 let (tx, signature) = TxEip7702::rlp_decode_with_signature(buf)?;
677 Ok(Self {
678 transaction: Transaction::Eip7702(tx),
679 signature,
680 hash: Default::default(),
681 })
682 }
683 }
684 }
685
686 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
687 let (tx, signature) = TxLegacy::rlp_decode_with_signature(buf)?;
688 Ok(Self { transaction: Transaction::Legacy(tx), signature, hash: Default::default() })
689 }
690}
691
692impl Encodable for TransactionSigned {
693 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
694 self.network_encode(out);
695 }
696
697 fn length(&self) -> usize {
698 self.network_len()
699 }
700}
701
702impl Decodable for TransactionSigned {
703 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
704 Self::network_decode(buf).map_err(Into::into)
705 }
706}
707
708#[cfg(any(test, feature = "reth-codec"))]
709impl reth_codecs::Compact for TransactionSigned {
710 fn to_compact<B>(&self, buf: &mut B) -> usize
711 where
712 B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
713 {
714 use alloy_consensus::Transaction;
715
716 let start = buf.as_mut().len();
717
718 buf.put_u8(0);
721
722 let sig_bit = self.signature.to_compact(buf) as u8;
723 let zstd_bit = self.transaction.input().len() >= 32;
724
725 let tx_bits = if zstd_bit {
726 let mut tmp = Vec::with_capacity(256);
727 if cfg!(feature = "std") {
728 reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
729 let mut compressor = compressor.borrow_mut();
730 let tx_bits = self.transaction.to_compact(&mut tmp);
731 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
732 tx_bits as u8
733 })
734 } else {
735 let mut compressor = reth_zstd_compressors::create_tx_compressor();
736 let tx_bits = self.transaction.to_compact(&mut tmp);
737 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
738 tx_bits as u8
739 }
740 } else {
741 self.transaction.to_compact(buf) as u8
742 };
743
744 buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
746
747 buf.as_mut().len() - start
748 }
749
750 fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
751 use alloy_rlp::bytes::Buf;
752
753 let bitflags = buf.get_u8() as usize;
755
756 let sig_bit = bitflags & 1;
757 let (signature, buf) = Signature::from_compact(buf, sig_bit);
758
759 let zstd_bit = bitflags >> 3;
760 let (transaction, buf) = if zstd_bit != 0 {
761 if cfg!(feature = "std") {
762 reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
763 let mut decompressor = decompressor.borrow_mut();
764
765 let transaction_type = (bitflags & 0b110) >> 1;
768 let (transaction, _) =
769 Transaction::from_compact(decompressor.decompress(buf), transaction_type);
770
771 (transaction, buf)
772 })
773 } else {
774 let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
775 let transaction_type = (bitflags & 0b110) >> 1;
776 let (transaction, _) =
777 Transaction::from_compact(decompressor.decompress(buf), transaction_type);
778
779 (transaction, buf)
780 }
781 } else {
782 let transaction_type = bitflags >> 1;
783 Transaction::from_compact(buf, transaction_type)
784 };
785
786 (Self { signature, transaction, hash: Default::default() }, buf)
787 }
788}
789
790impl FromRecoveredTx<TransactionSigned> for TxEnv {
791 fn from_recovered_tx(tx: &TransactionSigned, sender: Address) -> Self {
792 match tx.as_ref() {
793 Transaction::Legacy(tx) => Self {
794 gas_limit: tx.gas_limit,
795 gas_price: tx.gas_price,
796 gas_priority_fee: None,
797 kind: tx.to,
798 value: tx.value,
799 data: tx.input.clone(),
800 chain_id: tx.chain_id,
801 nonce: tx.nonce,
802 access_list: Default::default(),
803 blob_hashes: Default::default(),
804 max_fee_per_blob_gas: Default::default(),
805 authorization_list: Default::default(),
806 tx_type: 0,
807 caller: sender,
808 },
809 Transaction::Eip2930(tx) => Self {
810 gas_limit: tx.gas_limit,
811 gas_price: tx.gas_price,
812 gas_priority_fee: None,
813 kind: tx.to,
814 value: tx.value,
815 data: tx.input.clone(),
816 chain_id: Some(tx.chain_id),
817 nonce: tx.nonce,
818 access_list: tx.access_list.clone(),
819 blob_hashes: Default::default(),
820 max_fee_per_blob_gas: Default::default(),
821 authorization_list: Default::default(),
822 tx_type: 1,
823 caller: sender,
824 },
825 Transaction::Eip1559(tx) => Self {
826 gas_limit: tx.gas_limit,
827 gas_price: tx.max_fee_per_gas,
828 gas_priority_fee: Some(tx.max_priority_fee_per_gas),
829 kind: tx.to,
830 value: tx.value,
831 data: tx.input.clone(),
832 chain_id: Some(tx.chain_id),
833 nonce: tx.nonce,
834 access_list: tx.access_list.clone(),
835 blob_hashes: Default::default(),
836 max_fee_per_blob_gas: Default::default(),
837 authorization_list: Default::default(),
838 tx_type: 2,
839 caller: sender,
840 },
841 Transaction::Eip4844(tx) => Self {
842 gas_limit: tx.gas_limit,
843 gas_price: tx.max_fee_per_gas,
844 gas_priority_fee: Some(tx.max_priority_fee_per_gas),
845 kind: TxKind::Call(tx.to),
846 value: tx.value,
847 data: tx.input.clone(),
848 chain_id: Some(tx.chain_id),
849 nonce: tx.nonce,
850 access_list: tx.access_list.clone(),
851 blob_hashes: tx.blob_versioned_hashes.clone(),
852 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
853 authorization_list: Default::default(),
854 tx_type: 3,
855 caller: sender,
856 },
857 Transaction::Eip7702(tx) => Self {
858 gas_limit: tx.gas_limit,
859 gas_price: tx.max_fee_per_gas,
860 gas_priority_fee: Some(tx.max_priority_fee_per_gas),
861 kind: TxKind::Call(tx.to),
862 value: tx.value,
863 data: tx.input.clone(),
864 chain_id: Some(tx.chain_id),
865 nonce: tx.nonce,
866 access_list: tx.access_list.clone(),
867 blob_hashes: Default::default(),
868 max_fee_per_blob_gas: Default::default(),
869 authorization_list: tx.authorization_list.clone(),
870 tx_type: 4,
871 caller: sender,
872 },
873 }
874 }
875}
876
877impl SignedTransaction for TransactionSigned {
878 fn tx_hash(&self) -> &TxHash {
879 self.hash.get_or_init(|| self.recalculate_hash())
880 }
881
882 fn signature(&self) -> &Signature {
883 &self.signature
884 }
885
886 fn recover_signer(&self) -> Result<Address, RecoveryError> {
887 let signature_hash = self.signature_hash();
888 recover_signer(&self.signature, signature_hash)
889 }
890
891 fn recover_signer_unchecked_with_buf(
892 &self,
893 buf: &mut Vec<u8>,
894 ) -> Result<Address, RecoveryError> {
895 self.encode_for_signing(buf);
896 let signature_hash = keccak256(buf);
897 recover_signer_unchecked(&self.signature, signature_hash)
898 }
899}
900
901impl TryFrom<TransactionSigned> for PooledTransaction {
902 type Error = TransactionConversionError;
903
904 fn try_from(tx: TransactionSigned) -> Result<Self, Self::Error> {
905 let hash = *tx.tx_hash();
906 match tx {
907 TransactionSigned { transaction: Transaction::Legacy(tx), signature, .. } => {
908 Ok(Self::Legacy(Signed::new_unchecked(tx, signature, hash)))
909 }
910 TransactionSigned { transaction: Transaction::Eip2930(tx), signature, .. } => {
911 Ok(Self::Eip2930(Signed::new_unchecked(tx, signature, hash)))
912 }
913 TransactionSigned { transaction: Transaction::Eip1559(tx), signature, .. } => {
914 Ok(Self::Eip1559(Signed::new_unchecked(tx, signature, hash)))
915 }
916 TransactionSigned { transaction: Transaction::Eip7702(tx), signature, .. } => {
917 Ok(Self::Eip7702(Signed::new_unchecked(tx, signature, hash)))
918 }
919 TransactionSigned { transaction: Transaction::Eip4844(_), .. } => {
921 Err(TransactionConversionError::UnsupportedForP2P)
922 }
923 }
924 }
925}
926
927impl From<PooledTransaction> for TransactionSigned {
928 fn from(value: PooledTransaction) -> Self {
929 match value {
930 PooledTransaction::Legacy(tx) => tx.into(),
931 PooledTransaction::Eip2930(tx) => tx.into(),
932 PooledTransaction::Eip1559(tx) => tx.into(),
933 PooledTransaction::Eip7702(tx) => tx.into(),
934 PooledTransaction::Eip4844(tx) => {
935 let (tx, signature, hash) = tx.into_parts();
936 Signed::new_unchecked(tx.tx, signature, hash).into()
937 }
938 }
939 }
940}
941
942#[cfg(feature = "serde-bincode-compat")]
944pub(super) mod serde_bincode_compat {
945 use alloc::borrow::Cow;
946 use alloy_consensus::{
947 transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxEip7702, TxLegacy},
948 TxEip4844,
949 };
950 use alloy_primitives::{PrimitiveSignature as Signature, TxHash};
951 use reth_primitives_traits::{serde_bincode_compat::SerdeBincodeCompat, SignedTransaction};
952 use serde::{Deserialize, Serialize};
953
954 #[derive(Debug, Serialize, Deserialize)]
956 #[allow(missing_docs)]
957 pub enum Transaction<'a> {
958 Legacy(TxLegacy<'a>),
959 Eip2930(TxEip2930<'a>),
960 Eip1559(TxEip1559<'a>),
961 Eip4844(Cow<'a, TxEip4844>),
962 Eip7702(TxEip7702<'a>),
963 }
964
965 impl<'a> From<&'a super::Transaction> for Transaction<'a> {
966 fn from(value: &'a super::Transaction) -> Self {
967 match value {
968 super::Transaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)),
969 super::Transaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)),
970 super::Transaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)),
971 super::Transaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
972 super::Transaction::Eip7702(tx) => Self::Eip7702(TxEip7702::from(tx)),
973 }
974 }
975 }
976
977 impl<'a> From<Transaction<'a>> for super::Transaction {
978 fn from(value: Transaction<'a>) -> Self {
979 match value {
980 Transaction::Legacy(tx) => Self::Legacy(tx.into()),
981 Transaction::Eip2930(tx) => Self::Eip2930(tx.into()),
982 Transaction::Eip1559(tx) => Self::Eip1559(tx.into()),
983 Transaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
984 Transaction::Eip7702(tx) => Self::Eip7702(tx.into()),
985 }
986 }
987 }
988
989 #[derive(Debug, Serialize, Deserialize)]
991 pub struct TransactionSigned<'a> {
992 hash: TxHash,
993 signature: Signature,
994 transaction: Transaction<'a>,
995 }
996
997 impl<'a> From<&'a super::TransactionSigned> for TransactionSigned<'a> {
998 fn from(value: &'a super::TransactionSigned) -> Self {
999 Self {
1000 hash: *value.tx_hash(),
1001 signature: value.signature,
1002 transaction: Transaction::from(&value.transaction),
1003 }
1004 }
1005 }
1006
1007 impl<'a> From<TransactionSigned<'a>> for super::TransactionSigned {
1008 fn from(value: TransactionSigned<'a>) -> Self {
1009 Self {
1010 hash: value.hash.into(),
1011 signature: value.signature,
1012 transaction: value.transaction.into(),
1013 }
1014 }
1015 }
1016 impl SerdeBincodeCompat for super::TransactionSigned {
1017 type BincodeRepr<'a> = TransactionSigned<'a>;
1018
1019 fn as_repr(&self) -> Self::BincodeRepr<'_> {
1020 self.into()
1021 }
1022
1023 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
1024 repr.into()
1025 }
1026 }
1027
1028 #[cfg(test)]
1029 mod tests {
1030 use super::super::{serde_bincode_compat, Transaction, TransactionSigned};
1031 use arbitrary::Arbitrary;
1032 use rand::Rng;
1033 use reth_testing_utils::generators;
1034 use serde::{Deserialize, Serialize};
1035
1036 #[test]
1037 fn test_transaction_bincode_roundtrip() {
1038 #[derive(Debug, Serialize, Deserialize)]
1039 struct Data<'a> {
1040 transaction: serde_bincode_compat::Transaction<'a>,
1041 }
1042
1043 let mut bytes = [0u8; 1024];
1044 generators::rng().fill(bytes.as_mut_slice());
1045 let tx = Transaction::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1046 let data = Data { transaction: (&tx).into() };
1047
1048 let encoded = bincode::serialize(&data).unwrap();
1049 let decoded: Data<'_> = bincode::deserialize(&encoded).unwrap();
1050 assert_eq!(tx, decoded.transaction.into());
1051 }
1052
1053 #[test]
1054 fn test_transaction_signed_bincode_roundtrip() {
1055 #[derive(Debug, Serialize, Deserialize)]
1056 struct Data<'a> {
1057 transaction: serde_bincode_compat::TransactionSigned<'a>,
1058 }
1059
1060 let mut bytes = [0u8; 1024];
1061 generators::rng().fill(bytes.as_mut_slice());
1062 let tx =
1063 TransactionSigned::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1064 let data = Data { transaction: (&tx).into() };
1065
1066 let encoded = bincode::serialize(&data).unwrap();
1067 let decoded: Data<'_> = bincode::deserialize(&encoded).unwrap();
1068 assert_eq!(tx, decoded.transaction.into());
1069 }
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076 use alloy_consensus::{
1077 constants::LEGACY_TX_TYPE_ID, Block, Transaction as _, TxEip1559, TxLegacy,
1078 };
1079 use alloy_eips::{
1080 eip2718::{Decodable2718, Encodable2718},
1081 eip7702::constants::SECP256K1N_HALF,
1082 };
1083 use alloy_primitives::{
1084 address, b256, bytes, hex, Address, Bytes, PrimitiveSignature as Signature, TxKind, B256,
1085 U256,
1086 };
1087 use alloy_rlp::{Decodable, Encodable, Error as RlpError};
1088 use reth_codecs::Compact;
1089 use reth_primitives_traits::SignedTransaction;
1090 use std::str::FromStr;
1091
1092 #[test]
1093 fn eip_2_reject_high_s_value() {
1094 let raw_tx = hex!("f86d8085746a52880082520894c93f2250589a6563f5359051c1ea25746549f0d889208686e75e903bc000801ba034b6fdc33ea520e8123cf5ac4a9ff476f639cab68980cd9366ccae7aef437ea0a0e517caa5f50e27ca0d1e9a92c503b4ccb039680c6d9d0c71203ed611ea4feb33");
1100 let tx = TransactionSigned::decode_2718(&mut &raw_tx[..]).unwrap();
1101 let signature = tx.signature();
1102
1103 assert!(signature.s() > SECP256K1N_HALF);
1105
1106 let hash = *tx.tx_hash();
1108 assert!(recover_signer(signature, hash).is_err());
1109
1110 assert!(recover_signer_unchecked(signature, hash).is_ok());
1112 }
1113
1114 #[test]
1115 fn encode_decode_raw_block() {
1116 let bytes = hex!("f90288f90218a0fe21bb173f43067a9f90cfc59bbb6830a7a2929b5de4a61f372a9db28e87f9aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a061effbbcca94f0d3e02e5bd22e986ad57142acabf0cb3d129a6ad8d0f8752e94a0d911c25e97e27898680d242b7780b6faef30995c355a2d5de92e6b9a7212ad3aa0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008003834c4b408252081e80a00000000000000000000000000000000000000000000000000000000000000000880000000000000000842806be9da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421f869f86702842806be9e82520894658bdf435d810c91414ec09147daa6db624063798203e880820a95a040ce7918eeb045ebf8c8b1887ca139d076bda00fa828a07881d442a72626c42da0156576a68e456e295e4c9cf67cf9f53151f329438916e0f24fc69d6bbb7fbacfc0c0");
1117 let bytes_buf = &mut bytes.as_ref();
1118 let block = Block::<TransactionSigned>::decode(bytes_buf).unwrap();
1119 let mut encoded_buf = Vec::with_capacity(bytes.len());
1120 block.encode(&mut encoded_buf);
1121 assert_eq!(bytes[..], encoded_buf);
1122 }
1123
1124 #[test]
1125 fn empty_block_rlp() {
1126 let body = alloy_consensus::BlockBody::<TransactionSigned>::default();
1127 let mut buf = Vec::new();
1128 body.encode(&mut buf);
1129 let decoded = alloy_consensus::BlockBody::decode(&mut buf.as_slice()).unwrap();
1130 assert_eq!(body, decoded);
1131 }
1132
1133 #[test]
1134 fn test_decode_empty_typed_tx() {
1135 let input = [0x80u8];
1136 let res = TransactionSigned::decode(&mut &input[..]).unwrap_err();
1137 assert_eq!(RlpError::InputTooShort, res);
1138 }
1139
1140 #[test]
1141 fn raw_kind_encoding_sanity() {
1142 let mut buf = Vec::new();
1144 TxKind::Create.encode(&mut buf);
1145 assert_eq!(buf, vec![0x80]);
1146
1147 let buf = [0x80];
1149 let decoded = TxKind::decode(&mut &buf[..]).unwrap();
1150 assert_eq!(decoded, TxKind::Create);
1151 }
1152
1153 #[test]
1154 fn test_decode_create_goerli() {
1155 let tx_bytes = hex!("b901f202f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471");
1157
1158 let decoded = TransactionSigned::decode(&mut &tx_bytes[..]).unwrap();
1159 assert_eq!(tx_bytes.len(), decoded.length());
1160 assert_eq!(tx_bytes, &alloy_rlp::encode(decoded)[..]);
1161 }
1162
1163 #[test]
1164 fn test_decode_recover_mainnet_tx() {
1165 let tx_bytes = hex!("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9");
1167
1168 let decoded = TransactionSigned::decode_2718(&mut &tx_bytes[..]).unwrap();
1169 assert_eq!(
1170 decoded.recover_signer().unwrap(),
1171 address!("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5")
1172 );
1173 }
1174
1175 #[test]
1176 fn test_decode_recover_sepolia_4844_tx() {
1179 use alloy_primitives::{address, b256};
1180
1181 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1183 let decoded = TransactionSigned::decode_2718(&mut raw_tx.as_slice()).unwrap();
1184 assert!(alloy_consensus::Typed2718::is_eip4844(&decoded));
1185
1186 assert_eq!(
1187 decoded.recover_signer().ok(),
1188 Some(address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"))
1189 );
1190
1191 let tx = decoded.transaction;
1192
1193 assert_eq!(tx.to(), Some(address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064")));
1194
1195 assert_eq!(
1196 tx.blob_versioned_hashes(),
1197 Some(
1198 &[
1199 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1200 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1201 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1202 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1203 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549"),
1204 ][..]
1205 )
1206 );
1207 }
1208
1209 #[test]
1210 fn decode_transaction_consumes_buffer() {
1211 let bytes = &mut &hex!("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")[..];
1212 let _transaction_res = TransactionSigned::decode(bytes).unwrap();
1213 assert_eq!(
1214 bytes.len(),
1215 0,
1216 "did not consume all bytes in the buffer, {:?} remaining",
1217 bytes.len()
1218 );
1219 }
1220
1221 #[test]
1222 fn decode_multiple_network_txs() {
1223 let bytes = hex!("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18");
1224 let transaction = Transaction::Legacy(TxLegacy {
1225 chain_id: Some(4u64),
1226 nonce: 2,
1227 gas_price: 1000000000,
1228 gas_limit: 100000,
1229 to: Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap().into(),
1230 value: U256::from(1000000000000000u64),
1231 input: Bytes::default(),
1232 });
1233 let signature = Signature::new(
1234 U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
1235 .unwrap(),
1236 U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
1237 .unwrap(),
1238 false,
1239 );
1240 let hash = b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34");
1241 test_decode_and_encode(&bytes, transaction, signature, Some(hash));
1242
1243 let bytes = hex!("f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da");
1244 let transaction = Transaction::Legacy(TxLegacy {
1245 chain_id: Some(4),
1246 nonce: 1u64,
1247 gas_price: 1000000000,
1248 gas_limit: 100000,
1249 to: Address::from_slice(&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..]).into(),
1250 value: U256::from(693361000000000u64),
1251 input: Default::default(),
1252 });
1253 let signature = Signature::new(
1254 U256::from_str("0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a")
1255 .unwrap(),
1256 U256::from_str("0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da")
1257 .unwrap(),
1258 false,
1259 );
1260 test_decode_and_encode(&bytes, transaction, signature, None);
1261
1262 let bytes = hex!("f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88");
1263 let transaction = Transaction::Legacy(TxLegacy {
1264 chain_id: Some(4),
1265 nonce: 3,
1266 gas_price: 2000000000,
1267 gas_limit: 10000000,
1268 to: Address::from_slice(&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..]).into(),
1269 value: U256::from(1000000000000000u64),
1270 input: Bytes::default(),
1271 });
1272 let signature = Signature::new(
1273 U256::from_str("0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071")
1274 .unwrap(),
1275 U256::from_str("0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88")
1276 .unwrap(),
1277 false,
1278 );
1279 test_decode_and_encode(&bytes, transaction, signature, None);
1280
1281 let bytes = hex!("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469");
1282 let transaction = Transaction::Eip1559(TxEip1559 {
1283 chain_id: 4,
1284 nonce: 26,
1285 max_priority_fee_per_gas: 1500000000,
1286 max_fee_per_gas: 1500000013,
1287 gas_limit: 21_000,
1288 to: Address::from_slice(&hex!("61815774383099e24810ab832a5b2a5425c154d5")[..]).into(),
1289 value: U256::from(3000000000000000000u64),
1290 input: Default::default(),
1291 access_list: Default::default(),
1292 });
1293 let signature = Signature::new(
1294 U256::from_str("0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd")
1295 .unwrap(),
1296 U256::from_str("0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")
1297 .unwrap(),
1298 true,
1299 );
1300 test_decode_and_encode(&bytes, transaction, signature, None);
1301
1302 let bytes = hex!("f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860");
1303 let transaction = Transaction::Legacy(TxLegacy {
1304 chain_id: Some(4),
1305 nonce: 15,
1306 gas_price: 2200000000,
1307 gas_limit: 34811,
1308 to: Address::from_slice(&hex!("cf7f9e66af820a19257a2108375b180b0ec49167")[..]).into(),
1309 value: U256::from(1234),
1310 input: Bytes::default(),
1311 });
1312 let signature = Signature::new(
1313 U256::from_str("0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981")
1314 .unwrap(),
1315 U256::from_str("0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860")
1316 .unwrap(),
1317 true,
1318 );
1319 test_decode_and_encode(&bytes, transaction, signature, None);
1320 }
1321
1322 fn test_decode_and_encode(
1323 bytes: &[u8],
1324 transaction: Transaction,
1325 signature: Signature,
1326 hash: Option<B256>,
1327 ) {
1328 let expected = TransactionSigned::new_unhashed(transaction, signature);
1329 if let Some(hash) = hash {
1330 assert_eq!(hash, *expected.tx_hash());
1331 }
1332 assert_eq!(bytes.len(), expected.length());
1333
1334 let decoded = TransactionSigned::decode(&mut &bytes[..]).unwrap();
1335 assert_eq!(expected, decoded);
1336 assert_eq!(bytes, &alloy_rlp::encode(expected));
1337 }
1338
1339 #[test]
1340 fn decode_raw_tx_and_recover_signer() {
1341 use alloy_primitives::hex_literal::hex;
1342 let hash: B256 =
1345 hex!("559fb34c4a7f115db26cbf8505389475caaab3df45f5c7a0faa4abfa3835306c").into();
1346 let signer: Address = hex!("641c5d790f862a58ec7abcfd644c0442e9c201b3").into();
1347 let raw = hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2");
1348
1349 let mut pointer = raw.as_ref();
1350 let tx = TransactionSigned::decode(&mut pointer).unwrap();
1351 assert_eq!(*tx.tx_hash(), hash, "Expected same hash");
1352 let recovered = tx.recover_signer().expect("Recovering signer should pass");
1353 assert_eq!(recovered, signer);
1354 }
1355
1356 #[test]
1357 fn test_envelop_encode() {
1358 let input = hex!("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76");
1360 let decoded = TransactionSigned::decode(&mut &input[..]).unwrap();
1361
1362 let encoded = decoded.encoded_2718();
1363 assert_eq!(encoded[..], input);
1364 }
1365
1366 #[test]
1367 fn test_envelop_decode() {
1368 let input = bytes!("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76");
1370 let decoded = TransactionSigned::decode_2718(&mut input.as_ref()).unwrap();
1371
1372 let encoded = decoded.encoded_2718();
1373 assert_eq!(encoded, input);
1374 }
1375
1376 #[test]
1377 fn test_decode_tx() {
1378 let data = hex!("b86f02f86c0705843b9aca008506fc23ac00830124f89400000000000000000000000000000000000003160180c001a00293c713e2f1eab91c366621ff2f867e05ad7e99d4aa5d069aafeb9e1e8c9b6aa05ec6c0605ff20b57c90a6484ec3b0509e5923733d06f9b69bee9a2dabe4f1352");
1380 let tx = TransactionSigned::decode(&mut data.as_slice()).unwrap();
1381 let mut b = Vec::with_capacity(data.len());
1382 tx.encode(&mut b);
1383 assert_eq!(data.as_slice(), b.as_slice());
1384
1385 let data = hex!("f865048506fc23ac00830124f8940000000000000000000000000000000000000316018032a06b8fdfdcb84790816b7af85b19305f493665fe8b4e7c51ffdd7cc144cd776a60a028a09ab55def7b8d6602ba1c97a0ebbafe64ffc9c8e89520cec97a8edfb2ebe9");
1386 let tx = TransactionSigned::decode(&mut data.as_slice()).unwrap();
1387 let mut b = Vec::with_capacity(data.len());
1388 tx.encode(&mut b);
1389 assert_eq!(data.as_slice(), b.as_slice());
1390 }
1391
1392 #[test]
1394 fn recover_legacy_singer() {
1395 let data = hex!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
1396 let tx = TransactionSigned::fallback_decode(&mut data.as_slice()).unwrap();
1397 assert_eq!(tx.ty(), LEGACY_TX_TYPE_ID);
1398 let sender = tx.recover_signer().unwrap();
1399 assert_eq!(sender, address!("0xa12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
1400 }
1401
1402 #[test]
1405 fn recover_enveloped() {
1406 let data = hex!("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8");
1407 let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap();
1408 let sender = tx.recover_signer().unwrap();
1409 assert_eq!(sender, address!("0x001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
1410 assert_eq!(tx.to(), Some(address!("0xD9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
1411 assert_eq!(tx.input().as_ref(), hex!("1b55ba3a"));
1412 let encoded = tx.encoded_2718();
1413 assert_eq!(encoded.as_ref(), data.to_vec());
1414 }
1415
1416 #[test]
1419 fn recover_pre_eip2() {
1420 let data = hex!("f8ea0c850ba43b7400832dc6c0942935aa0a2d2fbb791622c29eb1c117b65b7a908580b884590528a9000000000000000000000001878ace42092b7f1ae1f28d16c1272b1aa80ca4670000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000d02ab486cedc0000000000000000000000000000000000000000000000000000557fe293cabc08cf1ca05bfaf3fda0a56b49cc78b22125feb5ae6a99d2b4781f00507d8b02c173771c85a0b5da0dbe6c5bc53740d0071fc83eb17ba0f709e49e9ae7df60dee625ef51afc5");
1421 let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap();
1422 let sender = tx.recover_signer();
1423 assert!(sender.is_err());
1424 let sender = tx.recover_signer_unchecked().unwrap();
1425
1426 assert_eq!(sender, address!("0x7e9e359edf0dbacf96a9952fa63092d919b0842b"));
1427 }
1428
1429 #[test]
1430 fn transaction_signed_no_hash_zstd_codec() {
1431 let signature = Signature::new(
1434 U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
1435 .unwrap(),
1436 U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
1437 .unwrap(),
1438 false,
1439 );
1440
1441 let inputs: Vec<Vec<u8>> = vec![
1442 vec![],
1443 vec![0],
1444 vec![255],
1445 vec![1u8; 31],
1446 vec![255u8; 31],
1447 vec![1u8; 32],
1448 vec![255u8; 32],
1449 vec![1u8; 64],
1450 vec![255u8; 64],
1451 ];
1452
1453 for input in inputs {
1454 let transaction = Transaction::Legacy(TxLegacy {
1455 chain_id: Some(4u64),
1456 nonce: 2,
1457 gas_price: 1000000000,
1458 gas_limit: 100000,
1459 to: Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap().into(),
1460 value: U256::from(1000000000000000u64),
1461 input: Bytes::from(input),
1462 });
1463
1464 let tx = TransactionSigned::new_unhashed(transaction, signature);
1465 test_transaction_signed_to_from_compact(tx);
1466 }
1467 }
1468
1469 fn test_transaction_signed_to_from_compact(tx: TransactionSigned) {
1470 let mut buff: Vec<u8> = Vec::new();
1472 let written_bytes = tx.to_compact(&mut buff);
1473 let (decoded, _) = TransactionSigned::from_compact(&buff, written_bytes);
1474 assert_eq!(tx, decoded);
1475 }
1476
1477 #[test]
1478 fn create_txs_disallowed_for_eip4844() {
1479 let data =
1480 [3, 208, 128, 128, 123, 128, 120, 128, 129, 129, 128, 192, 129, 129, 192, 128, 128, 9];
1481 let res = TransactionSigned::decode_2718(&mut &data[..]);
1482
1483 assert!(res.is_err());
1484 }
1485}