1use alloc::vec::Vec;
4use alloy_consensus::{
5 transaction::{RlpEcdsaTx, TxSeismicElements},
6 SignableTransaction, Signed, Transaction as _, TxEip1559, TxEip2930, TxEip4844,
7 TxEip4844Variant, TxEip7702, TxLegacy, TxSeismic, Typed2718, TypedTransaction,
8};
9use alloy_eips::{
10 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
11 eip2930::AccessList,
12 eip712::{Decodable712, Eip712Result, TypedDataRequest},
13 eip7702::SignedAuthorization,
14};
15use alloy_primitives::{
16 keccak256, Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
17};
18use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
19use alloy_rpc_types_eth::TransactionRequest;
20use core::hash::{Hash, Hasher};
21use derive_more::{AsRef, Deref};
22use once_cell as _;
23#[cfg(not(feature = "std"))]
24use once_cell::sync::{Lazy as LazyLock, OnceCell as OnceLock};
25#[cfg(feature = "optimism")]
26use op_alloy_consensus::DepositTransaction;
27#[cfg(feature = "optimism")]
28use op_alloy_consensus::TxDeposit;
29use rayon::prelude::{IntoParallelIterator, ParallelIterator};
30use reth_primitives_traits::{InMemorySize, SignedTransaction};
31use reth_tracing::tracing::*;
32use revm_primitives::{AuthorizationList, TxEnv};
33use serde::{Deserialize, Serialize};
34use signature::decode_with_eip155_chain_id;
35#[cfg(feature = "std")]
36use std::sync::{LazyLock, OnceLock};
37
38pub use compat::FillTxEnv;
39pub use meta::TransactionMeta;
40pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered};
41pub use reth_primitives_traits::{
42 transaction::error::{
43 InvalidTransactionError, TransactionConversionError, TryFromRecoveredTransactionError,
44 },
45 WithEncoded,
46};
47pub use sidecar::BlobTransaction;
48pub use signature::{recover_signer, recover_signer_unchecked};
49pub use tx_type::TxType;
50
51pub mod signature;
54pub mod util;
55
56pub(crate) mod access_list;
57mod compat;
58mod meta;
59mod pooled;
60mod sidecar;
61mod tx_type;
62
63#[cfg(any(test, feature = "reth-codec"))]
64pub use tx_type::{
65 COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930,
66 COMPACT_IDENTIFIER_LEGACY,
67};
68
69pub static PARALLEL_SENDER_RECOVERY_THRESHOLD: LazyLock<usize> =
72 LazyLock::new(|| match rayon::current_num_threads() {
73 0..=1 => usize::MAX,
74 2..=8 => 10,
75 _ => 5,
76 });
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, derive_more::From)]
82#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
83pub enum Transaction {
84 Legacy(TxLegacy),
92 Eip2930(TxEip2930),
98 Eip1559(TxEip1559),
111 Eip4844(TxEip4844),
123 Eip7702(TxEip7702),
129 #[cfg(feature = "optimism")]
131 Deposit(TxDeposit),
132 Seismic(TxSeismic),
134}
135
136#[cfg(feature = "optimism")]
137impl DepositTransaction for Transaction {
138 fn source_hash(&self) -> Option<B256> {
139 match self {
140 Self::Deposit(tx) => tx.source_hash(),
141 _ => None,
142 }
143 }
144 fn mint(&self) -> Option<u128> {
145 match self {
146 Self::Deposit(tx) => tx.mint(),
147 _ => None,
148 }
149 }
150 fn is_system_transaction(&self) -> bool {
151 match self {
152 Self::Deposit(tx) => tx.is_system_transaction(),
153 _ => false,
154 }
155 }
156 fn is_deposit(&self) -> bool {
157 matches!(self, Self::Deposit(_))
158 }
159}
160
161#[cfg(any(test, feature = "arbitrary"))]
162impl<'a> arbitrary::Arbitrary<'a> for Transaction {
163 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
164 let mut tx = match TxType::arbitrary(u)? {
165 TxType::Legacy => {
166 let tx = TxLegacy::arbitrary(u)?;
167 Self::Legacy(tx)
168 }
169 TxType::Eip2930 => {
170 let tx = TxEip2930::arbitrary(u)?;
171 Self::Eip2930(tx)
172 }
173 TxType::Eip1559 => {
174 let tx = TxEip1559::arbitrary(u)?;
175 Self::Eip1559(tx)
176 }
177 TxType::Eip4844 => {
178 let tx = TxEip4844::arbitrary(u)?;
179 Self::Eip4844(tx)
180 }
181
182 TxType::Eip7702 => {
183 let tx = TxEip7702::arbitrary(u)?;
184 Self::Eip7702(tx)
185 }
186 TxType::Seismic => {
187 let tx = TxSeismic::arbitrary(u)?;
188 Self::Seismic(tx)
189 }
190 #[cfg(feature = "optimism")]
191 TxType::Deposit => {
192 let tx = TxDeposit::arbitrary(u)?;
193 Self::Deposit(tx)
194 }
195 };
196
197 if let Some(chain_id) = tx.chain_id() {
199 tx.set_chain_id(chain_id % (u64::MAX / 2 - 36));
200 }
201
202 Ok(tx)
203 }
204}
205
206impl Typed2718 for Transaction {
207 fn ty(&self) -> u8 {
208 match self {
209 Self::Legacy(tx) => tx.ty(),
210 Self::Eip2930(tx) => tx.ty(),
211 Self::Eip1559(tx) => tx.ty(),
212 Self::Eip4844(tx) => tx.ty(),
213 Self::Eip7702(tx) => tx.ty(),
214 Self::Seismic(tx) => tx.ty(),
215 #[cfg(feature = "optimism")]
216 Self::Deposit(tx) => tx.ty(),
217 }
218 }
219}
220
221impl Transaction {
224 pub fn signature_hash(&self) -> B256 {
227 match self {
228 Self::Legacy(tx) => tx.signature_hash(),
229 Self::Eip2930(tx) => tx.signature_hash(),
230 Self::Eip1559(tx) => tx.signature_hash(),
231 Self::Eip4844(tx) => tx.signature_hash(),
232 Self::Eip7702(tx) => tx.signature_hash(),
233 Self::Seismic(tx) => tx.signature_hash(),
234 #[cfg(feature = "optimism")]
235 Self::Deposit(_) => B256::ZERO,
236 }
237 }
238
239 pub fn set_chain_id(&mut self, chain_id: u64) {
241 match self {
242 Self::Legacy(TxLegacy { chain_id: ref mut c, .. }) => *c = Some(chain_id),
243 Self::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) |
244 Self::Seismic(TxSeismic { chain_id: ref mut c, .. }) |
245 Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) |
246 Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) |
247 Self::Eip7702(TxEip7702 { chain_id: ref mut c, .. }) => *c = chain_id,
248 #[cfg(feature = "optimism")]
249 Self::Deposit(_) => { }
250 }
251 }
252
253 pub const fn tx_type(&self) -> TxType {
255 match self {
256 Self::Legacy(_) => TxType::Legacy,
257 Self::Eip2930(_) => TxType::Eip2930,
258 Self::Eip1559(_) => TxType::Eip1559,
259 Self::Eip4844(_) => TxType::Eip4844,
260 Self::Eip7702(_) => TxType::Eip7702,
261 Self::Seismic(_) => TxType::Seismic,
262 #[cfg(feature = "optimism")]
263 Self::Deposit(_) => TxType::Deposit,
264 }
265 }
266
267 pub fn blob_gas_used(&self) -> Option<u64> {
273 self.as_eip4844().map(TxEip4844::blob_gas)
274 }
275
276 pub fn effective_tip_per_gas(&self, base_fee: Option<u64>) -> Option<u128> {
284 let base_fee = match base_fee {
285 Some(base_fee) => base_fee as u128,
286 None => return Some(self.priority_fee_or_price()),
287 };
288
289 let max_fee_per_gas = self.max_fee_per_gas();
290
291 if max_fee_per_gas < base_fee {
293 return None
294 }
295
296 let fee = max_fee_per_gas - base_fee;
298
299 if let Some(priority_fee) = self.max_priority_fee_per_gas() {
301 Some(fee.min(priority_fee))
302 } else {
303 Some(fee)
304 }
305 }
306
307 pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
310 match self {
311 Self::Legacy(tx) => tx.encode_for_signing(out),
312 Self::Eip2930(tx) => tx.encode_for_signing(out),
313 Self::Eip1559(tx) => tx.encode_for_signing(out),
314 Self::Eip4844(tx) => tx.encode_for_signing(out),
315 Self::Eip7702(tx) => tx.encode_for_signing(out),
316 Self::Seismic(tx) => tx.encode_for_signing(out),
317 #[cfg(feature = "optimism")]
318 Self::Deposit(_) => {}
319 }
320 }
321
322 pub fn eip2718_encode(&self, signature: &Signature, out: &mut dyn bytes::BufMut) {
324 match self {
325 Self::Legacy(legacy_tx) => {
326 legacy_tx.eip2718_encode(signature, out);
328 }
329 Self::Eip2930(access_list_tx) => {
330 access_list_tx.eip2718_encode(signature, out);
331 }
332 Self::Eip1559(dynamic_fee_tx) => {
333 dynamic_fee_tx.eip2718_encode(signature, out);
334 }
335 Self::Eip4844(blob_tx) => blob_tx.eip2718_encode(signature, out),
336 Self::Eip7702(set_code_tx) => {
337 set_code_tx.eip2718_encode(signature, out);
338 }
339 Self::Seismic(seismic_tx) => {
340 seismic_tx.eip2718_encode(signature, out);
341 }
342 #[cfg(feature = "optimism")]
343 Self::Deposit(deposit_tx) => deposit_tx.encode_2718(out),
344 }
345 }
346
347 pub fn set_gas_limit(&mut self, gas_limit: u64) {
349 match self {
350 Self::Legacy(tx) => tx.gas_limit = gas_limit,
351 Self::Seismic(tx) => tx.gas_limit = gas_limit,
352 Self::Eip2930(tx) => tx.gas_limit = gas_limit,
353 Self::Eip1559(tx) => tx.gas_limit = gas_limit,
354 Self::Eip4844(tx) => tx.gas_limit = gas_limit,
355 Self::Eip7702(tx) => tx.gas_limit = gas_limit,
356 #[cfg(feature = "optimism")]
357 Self::Deposit(tx) => tx.gas_limit = gas_limit,
358 }
359 }
360
361 pub fn set_nonce(&mut self, nonce: u64) {
363 match self {
364 Self::Legacy(tx) => tx.nonce = nonce,
365 Self::Seismic(tx) => tx.nonce = nonce,
366 Self::Eip2930(tx) => tx.nonce = nonce,
367 Self::Eip1559(tx) => tx.nonce = nonce,
368 Self::Eip4844(tx) => tx.nonce = nonce,
369 Self::Eip7702(tx) => tx.nonce = nonce,
370 #[cfg(feature = "optimism")]
371 Self::Deposit(_) => { }
372 }
373 }
374
375 pub fn set_value(&mut self, value: U256) {
377 match self {
378 Self::Legacy(tx) => tx.value = value,
379 Self::Seismic(tx) => tx.value = value,
380 Self::Eip2930(tx) => tx.value = value,
381 Self::Eip1559(tx) => tx.value = value,
382 Self::Eip4844(tx) => tx.value = value,
383 Self::Eip7702(tx) => tx.value = value,
384 #[cfg(feature = "optimism")]
385 Self::Deposit(tx) => tx.value = value,
386 }
387 }
388
389 pub fn set_input(&mut self, input: Bytes) {
391 match self {
392 Self::Legacy(tx) => tx.input = input,
393 Self::Seismic(tx) => tx.input = input,
394 Self::Eip2930(tx) => tx.input = input,
395 Self::Eip1559(tx) => tx.input = input,
396 Self::Eip4844(tx) => tx.input = input,
397 Self::Eip7702(tx) => tx.input = input,
398 #[cfg(feature = "optimism")]
399 Self::Deposit(tx) => tx.input = input,
400 }
401 }
402
403 #[inline]
405 pub const fn is_legacy(&self) -> bool {
406 matches!(self, Self::Legacy(_))
407 }
408
409 #[inline]
411 pub const fn is_eip2930(&self) -> bool {
412 matches!(self, Self::Eip2930(_))
413 }
414
415 #[inline]
417 pub const fn is_eip1559(&self) -> bool {
418 matches!(self, Self::Eip1559(_))
419 }
420
421 #[inline]
423 pub const fn is_eip4844(&self) -> bool {
424 matches!(self, Self::Eip4844(_))
425 }
426
427 #[inline]
429 pub const fn is_eip7702(&self) -> bool {
430 matches!(self, Self::Eip7702(_))
431 }
432
433 pub const fn as_legacy(&self) -> Option<&TxLegacy> {
435 match self {
436 Self::Legacy(tx) => Some(tx),
437 _ => None,
438 }
439 }
440
441 pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
443 match self {
444 Self::Eip2930(tx) => Some(tx),
445 _ => None,
446 }
447 }
448
449 pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
451 match self {
452 Self::Eip1559(tx) => Some(tx),
453 _ => None,
454 }
455 }
456
457 pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
459 match self {
460 Self::Eip4844(tx) => Some(tx),
461 _ => None,
462 }
463 }
464
465 pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
467 match self {
468 Self::Eip7702(tx) => Some(tx),
469 _ => None,
470 }
471 }
472}
473
474impl InMemorySize for Transaction {
475 #[inline]
477 fn size(&self) -> usize {
478 match self {
479 Self::Legacy(tx) => tx.size(),
480 Self::Seismic(tx) => tx.size(),
481 Self::Eip2930(tx) => tx.size(),
482 Self::Eip1559(tx) => tx.size(),
483 Self::Eip4844(tx) => tx.size(),
484 Self::Eip7702(tx) => tx.size(),
485 #[cfg(feature = "optimism")]
486 Self::Deposit(tx) => tx.size(),
487 }
488 }
489}
490
491#[cfg(any(test, feature = "reth-codec"))]
492impl reth_codecs::Compact for Transaction {
493 fn to_compact<B>(&self, buf: &mut B) -> usize
496 where
497 B: bytes::BufMut + AsMut<[u8]>,
498 {
499 let identifier = self.tx_type().to_compact(buf);
500 match self {
501 Self::Legacy(tx) => {
502 tx.to_compact(buf);
503 }
504 Self::Eip2930(tx) => {
505 tx.to_compact(buf);
506 }
507 Self::Eip1559(tx) => {
508 tx.to_compact(buf);
509 }
510 Self::Eip4844(tx) => {
511 tx.to_compact(buf);
512 }
513 Self::Eip7702(tx) => {
514 tx.to_compact(buf);
515 }
516 Self::Seismic(tx) => {
517 tx.to_compact(buf);
518 }
519 #[cfg(feature = "optimism")]
520 Self::Deposit(tx) => {
521 tx.to_compact(buf);
522 }
523 }
524 identifier
525 }
526
527 fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
536 use bytes::Buf;
537
538 match identifier {
539 reth_codecs::txtype::COMPACT_IDENTIFIER_LEGACY => {
540 let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
541 (Self::Legacy(tx), buf)
542 }
543 reth_codecs::txtype::COMPACT_IDENTIFIER_EIP2930 => {
544 let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
545 (Self::Eip2930(tx), buf)
546 }
547 reth_codecs::txtype::COMPACT_IDENTIFIER_EIP1559 => {
548 let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
549 (Self::Eip1559(tx), buf)
550 }
551 reth_codecs::txtype::COMPACT_EXTENDED_IDENTIFIER_FLAG => {
552 let identifier = buf.get_u8();
558 match identifier {
559 alloy_consensus::constants::EIP4844_TX_TYPE_ID => {
560 let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
561 (Self::Eip4844(tx), buf)
562 }
563 alloy_consensus::constants::EIP7702_TX_TYPE_ID => {
564 let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
565 (Self::Eip7702(tx), buf)
566 }
567 alloy_consensus::constants::SEISMIC_TX_TYPE_ID => {
568 let (tx, buf) = TxSeismic::from_compact(buf, buf.len());
569 (Self::Seismic(tx), buf)
570 }
571 #[cfg(feature = "optimism")]
572 op_alloy_consensus::DEPOSIT_TX_TYPE_ID => {
573 let (tx, buf) = TxDeposit::from_compact(buf, buf.len());
574 (Self::Deposit(tx), buf)
575 }
576 _ => unreachable!(
577 "Junk data in database: unknown Transaction variant: {identifier}"
578 ),
579 }
580 }
581 _ => unreachable!("Junk data in database: unknown Transaction variant: {identifier}"),
582 }
583 }
584}
585
586impl Default for Transaction {
587 fn default() -> Self {
588 Self::Legacy(TxLegacy::default())
589 }
590}
591
592impl alloy_consensus::transaction::ShieldableTransaction for Transaction {
593 fn shield_input(&mut self) {
594 match self {
595 Self::Seismic(tx) => {
596 tx.shield_input();
597 }
598 _ => {}
599 }
600 }
601}
602impl alloy_consensus::Transaction for Transaction {
603 fn chain_id(&self) -> Option<ChainId> {
604 match self {
605 Self::Legacy(tx) => tx.chain_id(),
606 Self::Seismic(tx) => tx.chain_id(),
607 Self::Eip2930(tx) => tx.chain_id(),
608 Self::Eip1559(tx) => tx.chain_id(),
609 Self::Eip4844(tx) => tx.chain_id(),
610 Self::Eip7702(tx) => tx.chain_id(),
611 #[cfg(feature = "optimism")]
612 Self::Deposit(tx) => tx.chain_id(),
613 }
614 }
615
616 fn nonce(&self) -> u64 {
617 match self {
618 Self::Legacy(tx) => tx.nonce(),
619 Self::Seismic(tx) => tx.nonce(),
620 Self::Eip2930(tx) => tx.nonce(),
621 Self::Eip1559(tx) => tx.nonce(),
622 Self::Eip4844(tx) => tx.nonce(),
623 Self::Eip7702(tx) => tx.nonce(),
624 #[cfg(feature = "optimism")]
625 Self::Deposit(tx) => tx.nonce(),
626 }
627 }
628
629 fn gas_limit(&self) -> u64 {
630 match self {
631 Self::Legacy(tx) => tx.gas_limit(),
632 Self::Seismic(tx) => tx.gas_limit(),
633 Self::Eip2930(tx) => tx.gas_limit(),
634 Self::Eip1559(tx) => tx.gas_limit(),
635 Self::Eip4844(tx) => tx.gas_limit(),
636 Self::Eip7702(tx) => tx.gas_limit(),
637 #[cfg(feature = "optimism")]
638 Self::Deposit(tx) => tx.gas_limit(),
639 }
640 }
641
642 fn gas_price(&self) -> Option<u128> {
643 match self {
644 Self::Legacy(tx) => tx.gas_price(),
645 Self::Seismic(tx) => tx.gas_price(),
646 Self::Eip2930(tx) => tx.gas_price(),
647 Self::Eip1559(tx) => tx.gas_price(),
648 Self::Eip4844(tx) => tx.gas_price(),
649 Self::Eip7702(tx) => tx.gas_price(),
650 #[cfg(feature = "optimism")]
651 Self::Deposit(tx) => tx.gas_price(),
652 }
653 }
654
655 fn max_fee_per_gas(&self) -> u128 {
656 match self {
657 Self::Legacy(tx) => tx.max_fee_per_gas(),
658 Self::Seismic(tx) => tx.max_fee_per_gas(),
659 Self::Eip2930(tx) => tx.max_fee_per_gas(),
660 Self::Eip1559(tx) => tx.max_fee_per_gas(),
661 Self::Eip4844(tx) => tx.max_fee_per_gas(),
662 Self::Eip7702(tx) => tx.max_fee_per_gas(),
663 #[cfg(feature = "optimism")]
664 Self::Deposit(tx) => tx.max_fee_per_gas(),
665 }
666 }
667
668 fn max_priority_fee_per_gas(&self) -> Option<u128> {
669 match self {
670 Self::Legacy(tx) => tx.max_priority_fee_per_gas(),
671 Self::Seismic(tx) => tx.max_priority_fee_per_gas(),
672 Self::Eip2930(tx) => tx.max_priority_fee_per_gas(),
673 Self::Eip1559(tx) => tx.max_priority_fee_per_gas(),
674 Self::Eip4844(tx) => tx.max_priority_fee_per_gas(),
675 Self::Eip7702(tx) => tx.max_priority_fee_per_gas(),
676 #[cfg(feature = "optimism")]
677 Self::Deposit(tx) => tx.max_priority_fee_per_gas(),
678 }
679 }
680
681 fn max_fee_per_blob_gas(&self) -> Option<u128> {
682 match self {
683 Self::Legacy(tx) => tx.max_fee_per_blob_gas(),
684 Self::Seismic(tx) => tx.max_fee_per_blob_gas(),
685 Self::Eip2930(tx) => tx.max_fee_per_blob_gas(),
686 Self::Eip1559(tx) => tx.max_fee_per_blob_gas(),
687 Self::Eip4844(tx) => tx.max_fee_per_blob_gas(),
688 Self::Eip7702(tx) => tx.max_fee_per_blob_gas(),
689 #[cfg(feature = "optimism")]
690 Self::Deposit(tx) => tx.max_fee_per_blob_gas(),
691 }
692 }
693
694 fn priority_fee_or_price(&self) -> u128 {
695 match self {
696 Self::Legacy(tx) => tx.priority_fee_or_price(),
697 Self::Seismic(tx) => tx.priority_fee_or_price(),
698 Self::Eip2930(tx) => tx.priority_fee_or_price(),
699 Self::Eip1559(tx) => tx.priority_fee_or_price(),
700 Self::Eip4844(tx) => tx.priority_fee_or_price(),
701 Self::Eip7702(tx) => tx.priority_fee_or_price(),
702 #[cfg(feature = "optimism")]
703 Self::Deposit(tx) => tx.priority_fee_or_price(),
704 }
705 }
706
707 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
708 match self {
709 Self::Legacy(tx) => tx.effective_gas_price(base_fee),
710 Self::Seismic(tx) => tx.effective_gas_price(base_fee),
711 Self::Eip2930(tx) => tx.effective_gas_price(base_fee),
712 Self::Eip1559(tx) => tx.effective_gas_price(base_fee),
713 Self::Eip4844(tx) => tx.effective_gas_price(base_fee),
714 Self::Eip7702(tx) => tx.effective_gas_price(base_fee),
715 #[cfg(feature = "optimism")]
716 Self::Deposit(tx) => tx.effective_gas_price(base_fee),
717 }
718 }
719
720 fn is_dynamic_fee(&self) -> bool {
721 match self {
722 Self::Legacy(_) | Self::Eip2930(_) | Self::Seismic(_) => false,
723 Self::Eip1559(_) | Self::Eip4844(_) | Self::Eip7702(_) => true,
724 #[cfg(feature = "optimism")]
725 Self::Deposit(_) => false,
726 }
727 }
728
729 fn kind(&self) -> TxKind {
730 match self {
731 Self::Legacy(tx) => tx.kind(),
732 Self::Seismic(tx) => tx.kind(),
733 Self::Eip2930(tx) => tx.kind(),
734 Self::Eip1559(tx) => tx.kind(),
735 Self::Eip4844(tx) => tx.kind(),
736 Self::Eip7702(tx) => tx.kind(),
737 #[cfg(feature = "optimism")]
738 Self::Deposit(tx) => tx.kind(),
739 }
740 }
741
742 fn is_create(&self) -> bool {
743 match self {
744 Self::Legacy(tx) => tx.is_create(),
745 Self::Seismic(tx) => tx.is_create(),
746 Self::Eip2930(tx) => tx.is_create(),
747 Self::Eip1559(tx) => tx.is_create(),
748 Self::Eip4844(tx) => tx.is_create(),
749 Self::Eip7702(tx) => tx.is_create(),
750 #[cfg(feature = "optimism")]
751 Self::Deposit(tx) => tx.is_create(),
752 }
753 }
754
755 fn value(&self) -> U256 {
756 match self {
757 Self::Legacy(tx) => tx.value(),
758 Self::Seismic(tx) => tx.value(),
759 Self::Eip2930(tx) => tx.value(),
760 Self::Eip1559(tx) => tx.value(),
761 Self::Eip4844(tx) => tx.value(),
762 Self::Eip7702(tx) => tx.value(),
763 #[cfg(feature = "optimism")]
764 Self::Deposit(tx) => tx.value(),
765 }
766 }
767
768 fn input(&self) -> &Bytes {
769 match self {
770 Self::Legacy(tx) => tx.input(),
771 Self::Seismic(tx) => tx.input(),
772 Self::Eip2930(tx) => tx.input(),
773 Self::Eip1559(tx) => tx.input(),
774 Self::Eip4844(tx) => tx.input(),
775 Self::Eip7702(tx) => tx.input(),
776 #[cfg(feature = "optimism")]
777 Self::Deposit(tx) => tx.input(),
778 }
779 }
780
781 fn access_list(&self) -> Option<&AccessList> {
782 match self {
783 Self::Legacy(tx) => tx.access_list(),
784 Self::Seismic(tx) => tx.access_list(),
785 Self::Eip2930(tx) => tx.access_list(),
786 Self::Eip1559(tx) => tx.access_list(),
787 Self::Eip4844(tx) => tx.access_list(),
788 Self::Eip7702(tx) => tx.access_list(),
789 #[cfg(feature = "optimism")]
790 Self::Deposit(tx) => tx.access_list(),
791 }
792 }
793
794 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
795 match self {
796 Self::Legacy(tx) => tx.blob_versioned_hashes(),
797 Self::Seismic(tx) => tx.blob_versioned_hashes(),
798 Self::Eip2930(tx) => tx.blob_versioned_hashes(),
799 Self::Eip1559(tx) => tx.blob_versioned_hashes(),
800 Self::Eip4844(tx) => tx.blob_versioned_hashes(),
801 Self::Eip7702(tx) => tx.blob_versioned_hashes(),
802 #[cfg(feature = "optimism")]
803 Self::Deposit(tx) => tx.blob_versioned_hashes(),
804 }
805 }
806
807 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
808 match self {
809 Self::Legacy(tx) => tx.authorization_list(),
810 Self::Seismic(tx) => tx.authorization_list(),
811 Self::Eip2930(tx) => tx.authorization_list(),
812 Self::Eip1559(tx) => tx.authorization_list(),
813 Self::Eip4844(tx) => tx.authorization_list(),
814 Self::Eip7702(tx) => tx.authorization_list(),
815 #[cfg(feature = "optimism")]
816 Self::Deposit(tx) => tx.authorization_list(),
817 }
818 }
819
820 fn seismic_elements(&self) -> Option<&TxSeismicElements> {
821 match self {
822 Self::Seismic(tx) => tx.seismic_elements(),
823 _ => None,
824 }
825 }
826}
827
828impl From<TxEip4844Variant> for Transaction {
829 fn from(value: TxEip4844Variant) -> Self {
830 match value {
831 TxEip4844Variant::TxEip4844(tx) => tx.into(),
832 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.into(),
833 }
834 }
835}
836
837impl From<TypedTransaction> for Transaction {
838 fn from(value: TypedTransaction) -> Self {
839 match value {
840 TypedTransaction::Legacy(tx) => tx.into(),
841 TypedTransaction::Seismic(tx) => tx.into(),
842 TypedTransaction::Eip2930(tx) => tx.into(),
843 TypedTransaction::Eip1559(tx) => tx.into(),
844 TypedTransaction::Eip4844(tx) => tx.into(),
845 TypedTransaction::Eip7702(tx) => tx.into(),
846 }
847 }
848}
849
850#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
852#[derive(Debug, Clone, Eq, AsRef, Deref, Serialize, Deserialize)]
853pub struct TransactionSigned {
854 #[serde(skip)]
856 pub hash: OnceLock<TxHash>,
857 pub signature: Signature,
859 #[deref]
861 #[as_ref]
862 pub transaction: Transaction,
863}
864
865impl Default for TransactionSigned {
866 fn default() -> Self {
867 Self {
868 hash: Default::default(),
869 signature: Signature::test_signature(),
870 transaction: Default::default(),
871 }
872 }
873}
874
875impl AsRef<Self> for TransactionSigned {
876 fn as_ref(&self) -> &Self {
877 self
878 }
879}
880
881impl Hash for TransactionSigned {
882 fn hash<H: Hasher>(&self, state: &mut H) {
883 self.signature.hash(state);
884 self.transaction.hash(state);
885 }
886}
887
888impl PartialEq for TransactionSigned {
889 fn eq(&self, other: &Self) -> bool {
890 self.signature == other.signature &&
891 self.transaction == other.transaction &&
892 self.tx_hash() == other.tx_hash()
893 }
894}
895
896impl Typed2718 for TransactionSigned {
897 fn ty(&self) -> u8 {
898 self.deref().ty()
899 }
900}
901
902impl TransactionSigned {
905 pub fn new(transaction: Transaction, signature: Signature, hash: B256) -> Self {
907 Self { hash: hash.into(), signature, transaction }
908 }
909
910 pub fn new_unhashed(transaction: Transaction, signature: Signature) -> Self {
914 Self { hash: Default::default(), signature, transaction }
915 }
916
917 pub const fn transaction(&self) -> &Transaction {
919 &self.transaction
920 }
921
922 pub fn try_into_pooled(self) -> Result<PooledTransactionsElement, Self> {
928 let hash = self.hash();
929 match self {
930 Self { transaction: Transaction::Legacy(tx), signature, .. } => {
931 Ok(PooledTransactionsElement::Legacy(Signed::new_unchecked(tx, signature, hash)))
932 }
933 Self { transaction: Transaction::Eip2930(tx), signature, .. } => {
934 Ok(PooledTransactionsElement::Eip2930(Signed::new_unchecked(tx, signature, hash)))
935 }
936 Self { transaction: Transaction::Eip1559(tx), signature, .. } => {
937 Ok(PooledTransactionsElement::Eip1559(Signed::new_unchecked(tx, signature, hash)))
938 }
939 Self { transaction: Transaction::Eip7702(tx), signature, .. } => {
940 Ok(PooledTransactionsElement::Eip7702(Signed::new_unchecked(tx, signature, hash)))
941 }
942 Self { transaction: Transaction::Seismic(tx), signature, .. } => {
943 Ok(PooledTransactionsElement::Seismic(Signed::new_unchecked(tx, signature, hash)))
944 }
945 tx @ Self { transaction: Transaction::Eip4844(_), .. } => Err(tx),
947 #[cfg(feature = "optimism")]
948 tx @ Self { transaction: Transaction::Deposit(_), .. } => Err(tx),
950 }
951 }
952
953 pub fn hash(&self) -> TxHash {
955 *self.tx_hash()
956 }
957
958 pub fn recover_signers<'a, T>(txes: T, num_txes: usize) -> Option<Vec<Address>>
963 where
964 T: IntoParallelIterator<Item = &'a Self> + IntoIterator<Item = &'a Self> + Send,
965 {
966 if num_txes < *PARALLEL_SENDER_RECOVERY_THRESHOLD {
967 txes.into_iter().map(|tx| tx.recover_signer()).collect()
968 } else {
969 txes.into_par_iter().map(|tx| tx.recover_signer()).collect()
970 }
971 }
972
973 pub fn recover_signers_unchecked<'a, T>(txes: T, num_txes: usize) -> Option<Vec<Address>>
979 where
980 T: IntoParallelIterator<Item = &'a Self> + IntoIterator<Item = &'a Self>,
981 {
982 if num_txes < *PARALLEL_SENDER_RECOVERY_THRESHOLD {
983 txes.into_iter().map(|tx| tx.recover_signer_unchecked()).collect()
984 } else {
985 txes.into_par_iter().map(|tx| tx.recover_signer_unchecked()).collect()
986 }
987 }
988
989 #[inline]
991 pub const fn with_signer(self, signer: Address) -> RecoveredTx {
992 RecoveredTx::from_signed_transaction(self, signer)
993 }
994
995 pub fn into_ecrecovered(self) -> Option<RecoveredTx> {
999 let signer = self.recover_signer()?;
1000 Some(RecoveredTx { signed_transaction: self, signer })
1001 }
1002
1003 pub fn into_ecrecovered_unchecked(self) -> Option<RecoveredTx> {
1009 let signer = self.recover_signer_unchecked()?;
1010 Some(RecoveredTx { signed_transaction: self, signer })
1011 }
1012
1013 pub fn try_into_ecrecovered_unchecked(self) -> Result<RecoveredTx, Self> {
1019 match self.recover_signer_unchecked() {
1020 None => Err(self),
1021 Some(signer) => Ok(RecoveredTx { signed_transaction: self, signer }),
1022 }
1023 }
1024
1025 pub fn recalculate_hash(&self) -> B256 {
1028 match &self.transaction {
1029 Transaction::Legacy(tx) => {
1030 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1031 }
1032 Transaction::Eip2930(tx) => {
1033 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1034 }
1035 Transaction::Eip1559(tx) => {
1036 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1037 }
1038 Transaction::Eip4844(tx) => {
1039 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1040 }
1041 Transaction::Eip7702(tx) => {
1042 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1043 }
1044 Transaction::Seismic(tx) => {
1045 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1046 }
1047 #[cfg(feature = "optimism")]
1048 Transaction::Deposit(tx) => {
1049 *SignableTransaction::<Signature>::into_signed(tx.clone(), self.signature).hash()
1050 }
1051 }
1052 }
1053
1054 pub fn into_parts(self) -> (Transaction, Signature, B256) {
1056 let hash = self.hash();
1057 (self.transaction, self.signature, hash)
1058 }
1059
1060 pub fn decode_rlp_legacy_transaction_tuple(
1067 data: &mut &[u8],
1068 ) -> alloy_rlp::Result<(TxLegacy, TxHash, Signature)> {
1069 let original_encoding = *data;
1071
1072 let header = Header::decode(data)?;
1073 let remaining_len = data.len();
1074
1075 let transaction_payload_len = header.payload_length;
1076
1077 if transaction_payload_len > remaining_len {
1078 return Err(RlpError::InputTooShort)
1079 }
1080
1081 let mut transaction = TxLegacy {
1082 nonce: Decodable::decode(data)?,
1083 gas_price: Decodable::decode(data)?,
1084 gas_limit: Decodable::decode(data)?,
1085 to: Decodable::decode(data)?,
1086 value: Decodable::decode(data)?,
1087 input: Decodable::decode(data)?,
1088 chain_id: None,
1089 };
1090 let (signature, extracted_id) = decode_with_eip155_chain_id(data)?;
1091 transaction.chain_id = extracted_id;
1092
1093 let decoded = remaining_len - data.len();
1095 if decoded != transaction_payload_len {
1096 return Err(RlpError::UnexpectedLength)
1097 }
1098
1099 let tx_length = header.payload_length + header.length();
1100 let hash = keccak256(&original_encoding[..tx_length]);
1101 Ok((transaction, hash, signature))
1102 }
1103
1104 pub fn decode_rlp_legacy_transaction(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
1116 let (transaction, hash, signature) = Self::decode_rlp_legacy_transaction_tuple(data)?;
1117 let signed =
1118 Self { transaction: Transaction::Legacy(transaction), hash: hash.into(), signature };
1119 Ok(signed)
1120 }
1121
1122 pub fn shield_input(mut self) -> Self {
1124 self.transaction.set_input(Bytes::new());
1125 self
1126 }
1127}
1128
1129impl SignedTransaction for TransactionSigned {
1130 fn tx_hash(&self) -> &TxHash {
1131 self.hash.get_or_init(|| self.recalculate_hash())
1132 }
1133
1134 fn signature(&self) -> &Signature {
1135 &self.signature
1136 }
1137
1138 fn recover_signer(&self) -> Option<Address> {
1139 #[cfg(feature = "optimism")]
1142 if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction {
1143 return Some(from)
1144 }
1145 let signature_hash = self.signature_hash();
1146 recover_signer(&self.signature, signature_hash)
1147 }
1148
1149 fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> {
1150 #[cfg(feature = "optimism")]
1153 if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction {
1154 return Some(from)
1155 }
1156 self.encode_for_signing(buf);
1157 let signature_hash = keccak256(buf);
1158 recover_signer_unchecked(&self.signature, signature_hash)
1159 }
1160}
1161
1162impl reth_primitives_traits::FillTxEnv for TransactionSigned {
1163 fn fill_tx_env(&self, tx_env: &mut TxEnv, sender: Address) {
1164 tx_env.caller = sender;
1165 match self.as_ref() {
1166 Transaction::Legacy(tx) => {
1167 tx_env.gas_limit = tx.gas_limit;
1168 tx_env.gas_price = U256::from(tx.gas_price);
1169 tx_env.gas_priority_fee = None;
1170 tx_env.transact_to = tx.to;
1171 tx_env.value = tx.value;
1172 tx_env.data = tx.input.clone();
1173 tx_env.chain_id = tx.chain_id;
1174 tx_env.nonce = Some(tx.nonce);
1175 tx_env.access_list.clear();
1176 tx_env.blob_hashes.clear();
1177 tx_env.max_fee_per_blob_gas.take();
1178 tx_env.authorization_list = None;
1179 }
1180 Transaction::Eip2930(tx) => {
1181 tx_env.gas_limit = tx.gas_limit;
1182 tx_env.gas_price = U256::from(tx.gas_price);
1183 tx_env.gas_priority_fee = None;
1184 tx_env.transact_to = tx.to;
1185 tx_env.value = tx.value;
1186 tx_env.data = tx.input.clone();
1187 tx_env.chain_id = Some(tx.chain_id);
1188 tx_env.nonce = Some(tx.nonce);
1189 tx_env.access_list.clone_from(&tx.access_list.0);
1190 tx_env.blob_hashes.clear();
1191 tx_env.max_fee_per_blob_gas.take();
1192 tx_env.authorization_list = None;
1193 }
1194 Transaction::Eip1559(tx) => {
1195 tx_env.gas_limit = tx.gas_limit;
1196 tx_env.gas_price = U256::from(tx.max_fee_per_gas);
1197 tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
1198 tx_env.transact_to = tx.to;
1199 tx_env.value = tx.value;
1200 tx_env.data = tx.input.clone();
1201 tx_env.chain_id = Some(tx.chain_id);
1202 tx_env.nonce = Some(tx.nonce);
1203 tx_env.access_list.clone_from(&tx.access_list.0);
1204 tx_env.blob_hashes.clear();
1205 tx_env.max_fee_per_blob_gas.take();
1206 tx_env.authorization_list = None;
1207 }
1208 Transaction::Eip4844(tx) => {
1209 tx_env.gas_limit = tx.gas_limit;
1210 tx_env.gas_price = U256::from(tx.max_fee_per_gas);
1211 tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
1212 tx_env.transact_to = TxKind::Call(tx.to);
1213 tx_env.value = tx.value;
1214 tx_env.data = tx.input.clone();
1215 tx_env.chain_id = Some(tx.chain_id);
1216 tx_env.nonce = Some(tx.nonce);
1217 tx_env.access_list.clone_from(&tx.access_list.0);
1218 tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes);
1219 tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas));
1220 tx_env.authorization_list = None;
1221 }
1222 Transaction::Eip7702(tx) => {
1223 tx_env.gas_limit = tx.gas_limit;
1224 tx_env.gas_price = U256::from(tx.max_fee_per_gas);
1225 tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
1226 tx_env.transact_to = tx.to.into();
1227 tx_env.value = tx.value;
1228 tx_env.data = tx.input.clone();
1229 tx_env.chain_id = Some(tx.chain_id);
1230 tx_env.nonce = Some(tx.nonce);
1231 tx_env.access_list.clone_from(&tx.access_list.0);
1232 tx_env.blob_hashes.clear();
1233 tx_env.max_fee_per_blob_gas.take();
1234 tx_env.authorization_list =
1235 Some(AuthorizationList::Signed(tx.authorization_list.clone()));
1236 }
1237 Transaction::Seismic(_tx) => {
1238 error!(target: "reth::fill_tx_env", "Seismic transaction not filled");
1240 return
1241 }
1242 #[cfg(feature = "optimism")]
1243 Transaction::Deposit(_) => {}
1244 }
1245 }
1246}
1247
1248impl InMemorySize for TransactionSigned {
1249 #[inline]
1251 fn size(&self) -> usize {
1252 self.hash().size() + self.transaction.size() + self.signature().size()
1253 }
1254}
1255
1256impl alloy_consensus::transaction::ShieldableTransaction for TransactionSigned {
1257 fn shield_input(&mut self) {
1258 match &mut self.transaction {
1259 Transaction::Seismic(tx) => {
1260 tx.shield_input();
1261 }
1262 _ => {}
1263 }
1264 }
1265}
1266
1267impl alloy_consensus::Transaction for TransactionSigned {
1268 fn chain_id(&self) -> Option<ChainId> {
1269 self.deref().chain_id()
1270 }
1271
1272 fn nonce(&self) -> u64 {
1273 self.deref().nonce()
1274 }
1275
1276 fn gas_limit(&self) -> u64 {
1277 self.deref().gas_limit()
1278 }
1279
1280 fn gas_price(&self) -> Option<u128> {
1281 self.deref().gas_price()
1282 }
1283
1284 fn max_fee_per_gas(&self) -> u128 {
1285 self.deref().max_fee_per_gas()
1286 }
1287
1288 fn max_priority_fee_per_gas(&self) -> Option<u128> {
1289 self.deref().max_priority_fee_per_gas()
1290 }
1291
1292 fn max_fee_per_blob_gas(&self) -> Option<u128> {
1293 self.deref().max_fee_per_blob_gas()
1294 }
1295
1296 fn priority_fee_or_price(&self) -> u128 {
1297 self.deref().priority_fee_or_price()
1298 }
1299
1300 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1301 self.deref().effective_gas_price(base_fee)
1302 }
1303
1304 fn is_dynamic_fee(&self) -> bool {
1305 self.deref().is_dynamic_fee()
1306 }
1307
1308 fn kind(&self) -> TxKind {
1309 self.deref().kind()
1310 }
1311
1312 fn is_create(&self) -> bool {
1313 self.deref().is_create()
1314 }
1315
1316 fn value(&self) -> U256 {
1317 self.deref().value()
1318 }
1319
1320 fn input(&self) -> &Bytes {
1321 self.deref().input()
1322 }
1323
1324 fn access_list(&self) -> Option<&AccessList> {
1325 self.deref().access_list()
1326 }
1327
1328 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1329 alloy_consensus::Transaction::blob_versioned_hashes(self.deref())
1330 }
1331
1332 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1333 self.deref().authorization_list()
1334 }
1335
1336 fn seismic_elements(&self) -> Option<&TxSeismicElements> {
1337 match &self.transaction {
1338 Transaction::Seismic(tx) => tx.seismic_elements(),
1339 _ => None,
1340 }
1341 }
1342}
1343
1344impl From<RecoveredTx> for TransactionSigned {
1345 fn from(recovered: RecoveredTx) -> Self {
1346 recovered.signed_transaction
1347 }
1348}
1349
1350impl Encodable for TransactionSigned {
1351 fn encode(&self, out: &mut dyn bytes::BufMut) {
1360 self.network_encode(out);
1361 }
1362
1363 fn length(&self) -> usize {
1364 let mut payload_length = self.encode_2718_len();
1365 if !Encodable2718::is_legacy(self) {
1366 payload_length += Header { list: false, payload_length }.length();
1367 }
1368
1369 payload_length
1370 }
1371}
1372
1373impl Decodable for TransactionSigned {
1374 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1400 Self::network_decode(buf).map_err(Into::into)
1401 }
1402}
1403
1404impl Encodable2718 for TransactionSigned {
1405 fn type_flag(&self) -> Option<u8> {
1406 match self.transaction.tx_type() {
1407 TxType::Legacy => None,
1408 tx_type => Some(tx_type as u8),
1409 }
1410 }
1411
1412 fn encode_2718_len(&self) -> usize {
1413 match &self.transaction {
1414 Transaction::Legacy(legacy_tx) => legacy_tx.eip2718_encoded_length(&self.signature),
1415 Transaction::Eip2930(access_list_tx) => {
1416 access_list_tx.eip2718_encoded_length(&self.signature)
1417 }
1418 Transaction::Eip1559(dynamic_fee_tx) => {
1419 dynamic_fee_tx.eip2718_encoded_length(&self.signature)
1420 }
1421 Transaction::Eip4844(blob_tx) => blob_tx.eip2718_encoded_length(&self.signature),
1422 Transaction::Eip7702(set_code_tx) => {
1423 set_code_tx.eip2718_encoded_length(&self.signature)
1424 }
1425 Transaction::Seismic(seismic_tx) => seismic_tx.eip2718_encoded_length(&self.signature),
1426 #[cfg(feature = "optimism")]
1427 Transaction::Deposit(deposit_tx) => deposit_tx.eip2718_encoded_length(),
1428 }
1429 }
1430
1431 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
1432 self.transaction.eip2718_encode(&self.signature, out)
1433 }
1434
1435 fn trie_hash(&self) -> B256 {
1436 self.hash()
1437 }
1438}
1439
1440impl Decodable2718 for TransactionSigned {
1441 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
1442 match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
1443 TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
1444 TxType::Eip2930 => {
1445 let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts();
1446 Ok(Self { transaction: Transaction::Eip2930(tx), signature, hash: hash.into() })
1447 }
1448 TxType::Eip1559 => {
1449 let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts();
1450 Ok(Self { transaction: Transaction::Eip1559(tx), signature, hash: hash.into() })
1451 }
1452 TxType::Eip7702 => {
1453 let (tx, signature, hash) = TxEip7702::rlp_decode_signed(buf)?.into_parts();
1454 Ok(Self { transaction: Transaction::Eip7702(tx), signature, hash: hash.into() })
1455 }
1456 TxType::Eip4844 => {
1457 let (tx, signature, hash) = TxEip4844::rlp_decode_signed(buf)?.into_parts();
1458 Ok(Self { transaction: Transaction::Eip4844(tx), signature, hash: hash.into() })
1459 }
1460 TxType::Seismic => {
1461 let (tx, signature, hash) = TxSeismic::rlp_decode_signed(buf)?.into_parts();
1462 Ok(Self { transaction: Transaction::Seismic(tx), signature, hash: hash.into() })
1463 }
1464 #[cfg(feature = "optimism")]
1465 TxType::Deposit => Ok(Self::new_unhashed(
1466 Transaction::Deposit(TxDeposit::rlp_decode(buf)?),
1467 TxDeposit::signature(),
1468 )),
1469 }
1470 }
1471
1472 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
1473 Ok(Self::decode_rlp_legacy_transaction(buf)?)
1474 }
1475}
1476
1477impl Decodable712 for TransactionSigned {
1478 fn decode_712(typed_data: &TypedDataRequest) -> Eip712Result<Self> {
1479 let (tx, signature, hash) = TxSeismic::eip712_decode(&typed_data.data)?
1480 .into_signed(typed_data.signature)
1481 .into_parts();
1482 Ok(Self { transaction: Transaction::Seismic(tx), signature, hash: hash.into() })
1483 }
1484}
1485
1486#[cfg(any(test, feature = "reth-codec"))]
1487impl reth_codecs::Compact for TransactionSigned {
1488 fn to_compact<B>(&self, buf: &mut B) -> usize
1489 where
1490 B: bytes::BufMut + AsMut<[u8]>,
1491 {
1492 let start = buf.as_mut().len();
1493
1494 buf.put_u8(0);
1497
1498 let sig_bit = self.signature.to_compact(buf) as u8;
1499 let zstd_bit = self.transaction.input().len() >= 32;
1500
1501 let tx_bits = if zstd_bit {
1502 let mut tmp = Vec::with_capacity(256);
1503 if cfg!(feature = "std") {
1504 reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
1505 let mut compressor = compressor.borrow_mut();
1506 let tx_bits = self.transaction.to_compact(&mut tmp);
1507 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
1508 tx_bits as u8
1509 })
1510 } else {
1511 let mut compressor = reth_zstd_compressors::create_tx_compressor();
1512 let tx_bits = self.transaction.to_compact(&mut tmp);
1513 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
1514 tx_bits as u8
1515 }
1516 } else {
1517 self.transaction.to_compact(buf) as u8
1518 };
1519
1520 buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
1522
1523 buf.as_mut().len() - start
1524 }
1525
1526 fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
1527 use bytes::Buf;
1528
1529 let bitflags = buf.get_u8() as usize;
1531
1532 let sig_bit = bitflags & 1;
1533 let (signature, buf) = Signature::from_compact(buf, sig_bit);
1534
1535 let zstd_bit = bitflags >> 3;
1536 let (transaction, buf) = if zstd_bit != 0 {
1537 if cfg!(feature = "std") {
1538 reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
1539 let mut decompressor = decompressor.borrow_mut();
1540
1541 let transaction_type = (bitflags & 0b110) >> 1;
1544 let (transaction, _) =
1545 Transaction::from_compact(decompressor.decompress(buf), transaction_type);
1546
1547 (transaction, buf)
1548 })
1549 } else {
1550 let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
1551 let transaction_type = (bitflags & 0b110) >> 1;
1552 let (transaction, _) =
1553 Transaction::from_compact(decompressor.decompress(buf), transaction_type);
1554
1555 (transaction, buf)
1556 }
1557 } else {
1558 let transaction_type = bitflags >> 1;
1559 Transaction::from_compact(buf, transaction_type)
1560 };
1561
1562 (Self { signature, transaction, hash: Default::default() }, buf)
1563 }
1564}
1565
1566macro_rules! impl_from_signed {
1567 ($($tx:ident),*) => {
1568 $(
1569 impl From<Signed<$tx>> for TransactionSigned {
1570 fn from(value: Signed<$tx>) -> Self {
1571 let(tx,sig,hash) = value.into_parts();
1572 Self::new(tx.into(), sig, hash)
1573 }
1574 }
1575 )*
1576 };
1577}
1578
1579impl_from_signed!(
1580 TxLegacy,
1581 TxEip2930,
1582 TxEip1559,
1583 TxEip7702,
1584 TxEip4844,
1585 TypedTransaction,
1586 TxSeismic
1587);
1588
1589impl From<Signed<Transaction>> for TransactionSigned {
1590 fn from(value: Signed<Transaction>) -> Self {
1591 let (tx, sig, hash) = value.into_parts();
1592 Self::new(tx, sig, hash)
1593 }
1594}
1595
1596impl From<TransactionSigned> for Signed<Transaction> {
1597 fn from(value: TransactionSigned) -> Self {
1598 let (tx, sig, hash) = value.into_parts();
1599 Self::new_unchecked(tx, sig, hash)
1600 }
1601}
1602
1603#[cfg(any(test, feature = "arbitrary"))]
1604impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
1605 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1606 #[allow(unused_mut)]
1607 let mut transaction = Transaction::arbitrary(u)?;
1608
1609 let secp = secp256k1::Secp256k1::new();
1610 let key_pair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
1611 let signature = crate::sign_message(
1612 B256::from_slice(&key_pair.secret_bytes()[..]),
1613 transaction.signature_hash(),
1614 )
1615 .unwrap();
1616
1617 #[cfg(feature = "optimism")]
1618 if let Transaction::Deposit(ref mut tx_deposit) = transaction {
1622 if tx_deposit.mint == Some(0) {
1623 tx_deposit.mint = None;
1624 }
1625 }
1626
1627 #[cfg(feature = "optimism")]
1628 let signature = if transaction.is_deposit() { TxDeposit::signature() } else { signature };
1629 let tx_signed = Self::new_unhashed(transaction, signature);
1630 let hash = tx_signed.hash();
1631 Ok(Self::new(tx_signed.transaction, tx_signed.signature, hash))
1632 }
1633}
1634
1635impl From<TransactionSigned> for TransactionRequest {
1636 fn from(tx: TransactionSigned) -> Self {
1637 match tx.transaction {
1638 Transaction::Legacy(tx) => tx.into(),
1639 Transaction::Eip2930(tx) => tx.into(),
1640 Transaction::Eip1559(tx) => tx.into(),
1641 Transaction::Eip4844(tx) => tx.into(),
1642 Transaction::Eip7702(tx) => tx.into(),
1643 Transaction::Seismic(tx) => tx.into(),
1644 #[cfg(feature = "optimism")]
1645 Transaction::Deposit(tx) => tx.into(),
1646 }
1647 }
1648}
1649
1650pub type TransactionSignedEcRecovered<T = TransactionSigned> = RecoveredTx<T>;
1652
1653#[derive(Debug, Clone, PartialEq, Hash, Eq, AsRef, Deref)]
1655pub struct RecoveredTx<T = TransactionSigned> {
1656 signer: Address,
1658 #[deref]
1660 #[as_ref]
1661 signed_transaction: T,
1662}
1663
1664impl<T> RecoveredTx<T> {
1667 pub const fn signer(&self) -> Address {
1669 self.signer
1670 }
1671
1672 pub const fn signer_ref(&self) -> &Address {
1674 &self.signer
1675 }
1676
1677 pub const fn as_signed(&self) -> &T {
1679 &self.signed_transaction
1680 }
1681
1682 pub fn into_signed(self) -> T {
1684 self.signed_transaction
1685 }
1686
1687 pub fn to_components(self) -> (T, Address) {
1689 (self.signed_transaction, self.signer)
1690 }
1691
1692 #[inline]
1695 pub const fn from_signed_transaction(signed_transaction: T, signer: Address) -> Self {
1696 Self { signed_transaction, signer }
1697 }
1698
1699 pub fn map_transaction<Tx>(self, f: impl FnOnce(T) -> Tx) -> RecoveredTx<Tx> {
1701 RecoveredTx::from_signed_transaction(f(self.signed_transaction), self.signer)
1702 }
1703}
1704
1705impl RecoveredTx<TransactionSigned> {
1706 pub fn shield_input(mut self) -> Self {
1708 self.signed_transaction = self.signed_transaction.shield_input();
1709 self
1710 }
1711}
1712
1713impl<T: Encodable> Encodable for RecoveredTx<T> {
1714 fn encode(&self, out: &mut dyn bytes::BufMut) {
1718 self.signed_transaction.encode(out)
1719 }
1720
1721 fn length(&self) -> usize {
1722 self.signed_transaction.length()
1723 }
1724}
1725
1726impl<T: SignedTransaction> Decodable for RecoveredTx<T> {
1727 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1728 let signed_transaction = T::decode(buf)?;
1729 let signer = signed_transaction
1730 .recover_signer()
1731 .ok_or(RlpError::Custom("Unable to recover decoded transaction signer."))?;
1732 Ok(Self { signer, signed_transaction })
1733 }
1734}
1735
1736impl<T: Encodable2718> Encodable2718 for RecoveredTx<T> {
1737 fn type_flag(&self) -> Option<u8> {
1738 self.signed_transaction.type_flag()
1739 }
1740
1741 fn encode_2718_len(&self) -> usize {
1742 self.signed_transaction.encode_2718_len()
1743 }
1744
1745 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
1746 self.signed_transaction.encode_2718(out)
1747 }
1748
1749 fn trie_hash(&self) -> B256 {
1750 self.signed_transaction.trie_hash()
1751 }
1752}
1753
1754pub trait SignedTransactionIntoRecoveredExt: SignedTransaction {
1756 fn try_ecrecovered(&self) -> Option<RecoveredTx<Self>> {
1758 let signer = self.recover_signer()?;
1759 Some(RecoveredTx { signed_transaction: self.clone(), signer })
1760 }
1761
1762 fn try_into_ecrecovered(self) -> Result<RecoveredTx<Self>, Self> {
1767 match self.recover_signer() {
1768 None => Err(self),
1769 Some(signer) => Ok(RecoveredTx { signed_transaction: self, signer }),
1770 }
1771 }
1772
1773 fn into_ecrecovered_unchecked(self) -> Option<RecoveredTx<Self>> {
1778 let signer = self.recover_signer_unchecked()?;
1779 Some(RecoveredTx::from_signed_transaction(self, signer))
1780 }
1781
1782 fn with_signer(self, signer: Address) -> RecoveredTx<Self> {
1784 RecoveredTx::from_signed_transaction(self, signer)
1785 }
1786}
1787
1788impl<T> SignedTransactionIntoRecoveredExt for T where T: SignedTransaction {}
1789
1790#[cfg(feature = "serde-bincode-compat")]
1792pub mod serde_bincode_compat {
1793 use alloc::borrow::Cow;
1794 use alloy_consensus::{
1795 transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxEip7702, TxLegacy, TxSeismic},
1796 TxEip4844,
1797 };
1798 use alloy_primitives::{PrimitiveSignature as Signature, TxHash};
1799 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1800 use serde_with::{DeserializeAs, SerializeAs};
1801
1802 #[derive(Debug, Serialize, Deserialize)]
1818 #[allow(missing_docs)]
1819 pub enum Transaction<'a> {
1820 Legacy(TxLegacy<'a>),
1821 Seismic(TxSeismic<'a>),
1822 Eip2930(TxEip2930<'a>),
1823 Eip1559(TxEip1559<'a>),
1824 Eip4844(Cow<'a, TxEip4844>),
1825 Eip7702(TxEip7702<'a>),
1826 #[cfg(feature = "optimism")]
1827 Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit<'a>),
1828 }
1829
1830 impl<'a> From<&'a super::Transaction> for Transaction<'a> {
1831 fn from(value: &'a super::Transaction) -> Self {
1832 match value {
1833 super::Transaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)),
1834 super::Transaction::Seismic(tx) => Self::Seismic(TxSeismic::from(tx)),
1835 super::Transaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)),
1836 super::Transaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)),
1837 super::Transaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
1838 super::Transaction::Eip7702(tx) => Self::Eip7702(TxEip7702::from(tx)),
1839 #[cfg(feature = "optimism")]
1840 super::Transaction::Deposit(tx) => {
1841 Self::Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit::from(tx))
1842 }
1843 }
1844 }
1845 }
1846
1847 impl<'a> From<Transaction<'a>> for super::Transaction {
1848 fn from(value: Transaction<'a>) -> Self {
1849 match value {
1850 Transaction::Legacy(tx) => Self::Legacy(tx.into()),
1851 Transaction::Eip2930(tx) => Self::Eip2930(tx.into()),
1852 Transaction::Eip1559(tx) => Self::Eip1559(tx.into()),
1853 Transaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
1854 Transaction::Eip7702(tx) => Self::Eip7702(tx.into()),
1855 Transaction::Seismic(tx) => Self::Seismic(tx.into()),
1856 #[cfg(feature = "optimism")]
1857 Transaction::Deposit(tx) => Self::Deposit(tx.into()),
1858 }
1859 }
1860 }
1861
1862 impl SerializeAs<super::Transaction> for Transaction<'_> {
1863 fn serialize_as<S>(source: &super::Transaction, serializer: S) -> Result<S::Ok, S::Error>
1864 where
1865 S: Serializer,
1866 {
1867 Transaction::from(source).serialize(serializer)
1868 }
1869 }
1870
1871 impl<'de> DeserializeAs<'de, super::Transaction> for Transaction<'de> {
1872 fn deserialize_as<D>(deserializer: D) -> Result<super::Transaction, D::Error>
1873 where
1874 D: Deserializer<'de>,
1875 {
1876 Transaction::deserialize(deserializer).map(Into::into)
1877 }
1878 }
1879
1880 #[derive(Debug, Serialize, Deserialize)]
1896 pub struct TransactionSigned<'a> {
1897 hash: TxHash,
1898 signature: Signature,
1899 transaction: Transaction<'a>,
1900 }
1901
1902 impl<'a> From<&'a super::TransactionSigned> for TransactionSigned<'a> {
1903 fn from(value: &'a super::TransactionSigned) -> Self {
1904 Self {
1905 hash: value.hash(),
1906 signature: value.signature,
1907 transaction: Transaction::from(&value.transaction),
1908 }
1909 }
1910 }
1911
1912 impl<'a> From<TransactionSigned<'a>> for super::TransactionSigned {
1913 fn from(value: TransactionSigned<'a>) -> Self {
1914 Self {
1915 hash: value.hash.into(),
1916 signature: value.signature,
1917 transaction: value.transaction.into(),
1918 }
1919 }
1920 }
1921
1922 impl SerializeAs<super::TransactionSigned> for TransactionSigned<'_> {
1923 fn serialize_as<S>(
1924 source: &super::TransactionSigned,
1925 serializer: S,
1926 ) -> Result<S::Ok, S::Error>
1927 where
1928 S: Serializer,
1929 {
1930 TransactionSigned::from(source).serialize(serializer)
1931 }
1932 }
1933
1934 impl<'de> DeserializeAs<'de, super::TransactionSigned> for TransactionSigned<'de> {
1935 fn deserialize_as<D>(deserializer: D) -> Result<super::TransactionSigned, D::Error>
1936 where
1937 D: Deserializer<'de>,
1938 {
1939 TransactionSigned::deserialize(deserializer).map(Into::into)
1940 }
1941 }
1942
1943 #[cfg(test)]
1944 mod tests {
1945 use super::super::{serde_bincode_compat, Transaction, TransactionSigned};
1946 use arbitrary::Arbitrary;
1947 use rand::Rng;
1948 use reth_testing_utils::generators;
1949 use serde::{Deserialize, Serialize};
1950 use serde_with::serde_as;
1951
1952 #[test]
1953 fn test_transaction_bincode_roundtrip() {
1954 #[serde_as]
1955 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1956 struct Data {
1957 #[serde_as(as = "serde_bincode_compat::Transaction")]
1958 transaction: Transaction,
1959 }
1960
1961 let mut bytes = [0u8; 1024];
1962 generators::rng().fill(bytes.as_mut_slice());
1963 let data = Data {
1964 transaction: Transaction::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
1965 .unwrap(),
1966 };
1967
1968 let encoded = bincode::serialize(&data).unwrap();
1969 let decoded: Data = bincode::deserialize(&encoded).unwrap();
1970 assert_eq!(decoded, data);
1971 }
1972
1973 #[test]
1974 fn test_transaction_signed_bincode_roundtrip() {
1975 #[serde_as]
1976 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1977 struct Data {
1978 #[serde_as(as = "serde_bincode_compat::TransactionSigned")]
1979 transaction: TransactionSigned,
1980 }
1981
1982 let mut bytes = [0u8; 1024];
1983 generators::rng().fill(bytes.as_mut_slice());
1984 let data = Data {
1985 transaction: TransactionSigned::arbitrary(&mut arbitrary::Unstructured::new(
1986 &bytes,
1987 ))
1988 .unwrap(),
1989 };
1990
1991 let encoded = bincode::serialize(&data).unwrap();
1992 let decoded: Data = bincode::deserialize(&encoded).unwrap();
1993 assert_eq!(decoded, data);
1994 }
1995 }
1996}
1997
1998pub fn recover_signers<'a, I, T>(txes: I, num_txes: usize) -> Option<Vec<Address>>
2002where
2003 T: SignedTransaction,
2004 I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send,
2005{
2006 if num_txes < *PARALLEL_SENDER_RECOVERY_THRESHOLD {
2007 txes.into_iter().map(|tx| tx.recover_signer()).collect()
2008 } else {
2009 txes.into_par_iter().map(|tx| tx.recover_signer()).collect()
2010 }
2011}
2012
2013pub fn recover_signers_unchecked<'a, I, T>(txes: I, num_txes: usize) -> Option<Vec<Address>>
2018where
2019 T: SignedTransaction,
2020 I: IntoParallelIterator<Item = &'a T> + IntoIterator<Item = &'a T> + Send,
2021{
2022 if num_txes < *PARALLEL_SENDER_RECOVERY_THRESHOLD {
2023 txes.into_iter().map(|tx| tx.recover_signer_unchecked()).collect()
2024 } else {
2025 txes.into_par_iter().map(|tx| tx.recover_signer_unchecked()).collect()
2026 }
2027}
2028
2029#[cfg(test)]
2030mod tests {
2031 use crate::{
2032 transaction::{TxEip1559, TxKind, TxLegacy},
2033 RecoveredTx, Transaction, TransactionSigned,
2034 };
2035 use alloy_consensus::Transaction as _;
2036 use alloy_eips::eip2718::{Decodable2718, Encodable2718};
2037 use alloy_primitives::{
2038 address, b256, bytes, hex, Address, Bytes, PrimitiveSignature as Signature, B256, U256,
2039 };
2040 use alloy_rlp::{Decodable, Encodable, Error as RlpError};
2041 use reth_chainspec::MIN_TRANSACTION_GAS;
2042 use reth_codecs::Compact;
2043 use reth_primitives_traits::SignedTransaction;
2044 use std::str::FromStr;
2045
2046 #[test]
2047 fn test_decode_empty_typed_tx() {
2048 let input = [0x80u8];
2049 let res = TransactionSigned::decode(&mut &input[..]).unwrap_err();
2050 assert_eq!(RlpError::InputTooShort, res);
2051 }
2052
2053 #[test]
2054 fn raw_kind_encoding_sanity() {
2055 let mut buf = Vec::new();
2057 TxKind::Create.encode(&mut buf);
2058 assert_eq!(buf, vec![0x80]);
2059
2060 let buf = [0x80];
2062 let decoded = TxKind::decode(&mut &buf[..]).unwrap();
2063 assert_eq!(decoded, TxKind::Create);
2064 }
2065
2066 #[test]
2067 fn test_decode_create_goerli() {
2068 let tx_bytes = hex!("b901f202f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471");
2070
2071 let decoded = TransactionSigned::decode(&mut &tx_bytes[..]).unwrap();
2072 assert_eq!(tx_bytes.len(), decoded.length());
2073 assert_eq!(tx_bytes, &alloy_rlp::encode(decoded)[..]);
2074 }
2075
2076 #[test]
2077 fn test_decode_recover_mainnet_tx() {
2078 let tx_bytes = hex!("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9");
2080
2081 let decoded = TransactionSigned::decode_2718(&mut &tx_bytes[..]).unwrap();
2082 assert_eq!(
2083 decoded.recover_signer(),
2084 Some(Address::from_str("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5").unwrap())
2085 );
2086 }
2087
2088 #[test]
2089 fn test_decode_recover_sepolia_4844_tx() {
2092 use alloy_primitives::{address, b256};
2093
2094 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
2096 let decoded = TransactionSigned::decode_2718(&mut raw_tx.as_slice()).unwrap();
2097 assert!(decoded.is_eip4844());
2098
2099 let from = decoded.recover_signer();
2100 assert_eq!(from, Some(address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2")));
2101
2102 let tx = decoded.transaction;
2103
2104 assert_eq!(tx.to(), Some(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064")));
2105
2106 assert_eq!(
2107 tx.blob_versioned_hashes(),
2108 Some(
2109 &[
2110 b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
2111 b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
2112 b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
2113 b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
2114 b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549"),
2115 ][..]
2116 )
2117 );
2118 }
2119
2120 #[test]
2121 fn decode_transaction_consumes_buffer() {
2122 let bytes = &mut &hex!("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")[..];
2123 let _transaction_res = TransactionSigned::decode(bytes).unwrap();
2124 assert_eq!(
2125 bytes.len(),
2126 0,
2127 "did not consume all bytes in the buffer, {:?} remaining",
2128 bytes.len()
2129 );
2130 }
2131
2132 #[test]
2133 fn decode_multiple_network_txs() {
2134 let bytes = hex!("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18");
2135 let transaction = Transaction::Legacy(TxLegacy {
2136 chain_id: Some(4u64),
2137 nonce: 2,
2138 gas_price: 1000000000,
2139 gas_limit: 100000,
2140 to: Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap().into(),
2141 value: U256::from(1000000000000000u64),
2142 input: Bytes::default(),
2143 });
2144 let signature = Signature::new(
2145 U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
2146 .unwrap(),
2147 U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
2148 .unwrap(),
2149 false,
2150 );
2151 let hash = b256!("a517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34");
2152 test_decode_and_encode(&bytes, transaction, signature, Some(hash));
2153
2154 let bytes = hex!("f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da");
2155 let transaction = Transaction::Legacy(TxLegacy {
2156 chain_id: Some(4),
2157 nonce: 1u64,
2158 gas_price: 1000000000,
2159 gas_limit: 100000,
2160 to: Address::from_slice(&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..]).into(),
2161 value: U256::from(693361000000000u64),
2162 input: Default::default(),
2163 });
2164 let signature = Signature::new(
2165 U256::from_str("0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a")
2166 .unwrap(),
2167 U256::from_str("0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da")
2168 .unwrap(),
2169 false,
2170 );
2171 test_decode_and_encode(&bytes, transaction, signature, None);
2172
2173 let bytes = hex!("f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88");
2174 let transaction = Transaction::Legacy(TxLegacy {
2175 chain_id: Some(4),
2176 nonce: 3,
2177 gas_price: 2000000000,
2178 gas_limit: 10000000,
2179 to: Address::from_slice(&hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046")[..]).into(),
2180 value: U256::from(1000000000000000u64),
2181 input: Bytes::default(),
2182 });
2183 let signature = Signature::new(
2184 U256::from_str("0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071")
2185 .unwrap(),
2186 U256::from_str("0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88")
2187 .unwrap(),
2188 false,
2189 );
2190 test_decode_and_encode(&bytes, transaction, signature, None);
2191
2192 let bytes = hex!("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469");
2193 let transaction = Transaction::Eip1559(TxEip1559 {
2194 chain_id: 4,
2195 nonce: 26,
2196 max_priority_fee_per_gas: 1500000000,
2197 max_fee_per_gas: 1500000013,
2198 gas_limit: MIN_TRANSACTION_GAS,
2199 to: Address::from_slice(&hex!("61815774383099e24810ab832a5b2a5425c154d5")[..]).into(),
2200 value: U256::from(3000000000000000000u64),
2201 input: Default::default(),
2202 access_list: Default::default(),
2203 });
2204 let signature = Signature::new(
2205 U256::from_str("0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd")
2206 .unwrap(),
2207 U256::from_str("0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469")
2208 .unwrap(),
2209 true,
2210 );
2211 test_decode_and_encode(&bytes, transaction, signature, None);
2212
2213 let bytes = hex!("f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860");
2214 let transaction = Transaction::Legacy(TxLegacy {
2215 chain_id: Some(4),
2216 nonce: 15,
2217 gas_price: 2200000000,
2218 gas_limit: 34811,
2219 to: Address::from_slice(&hex!("cf7f9e66af820a19257a2108375b180b0ec49167")[..]).into(),
2220 value: U256::from(1234),
2221 input: Bytes::default(),
2222 });
2223 let signature = Signature::new(
2224 U256::from_str("0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981")
2225 .unwrap(),
2226 U256::from_str("0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860")
2227 .unwrap(),
2228 true,
2229 );
2230 test_decode_and_encode(&bytes, transaction, signature, None);
2231 }
2232
2233 fn test_decode_and_encode(
2234 bytes: &[u8],
2235 transaction: Transaction,
2236 signature: Signature,
2237 hash: Option<B256>,
2238 ) {
2239 let expected = TransactionSigned::new_unhashed(transaction, signature);
2240 if let Some(hash) = hash {
2241 assert_eq!(hash, expected.hash());
2242 }
2243 assert_eq!(bytes.len(), expected.length());
2244
2245 let decoded = TransactionSigned::decode(&mut &bytes[..]).unwrap();
2246 assert_eq!(expected, decoded);
2247 assert_eq!(bytes, &alloy_rlp::encode(expected));
2248 }
2249
2250 #[test]
2251 fn decode_raw_tx_and_recover_signer() {
2252 use alloy_primitives::hex_literal::hex;
2253 let hash: B256 =
2256 hex!("559fb34c4a7f115db26cbf8505389475caaab3df45f5c7a0faa4abfa3835306c").into();
2257 let signer: Address = hex!("641c5d790f862a58ec7abcfd644c0442e9c201b3").into();
2258 let raw = hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2");
2259
2260 let mut pointer = raw.as_ref();
2261 let tx = TransactionSigned::decode(&mut pointer).unwrap();
2262 assert_eq!(tx.hash(), hash, "Expected same hash");
2263 assert_eq!(tx.recover_signer(), Some(signer), "Recovering signer should pass.");
2264 }
2265
2266 #[test]
2267 fn test_envelop_encode() {
2268 let input = hex!("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76");
2270 let decoded = TransactionSigned::decode(&mut &input[..]).unwrap();
2271
2272 let encoded = decoded.encoded_2718();
2273 assert_eq!(encoded[..], input);
2274 }
2275
2276 #[test]
2277 fn test_envelop_decode() {
2278 let input = bytes!("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76");
2280 let decoded = TransactionSigned::decode_2718(&mut input.as_ref()).unwrap();
2281
2282 let encoded = decoded.encoded_2718();
2283 assert_eq!(encoded, input);
2284 }
2285
2286 #[test]
2287 fn test_decode_signed_ec_recovered_transaction() {
2288 let input = hex!("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76");
2290 let tx = TransactionSigned::decode(&mut &input[..]).unwrap();
2291 let recovered = tx.into_ecrecovered().unwrap();
2292
2293 let decoded = RecoveredTx::decode(&mut &alloy_rlp::encode(&recovered)[..]).unwrap();
2294 assert_eq!(recovered, decoded)
2295 }
2296
2297 #[test]
2298 fn test_decode_tx() {
2299 let data = hex!("b86f02f86c0705843b9aca008506fc23ac00830124f89400000000000000000000000000000000000003160180c001a00293c713e2f1eab91c366621ff2f867e05ad7e99d4aa5d069aafeb9e1e8c9b6aa05ec6c0605ff20b57c90a6484ec3b0509e5923733d06f9b69bee9a2dabe4f1352");
2301 let tx = TransactionSigned::decode(&mut data.as_slice()).unwrap();
2302 let mut b = Vec::with_capacity(data.len());
2303 tx.encode(&mut b);
2304 assert_eq!(data.as_slice(), b.as_slice());
2305
2306 let data = hex!("f865048506fc23ac00830124f8940000000000000000000000000000000000000316018032a06b8fdfdcb84790816b7af85b19305f493665fe8b4e7c51ffdd7cc144cd776a60a028a09ab55def7b8d6602ba1c97a0ebbafe64ffc9c8e89520cec97a8edfb2ebe9");
2307 let tx = TransactionSigned::decode(&mut data.as_slice()).unwrap();
2308 let mut b = Vec::with_capacity(data.len());
2309 tx.encode(&mut b);
2310 assert_eq!(data.as_slice(), b.as_slice());
2311 }
2312
2313 #[cfg(feature = "secp256k1")]
2314 proptest::proptest! {
2315 #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1))]
2316
2317 #[test]
2318 fn test_parallel_recovery_order(txes in proptest::collection::vec(
2319 proptest_arbitrary_interop::arb::<Transaction>(),
2320 *crate::transaction::PARALLEL_SENDER_RECOVERY_THRESHOLD * 5
2321 )) {
2322 let mut rng =rand::thread_rng();
2323 let secp = secp256k1::Secp256k1::new();
2324 let txes: Vec<TransactionSigned> = txes.into_iter().map(|mut tx| {
2325 if let Some(chain_id) = tx.chain_id() {
2326 tx.set_chain_id(chain_id % (u64::MAX / 2 - 36));
2328 }
2329
2330 let key_pair = secp256k1::Keypair::new(&secp, &mut rng);
2331
2332 let signature =
2333 crate::sign_message(B256::from_slice(&key_pair.secret_bytes()[..]), tx.signature_hash()).unwrap();
2334
2335 TransactionSigned::new_unhashed(tx, signature)
2336 }).collect();
2337
2338 let parallel_senders = TransactionSigned::recover_signers(&txes, txes.len()).unwrap();
2339 let seq_senders = txes.iter().map(|tx| tx.recover_signer()).collect::<Option<Vec<_>>>().unwrap();
2340
2341 assert_eq!(parallel_senders, seq_senders);
2342 }
2343 }
2344
2345 #[test]
2347 fn recover_legacy_singer() {
2348 let data = hex!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
2349 let tx = TransactionSigned::decode_rlp_legacy_transaction(&mut data.as_slice()).unwrap();
2350 assert!(tx.is_legacy());
2351 let sender = tx.recover_signer().unwrap();
2352 assert_eq!(sender, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
2353 }
2354
2355 #[test]
2358 fn recover_enveloped() {
2359 let data = hex!("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8");
2360 let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap();
2361 let sender = tx.recover_signer().unwrap();
2362 assert_eq!(sender, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
2363 assert_eq!(tx.to(), Some(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
2364 assert_eq!(tx.input().as_ref(), hex!("1b55ba3a"));
2365 let encoded = tx.encoded_2718();
2366 assert_eq!(encoded.as_ref(), data.to_vec());
2367 }
2368
2369 #[test]
2372 fn recover_pre_eip2() {
2373 let data = hex!("f8ea0c850ba43b7400832dc6c0942935aa0a2d2fbb791622c29eb1c117b65b7a908580b884590528a9000000000000000000000001878ace42092b7f1ae1f28d16c1272b1aa80ca4670000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000d02ab486cedc0000000000000000000000000000000000000000000000000000557fe293cabc08cf1ca05bfaf3fda0a56b49cc78b22125feb5ae6a99d2b4781f00507d8b02c173771c85a0b5da0dbe6c5bc53740d0071fc83eb17ba0f709e49e9ae7df60dee625ef51afc5");
2374 let tx = TransactionSigned::decode_2718(&mut data.as_slice()).unwrap();
2375 let sender = tx.recover_signer();
2376 assert!(sender.is_none());
2377 let sender = tx.recover_signer_unchecked().unwrap();
2378
2379 assert_eq!(sender, address!("7e9e359edf0dbacf96a9952fa63092d919b0842b"));
2380 }
2381
2382 #[test]
2383 fn transaction_signed_no_hash_zstd_codec() {
2384 let signature = Signature::new(
2387 U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae")
2388 .unwrap(),
2389 U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18")
2390 .unwrap(),
2391 false,
2392 );
2393
2394 let inputs: Vec<Vec<u8>> = vec![
2395 vec![],
2396 vec![0],
2397 vec![255],
2398 vec![1u8; 31],
2399 vec![255u8; 31],
2400 vec![1u8; 32],
2401 vec![255u8; 32],
2402 vec![1u8; 64],
2403 vec![255u8; 64],
2404 ];
2405
2406 for input in inputs {
2407 let transaction = Transaction::Legacy(TxLegacy {
2408 chain_id: Some(4u64),
2409 nonce: 2,
2410 gas_price: 1000000000,
2411 gas_limit: 100000,
2412 to: Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap().into(),
2413 value: U256::from(1000000000000000u64),
2414 input: Bytes::from(input),
2415 });
2416
2417 let tx = TransactionSigned::new_unhashed(transaction, signature);
2418 test_transaction_signed_to_from_compact(tx);
2419 }
2420 }
2421
2422 fn test_transaction_signed_to_from_compact(tx: TransactionSigned) {
2423 let mut buff: Vec<u8> = Vec::new();
2425 let written_bytes = tx.to_compact(&mut buff);
2426 let (decoded, _) = TransactionSigned::from_compact(&buff, written_bytes);
2427 assert_eq!(tx, decoded);
2428 }
2429
2430 #[test]
2431 fn create_txs_disallowed_for_eip4844() {
2432 let data =
2433 [3, 208, 128, 128, 123, 128, 120, 128, 129, 129, 128, 192, 129, 129, 192, 128, 128, 9];
2434 let res = TransactionSigned::decode_2718(&mut &data[..]);
2435
2436 assert!(res.is_err());
2437 }
2438}