reth_seismic_primitives/transaction/
signed.rs

1//! A signed Seismic transaction.
2
3use alloc::vec::Vec;
4use alloy_consensus::{
5    transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
6    SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy,
7    Typed2718,
8};
9use alloy_eips::{
10    eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
11    eip2930::AccessList,
12    eip7702::SignedAuthorization,
13};
14use alloy_evm::FromRecoveredTx;
15use alloy_primitives::{keccak256, Address, Bytes, Signature, TxHash, TxKind, Uint, B256};
16use alloy_rlp::Header;
17use core::{
18    hash::{Hash, Hasher},
19    mem,
20    ops::Deref,
21};
22use derive_more::{AsRef, Deref};
23#[cfg(any(test, feature = "reth-codec"))]
24use proptest as _;
25use reth_primitives_traits::{
26    crypto::secp256k1::{recover_signer, recover_signer_unchecked},
27    sync::OnceLock,
28    transaction::signed::RecoveryError,
29    InMemorySize, SignedTransaction, SignerRecoverable,
30};
31use revm_context::{either::Either, TxEnv};
32use seismic_alloy_consensus::{
33    InputDecryptionElements, InputDecryptionElementsError, SeismicTxEnvelope,
34    SeismicTypedTransaction, TxSeismic, TxSeismicElements,
35};
36use seismic_revm::{transaction::abstraction::RngMode, SeismicTransaction};
37
38// Seismic imports, not used by upstream
39use alloy_consensus::TxEip4844Variant;
40use alloy_evm::FromTxWithEncoded;
41
42/// Signed transaction.
43///
44/// [`SeismicTransactionSigned`] is a wrapper around a [`SeismicTypedTransaction`] enum,
45/// which can be Seismic(TxSeismic) with additional fields, or Ethereum compatible transactions.
46#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[derive(Debug, Clone, Eq, AsRef, Deref)]
49pub struct SeismicTransactionSigned {
50    /// Transaction hash
51    #[cfg_attr(feature = "serde", serde(skip))]
52    hash: OnceLock<TxHash>,
53    /// The transaction signature values
54    signature: Signature,
55    /// Raw transaction info
56    #[deref]
57    #[as_ref]
58    transaction: SeismicTypedTransaction,
59}
60
61impl SeismicTransactionSigned {
62    /// Creates a new signed transaction from the given transaction, signature and hash.
63    pub fn new(transaction: SeismicTypedTransaction, signature: Signature, hash: B256) -> Self {
64        Self { hash: hash.into(), signature, transaction }
65    }
66
67    /// Consumes the type and returns the transaction.
68    #[inline]
69    pub fn into_transaction(self) -> SeismicTypedTransaction {
70        self.transaction
71    }
72
73    /// Returns the transaction.
74    #[inline]
75    pub const fn transaction(&self) -> &SeismicTypedTransaction {
76        &self.transaction
77    }
78
79    /// Splits the `SeismicTransactionSigned` into its transaction and signature.
80    pub fn split(self) -> (SeismicTypedTransaction, Signature) {
81        (self.transaction, self.signature)
82    }
83
84    /// Creates a new signed transaction from the given transaction and signature without the hash.
85    ///
86    /// Note: this only calculates the hash on the first [`SeismicTransactionSigned::hash`] call.
87    pub fn new_unhashed(transaction: SeismicTypedTransaction, signature: Signature) -> Self {
88        Self { hash: Default::default(), signature, transaction }
89    }
90
91    /// Splits the transaction into parts.
92    pub fn into_parts(self) -> (SeismicTypedTransaction, Signature, B256) {
93        let hash = *self.hash.get_or_init(|| self.recalculate_hash());
94        (self.transaction, self.signature, hash)
95    }
96}
97
98impl SignerRecoverable for SeismicTransactionSigned {
99    fn recover_signer(&self) -> Result<Address, RecoveryError> {
100        let Self { transaction, signature, .. } = self;
101        let signature_hash = signature_hash(transaction);
102        recover_signer(signature, signature_hash)
103    }
104
105    fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
106        let Self { transaction, signature, .. } = self;
107        let signature_hash = signature_hash(transaction);
108        recover_signer_unchecked(signature, signature_hash)
109    }
110}
111
112impl SignedTransaction for SeismicTransactionSigned {
113    fn tx_hash(&self) -> &TxHash {
114        self.hash.get_or_init(|| self.recalculate_hash())
115    }
116
117    fn recover_signer_unchecked_with_buf(
118        &self,
119        buf: &mut Vec<u8>,
120    ) -> Result<Address, RecoveryError> {
121        match &self.transaction {
122            SeismicTypedTransaction::Legacy(tx) => tx.encode_for_signing(buf),
123            SeismicTypedTransaction::Eip2930(tx) => tx.encode_for_signing(buf),
124            SeismicTypedTransaction::Eip1559(tx) => tx.encode_for_signing(buf),
125            SeismicTypedTransaction::Eip4844(tx) => tx.encode_for_signing(buf),
126            SeismicTypedTransaction::Eip7702(tx) => tx.encode_for_signing(buf),
127            SeismicTypedTransaction::Seismic(tx) => tx.encode_for_signing(buf),
128        };
129        recover_signer_unchecked(&self.signature, keccak256(buf))
130    }
131
132    fn recalculate_hash(&self) -> B256 {
133        keccak256(self.encoded_2718())
134    }
135}
136
137macro_rules! impl_from_signed {
138    ($($tx:ident),*) => {
139        $(
140            impl From<Signed<$tx>> for SeismicTransactionSigned {
141                fn from(value: Signed<$tx>) -> Self {
142                    let(tx,sig,hash) = value.into_parts();
143                    Self::new(tx.into(), sig, hash)
144                }
145            }
146        )*
147    };
148}
149
150impl_from_signed!(
151    TxLegacy,
152    TxEip2930,
153    TxEip1559,
154    TxEip4844Variant,
155    TxEip7702,
156    TxSeismic,
157    SeismicTypedTransaction
158);
159
160impl From<SeismicTxEnvelope> for SeismicTransactionSigned {
161    fn from(value: SeismicTxEnvelope) -> Self {
162        match value {
163            SeismicTxEnvelope::Legacy(tx) => tx.into(),
164            SeismicTxEnvelope::Eip2930(tx) => tx.into(),
165            SeismicTxEnvelope::Eip1559(tx) => tx.into(),
166            SeismicTxEnvelope::Eip4844(tx) => tx.into(),
167            SeismicTxEnvelope::Eip7702(tx) => tx.into(),
168            SeismicTxEnvelope::Seismic(tx) => tx.into(),
169        }
170    }
171}
172
173impl From<SeismicTransactionSigned> for SeismicTxEnvelope {
174    fn from(value: SeismicTransactionSigned) -> Self {
175        let (tx, signature, hash) = value.into_parts();
176        match tx {
177            SeismicTypedTransaction::Legacy(tx) => {
178                Signed::new_unchecked(tx, signature, hash).into()
179            }
180            SeismicTypedTransaction::Eip2930(tx) => {
181                Signed::new_unchecked(tx, signature, hash).into()
182            }
183            SeismicTypedTransaction::Eip1559(tx) => {
184                Signed::new_unchecked(tx, signature, hash).into()
185            }
186            SeismicTypedTransaction::Eip4844(tx) => {
187                Signed::new_unchecked(tx, signature, hash).into()
188            }
189            SeismicTypedTransaction::Seismic(tx) => {
190                Signed::new_unchecked(tx, signature, hash).into()
191            }
192            SeismicTypedTransaction::Eip7702(tx) => {
193                Signed::new_unchecked(tx, signature, hash).into()
194            }
195        }
196    }
197}
198
199impl From<SeismicTransactionSigned> for Signed<SeismicTypedTransaction> {
200    fn from(value: SeismicTransactionSigned) -> Self {
201        let (tx, sig, hash) = value.into_parts();
202        Self::new_unchecked(tx, sig, hash)
203    }
204}
205
206// This function converts reth types (SeismicTransactionSigned) to revm types
207// (SeismicTransaction<TxEnv>)
208impl FromRecoveredTx<SeismicTransactionSigned> for SeismicTransaction<TxEnv> {
209    fn from_recovered_tx(tx: &SeismicTransactionSigned, sender: Address) -> Self {
210        let tx_hash = tx.tx_hash().clone();
211        let rng_mode = RngMode::Execution; // TODO WARNING: chose a default value
212        let tx = match &tx.transaction {
213            SeismicTypedTransaction::Legacy(tx) => SeismicTransaction::<TxEnv> {
214                base: TxEnv {
215                    gas_limit: tx.gas_limit,
216                    gas_price: tx.gas_price,
217                    gas_priority_fee: None,
218                    kind: tx.to,
219                    value: tx.value,
220                    data: tx.input.clone(),
221                    chain_id: tx.chain_id,
222                    nonce: tx.nonce,
223                    access_list: Default::default(),
224                    blob_hashes: Default::default(),
225                    max_fee_per_blob_gas: Default::default(),
226                    authorization_list: Default::default(),
227                    tx_type: 0,
228                    caller: sender,
229                },
230                tx_hash,
231                rng_mode,
232            },
233            SeismicTypedTransaction::Eip2930(tx) => SeismicTransaction::<TxEnv> {
234                base: TxEnv {
235                    gas_limit: tx.gas_limit,
236                    gas_price: tx.gas_price,
237                    gas_priority_fee: None,
238                    kind: tx.to,
239                    value: tx.value,
240                    data: tx.input.clone(),
241                    chain_id: Some(tx.chain_id),
242                    nonce: tx.nonce,
243                    access_list: tx.access_list.clone(),
244                    blob_hashes: Default::default(),
245                    max_fee_per_blob_gas: Default::default(),
246                    authorization_list: Default::default(),
247                    tx_type: 1,
248                    caller: sender,
249                },
250                tx_hash,
251                rng_mode,
252            },
253            SeismicTypedTransaction::Eip1559(tx) => SeismicTransaction::<TxEnv> {
254                base: TxEnv {
255                    gas_limit: tx.gas_limit,
256                    gas_price: tx.max_fee_per_gas,
257                    gas_priority_fee: Some(tx.max_priority_fee_per_gas),
258                    kind: tx.to,
259                    value: tx.value,
260                    data: tx.input.clone(),
261                    chain_id: Some(tx.chain_id),
262                    nonce: tx.nonce,
263                    access_list: tx.access_list.clone(),
264                    blob_hashes: Default::default(),
265                    max_fee_per_blob_gas: Default::default(),
266                    authorization_list: Default::default(),
267                    tx_type: 2,
268                    caller: sender,
269                },
270                tx_hash,
271                rng_mode,
272            },
273            SeismicTypedTransaction::Eip4844(tx) => match tx {
274                TxEip4844Variant::TxEip4844(tx) => SeismicTransaction::<TxEnv> {
275                    base: TxEnv {
276                        gas_limit: tx.gas_limit,
277                        gas_price: tx.max_fee_per_gas,
278                        gas_priority_fee: Some(tx.max_priority_fee_per_gas),
279                        kind: TxKind::Call(tx.to),
280                        value: tx.value,
281                        data: tx.input.clone(),
282                        chain_id: Some(tx.chain_id),
283                        nonce: tx.nonce,
284                        access_list: tx.access_list.clone(),
285                        blob_hashes: Default::default(),
286                        max_fee_per_blob_gas: Default::default(),
287                        authorization_list: Default::default(),
288                        tx_type: 4,
289                        caller: sender,
290                    },
291                    tx_hash,
292                    rng_mode,
293                },
294                TxEip4844Variant::TxEip4844WithSidecar(tx) => SeismicTransaction::<TxEnv> {
295                    base: TxEnv {
296                        gas_limit: tx.tx.gas_limit,
297                        gas_price: tx.tx.max_fee_per_gas,
298                        gas_priority_fee: Some(tx.tx.max_priority_fee_per_gas),
299                        kind: TxKind::Call(tx.tx.to),
300                        value: tx.tx.value,
301                        data: tx.tx.input.clone(),
302                        chain_id: Some(tx.tx.chain_id),
303                        nonce: tx.tx.nonce,
304                        access_list: tx.tx.access_list.clone(),
305                        blob_hashes: Default::default(),
306                        max_fee_per_blob_gas: Default::default(),
307                        authorization_list: Default::default(),
308                        tx_type: 4,
309                        caller: sender,
310                    },
311                    tx_hash,
312                    rng_mode,
313                },
314            },
315            SeismicTypedTransaction::Eip7702(tx) => SeismicTransaction::<TxEnv> {
316                base: TxEnv {
317                    gas_limit: tx.gas_limit,
318                    gas_price: tx.max_fee_per_gas,
319                    gas_priority_fee: Some(tx.max_priority_fee_per_gas),
320                    kind: TxKind::Call(tx.to),
321                    value: tx.value,
322                    data: tx.input.clone(),
323                    chain_id: Some(tx.chain_id),
324                    nonce: tx.nonce,
325                    access_list: tx.access_list.clone(),
326                    blob_hashes: Default::default(),
327                    max_fee_per_blob_gas: Default::default(),
328                    authorization_list: tx
329                        .authorization_list
330                        .iter()
331                        .map(|auth| Either::Left(auth.clone()))
332                        .collect(),
333                    tx_type: 4,
334                    caller: sender,
335                },
336                tx_hash,
337                rng_mode,
338            },
339            SeismicTypedTransaction::Seismic(tx) => SeismicTransaction::<TxEnv> {
340                base: TxEnv {
341                    gas_limit: tx.gas_limit,
342                    gas_price: tx.gas_price,
343                    gas_priority_fee: None,
344                    kind: tx.to,
345                    value: tx.value,
346                    data: tx.input.clone(),
347                    chain_id: Some(tx.chain_id),
348                    nonce: tx.nonce,
349                    access_list: Default::default(),
350                    blob_hashes: Default::default(),
351                    max_fee_per_blob_gas: Default::default(),
352                    authorization_list: Default::default(),
353                    tx_type: TxSeismic::TX_TYPE,
354                    caller: sender,
355                },
356                tx_hash,
357                rng_mode,
358            },
359        };
360        tracing::debug!("from_recovered_tx: tx: {:?}", tx);
361        tx
362    }
363}
364
365impl FromTxWithEncoded<SeismicTransactionSigned> for SeismicTransaction<TxEnv> {
366    fn from_encoded_tx(tx: &SeismicTransactionSigned, sender: Address, _encoded: Bytes) -> Self {
367        let tx_env = SeismicTransaction::<TxEnv>::from_recovered_tx(tx, sender);
368        Self { base: tx_env.base, tx_hash: tx_env.tx_hash, rng_mode: RngMode::Execution }
369    }
370}
371
372impl InMemorySize for SeismicTransactionSigned {
373    #[inline]
374    fn size(&self) -> usize {
375        mem::size_of::<TxHash>() + self.transaction.size() + mem::size_of::<Signature>()
376    }
377}
378
379impl alloy_rlp::Encodable for SeismicTransactionSigned {
380    fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
381        self.network_encode(out);
382    }
383
384    fn length(&self) -> usize {
385        let mut payload_length = self.encode_2718_len();
386        if !self.is_legacy() {
387            payload_length += Header { list: false, payload_length }.length();
388        }
389
390        payload_length
391    }
392}
393
394impl alloy_rlp::Decodable for SeismicTransactionSigned {
395    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
396        Self::network_decode(buf).map_err(Into::into)
397    }
398}
399
400impl Encodable2718 for SeismicTransactionSigned {
401    fn type_flag(&self) -> Option<u8> {
402        if Typed2718::is_legacy(self) {
403            None
404        } else {
405            Some(self.ty())
406        }
407    }
408
409    fn encode_2718_len(&self) -> usize {
410        match &self.transaction {
411            SeismicTypedTransaction::Legacy(legacy_tx) => {
412                legacy_tx.eip2718_encoded_length(&self.signature)
413            }
414            SeismicTypedTransaction::Eip2930(access_list_tx) => {
415                access_list_tx.eip2718_encoded_length(&self.signature)
416            }
417            SeismicTypedTransaction::Eip1559(dynamic_fee_tx) => {
418                dynamic_fee_tx.eip2718_encoded_length(&self.signature)
419            }
420            SeismicTypedTransaction::Eip4844(blob_tx) => {
421                blob_tx.eip2718_encoded_length(&self.signature)
422            }
423            SeismicTypedTransaction::Eip7702(set_code_tx) => {
424                set_code_tx.eip2718_encoded_length(&self.signature)
425            }
426            SeismicTypedTransaction::Seismic(seismic_tx) => {
427                seismic_tx.eip2718_encoded_length(&self.signature)
428            }
429        }
430    }
431
432    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
433        let Self { transaction, signature, .. } = self;
434
435        match &transaction {
436            SeismicTypedTransaction::Legacy(legacy_tx) => {
437                // do nothing w/ with_header
438                legacy_tx.eip2718_encode(signature, out)
439            }
440            SeismicTypedTransaction::Eip2930(access_list_tx) => {
441                access_list_tx.eip2718_encode(signature, out)
442            }
443            SeismicTypedTransaction::Eip1559(dynamic_fee_tx) => {
444                dynamic_fee_tx.eip2718_encode(signature, out)
445            }
446            SeismicTypedTransaction::Eip4844(blob_tx) => blob_tx.eip2718_encode(signature, out),
447            SeismicTypedTransaction::Eip7702(set_code_tx) => {
448                set_code_tx.eip2718_encode(signature, out)
449            }
450            SeismicTypedTransaction::Seismic(seismic_tx) => {
451                seismic_tx.eip2718_encode(signature, out)
452            }
453        }
454    }
455}
456
457impl Decodable2718 for SeismicTransactionSigned {
458    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
459        match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
460            seismic_alloy_consensus::SeismicTxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
461            seismic_alloy_consensus::SeismicTxType::Eip2930 => {
462                let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts();
463                let signed_tx = Self::new_unhashed(SeismicTypedTransaction::Eip2930(tx), signature);
464                signed_tx.hash.get_or_init(|| hash);
465                Ok(signed_tx)
466            }
467            seismic_alloy_consensus::SeismicTxType::Eip1559 => {
468                let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts();
469                let signed_tx = Self::new_unhashed(SeismicTypedTransaction::Eip1559(tx), signature);
470                signed_tx.hash.get_or_init(|| hash);
471                Ok(signed_tx)
472            }
473            seismic_alloy_consensus::SeismicTxType::Eip4844 => {
474                let (tx, signature, hash) = TxEip4844::rlp_decode_signed(buf)?.into_parts();
475                let signed_tx = Self::new_unhashed(
476                    SeismicTypedTransaction::Eip4844(TxEip4844Variant::TxEip4844(tx)),
477                    signature,
478                );
479                signed_tx.hash.get_or_init(|| hash);
480                Ok(signed_tx)
481            }
482            seismic_alloy_consensus::SeismicTxType::Eip7702 => {
483                let (tx, signature, hash) = TxEip7702::rlp_decode_signed(buf)?.into_parts();
484                let signed_tx = Self::new_unhashed(SeismicTypedTransaction::Eip7702(tx), signature);
485                signed_tx.hash.get_or_init(|| hash);
486                Ok(signed_tx)
487            }
488            seismic_alloy_consensus::SeismicTxType::Seismic => {
489                let (tx, signature, hash) = TxSeismic::rlp_decode_signed(buf)?.into_parts();
490                let signed_tx = Self::new_unhashed(SeismicTypedTransaction::Seismic(tx), signature);
491                signed_tx.hash.get_or_init(|| hash);
492                Ok(signed_tx)
493            }
494        }
495    }
496
497    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
498        let (transaction, signature) = TxLegacy::rlp_decode_with_signature(buf)?;
499        let signed_tx = Self::new_unhashed(SeismicTypedTransaction::Legacy(transaction), signature);
500
501        Ok(signed_tx)
502    }
503}
504
505impl Transaction for SeismicTransactionSigned {
506    fn chain_id(&self) -> Option<u64> {
507        self.deref().chain_id()
508    }
509
510    fn nonce(&self) -> u64 {
511        self.deref().nonce()
512    }
513
514    fn gas_limit(&self) -> u64 {
515        self.deref().gas_limit()
516    }
517
518    fn gas_price(&self) -> Option<u128> {
519        self.deref().gas_price()
520    }
521
522    fn max_fee_per_gas(&self) -> u128 {
523        self.deref().max_fee_per_gas()
524    }
525
526    fn max_priority_fee_per_gas(&self) -> Option<u128> {
527        self.deref().max_priority_fee_per_gas()
528    }
529
530    fn max_fee_per_blob_gas(&self) -> Option<u128> {
531        self.deref().max_fee_per_blob_gas()
532    }
533
534    fn priority_fee_or_price(&self) -> u128 {
535        self.deref().priority_fee_or_price()
536    }
537
538    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
539        self.deref().effective_gas_price(base_fee)
540    }
541
542    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
543        self.deref().effective_tip_per_gas(base_fee)
544    }
545
546    fn is_dynamic_fee(&self) -> bool {
547        self.deref().is_dynamic_fee()
548    }
549
550    fn kind(&self) -> TxKind {
551        self.deref().kind()
552    }
553
554    fn is_create(&self) -> bool {
555        self.deref().is_create()
556    }
557
558    fn value(&self) -> Uint<256, 4> {
559        self.deref().value()
560    }
561
562    fn input(&self) -> &Bytes {
563        self.deref().input()
564    }
565
566    fn access_list(&self) -> Option<&AccessList> {
567        self.deref().access_list()
568    }
569
570    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
571        self.deref().blob_versioned_hashes()
572    }
573
574    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
575        self.deref().authorization_list()
576    }
577}
578
579impl InputDecryptionElements for SeismicTransactionSigned {
580    fn get_decryption_elements(&self) -> Result<TxSeismicElements, InputDecryptionElementsError> {
581        self.transaction.get_decryption_elements()
582    }
583
584    fn get_input(&self) -> Bytes {
585        self.transaction.get_input()
586    }
587
588    fn set_input(&mut self, input: Bytes) -> Result<(), InputDecryptionElementsError> {
589        self.transaction.set_input(input)
590    }
591}
592
593impl Typed2718 for SeismicTransactionSigned {
594    fn ty(&self) -> u8 {
595        self.deref().ty()
596    }
597}
598
599impl PartialEq for SeismicTransactionSigned {
600    fn eq(&self, other: &Self) -> bool {
601        self.signature == other.signature &&
602            self.transaction == other.transaction &&
603            self.tx_hash() == other.tx_hash()
604    }
605}
606
607impl Hash for SeismicTransactionSigned {
608    fn hash<H: Hasher>(&self, state: &mut H) {
609        self.signature.hash(state);
610        self.transaction.hash(state);
611    }
612}
613
614#[cfg(feature = "reth-codec")]
615impl reth_codecs::Compact for SeismicTransactionSigned {
616    fn to_compact<B>(&self, buf: &mut B) -> usize
617    where
618        B: bytes::BufMut + AsMut<[u8]>,
619    {
620        let start = buf.as_mut().len();
621
622        // Placeholder for bitflags.
623        // The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit]
624        buf.put_u8(0);
625
626        let sig_bit = self.signature.to_compact(buf) as u8;
627        let zstd_bit = self.transaction.input().len() >= 32;
628
629        let tx_bits = if zstd_bit {
630            let mut tmp = Vec::with_capacity(256);
631            if cfg!(feature = "std") {
632                reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
633                    let mut compressor = compressor.borrow_mut();
634                    let tx_bits = self.transaction.to_compact(&mut tmp);
635                    buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
636                    tx_bits as u8
637                })
638            } else {
639                let mut compressor = reth_zstd_compressors::create_tx_compressor();
640                let tx_bits = self.transaction.to_compact(&mut tmp);
641                buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
642                tx_bits as u8
643            }
644        } else {
645            self.transaction.to_compact(buf) as u8
646        };
647
648        // Replace bitflags with the actual values
649        buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
650
651        buf.as_mut().len() - start
652    }
653
654    fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
655        use bytes::Buf;
656
657        // The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1]
658        let bitflags = buf.get_u8() as usize;
659
660        let sig_bit = bitflags & 1;
661        let (signature, buf) = Signature::from_compact(buf, sig_bit);
662
663        let zstd_bit = bitflags >> 3;
664        let (transaction, buf) = if zstd_bit != 0 {
665            if cfg!(feature = "std") {
666                reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
667                    let mut decompressor = decompressor.borrow_mut();
668
669                    // TODO: enforce that zstd is only present at a "top" level type
670                    let transaction_type = (bitflags & 0b110) >> 1;
671                    let (transaction, _) = SeismicTypedTransaction::from_compact(
672                        decompressor.decompress(buf),
673                        transaction_type,
674                    );
675
676                    (transaction, buf)
677                })
678            } else {
679                let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
680                let transaction_type = (bitflags & 0b110) >> 1;
681                let (transaction, _) = SeismicTypedTransaction::from_compact(
682                    decompressor.decompress(buf),
683                    transaction_type,
684                );
685
686                (transaction, buf)
687            }
688        } else {
689            let transaction_type = bitflags >> 1;
690            SeismicTypedTransaction::from_compact(buf, transaction_type)
691        };
692
693        (Self { signature, transaction, hash: Default::default() }, buf)
694    }
695}
696
697#[cfg(any(test, feature = "arbitrary"))]
698impl<'a> arbitrary::Arbitrary<'a> for SeismicTransactionSigned {
699    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
700        #[allow(unused_mut)]
701        let mut transaction = SeismicTypedTransaction::arbitrary(u)?;
702
703        let secp = secp256k1::Secp256k1::new();
704        let key_pair = secp256k1::Keypair::new(&secp, &mut rand_08::thread_rng());
705        let signature = reth_primitives_traits::crypto::secp256k1::sign_message(
706            B256::from_slice(&key_pair.secret_bytes()[..]),
707            signature_hash(&transaction),
708        )
709        .unwrap();
710
711        Ok(Self::new_unhashed(transaction, signature))
712    }
713}
714
715/// Calculates the signing hash for the transaction.
716fn signature_hash(tx: &SeismicTypedTransaction) -> B256 {
717    match tx {
718        SeismicTypedTransaction::Legacy(tx) => tx.signature_hash(),
719        SeismicTypedTransaction::Eip2930(tx) => tx.signature_hash(),
720        SeismicTypedTransaction::Eip1559(tx) => tx.signature_hash(),
721        SeismicTypedTransaction::Eip4844(tx) => match tx {
722            TxEip4844Variant::TxEip4844(tx) => tx.signature_hash(),
723            TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.signature_hash(),
724        },
725        SeismicTypedTransaction::Eip7702(tx) => tx.signature_hash(),
726        SeismicTypedTransaction::Seismic(tx) => tx.signature_hash(),
727    }
728}
729
730/// Bincode-compatible transaction type serde implementations.
731#[cfg(feature = "serde-bincode-compat")]
732pub mod serde_bincode_compat {
733    use alloy_consensus::transaction::serde_bincode_compat::{
734        TxEip1559, TxEip2930, TxEip7702, TxLegacy,
735    };
736    use alloy_primitives::{Signature, TxHash};
737    use reth_primitives_traits::{serde_bincode_compat::SerdeBincodeCompat, SignedTransaction};
738    use seismic_alloy_consensus::serde_bincode_compat::TxSeismic;
739    use serde::{Deserialize, Serialize};
740
741    /// Bincode-compatible [`super::SeismicTypedTransaction`] serde implementation.
742    #[derive(Debug, Serialize, Deserialize)]
743    #[allow(missing_docs)]
744    enum SeismicTypedTransaction<'a> {
745        Legacy(TxLegacy<'a>),
746        Eip2930(TxEip2930<'a>),
747        Eip1559(TxEip1559<'a>),
748        Eip7702(TxEip7702<'a>),
749        Seismic(seismic_alloy_consensus::serde_bincode_compat::TxSeismic<'a>),
750    }
751
752    impl<'a> From<&'a super::SeismicTypedTransaction> for SeismicTypedTransaction<'a> {
753        fn from(value: &'a super::SeismicTypedTransaction) -> Self {
754            match value {
755                super::SeismicTypedTransaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)),
756                super::SeismicTypedTransaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)),
757                super::SeismicTypedTransaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)),
758                super::SeismicTypedTransaction::Eip4844(_tx) => {
759                    todo!("seismic upstream merge:Eip4844 not supported")
760                }
761                super::SeismicTypedTransaction::Eip7702(tx) => Self::Eip7702(TxEip7702::from(tx)),
762                super::SeismicTypedTransaction::Seismic(tx) => Self::Seismic(TxSeismic::from(tx)),
763            }
764        }
765    }
766
767    impl<'a> From<SeismicTypedTransaction<'a>> for super::SeismicTypedTransaction {
768        fn from(value: SeismicTypedTransaction<'a>) -> Self {
769            match value {
770                SeismicTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
771                SeismicTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
772                SeismicTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
773                SeismicTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
774                SeismicTypedTransaction::Seismic(tx) => Self::Seismic(tx.into()),
775            }
776        }
777    }
778
779    /// Bincode-compatible [`super::SeismicTransactionSigned`] serde implementation.
780    #[derive(Debug, Serialize, Deserialize)]
781    pub struct SeismicTransactionSigned<'a> {
782        hash: TxHash,
783        signature: Signature,
784        transaction: SeismicTypedTransaction<'a>,
785    }
786
787    impl<'a> From<&'a super::SeismicTransactionSigned> for SeismicTransactionSigned<'a> {
788        fn from(value: &'a super::SeismicTransactionSigned) -> Self {
789            Self {
790                hash: *value.tx_hash(),
791                signature: value.signature,
792                transaction: SeismicTypedTransaction::from(&value.transaction),
793            }
794        }
795    }
796
797    impl<'a> From<SeismicTransactionSigned<'a>> for super::SeismicTransactionSigned {
798        fn from(value: SeismicTransactionSigned<'a>) -> Self {
799            Self {
800                hash: value.hash.into(),
801                signature: value.signature,
802                transaction: value.transaction.into(),
803            }
804        }
805    }
806
807    impl SerdeBincodeCompat for super::SeismicTransactionSigned {
808        type BincodeRepr<'a> = SeismicTransactionSigned<'a>;
809
810        fn as_repr(&self) -> Self::BincodeRepr<'_> {
811            self.into()
812        }
813
814        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
815            repr.into()
816        }
817    }
818}
819
820#[cfg(test)]
821mod tests {
822    use crate::test_utils::{get_signed_seismic_tx, get_signing_private_key};
823
824    use super::*;
825    use proptest::proptest;
826    use proptest_arbitrary_interop::arb;
827    use reth_codecs::Compact;
828    use seismic_alloy_consensus::SeismicTxType;
829
830    #[test]
831    fn recover_signer_test() {
832        let signed_tx = get_signed_seismic_tx();
833        let recovered_signer = signed_tx.recover_signer().expect("Failed to recover signer");
834
835        let expected_signer = Address::from_private_key(&get_signing_private_key());
836
837        assert_eq!(recovered_signer, expected_signer);
838    }
839
840    proptest! {
841        #[test]
842        fn test_roundtrip_2718(signed_tx in arb::<SeismicTransactionSigned>()) {
843            if signed_tx.transaction().tx_type() == SeismicTxType::Eip4844 {
844                // comapct not supported for eip4844
845                return Ok(())
846            }
847
848            let mut signed_tx_bytes = Vec::<u8>::new();
849            signed_tx.encode_2718(&mut signed_tx_bytes);
850            let recovered_tx = SeismicTransactionSigned::decode_2718(&mut &signed_tx_bytes[..])
851                .expect("Failed to decode transaction");
852            assert_eq!(recovered_tx, signed_tx);
853
854        }
855
856        #[test]
857        fn test_roundtrip_compact_encode_envelope(reth_tx in arb::<SeismicTransactionSigned>()) {
858            let mut expected_buf = Vec::<u8>::new();
859            let expected_len = reth_tx.to_compact(&mut expected_buf);
860
861            let mut actual_but  = Vec::<u8>::new();
862            let alloy_tx = SeismicTxEnvelope::from(reth_tx);
863            let actual_len = alloy_tx.to_compact(&mut actual_but);
864
865            assert_eq!(actual_but, expected_buf);
866            assert_eq!(actual_len, expected_len);
867        }
868
869        #[test]
870        fn test_roundtrip_compact_decode_envelope(reth_tx in arb::<SeismicTransactionSigned>()) {
871            if reth_tx.transaction().tx_type() == SeismicTxType::Eip4844 {
872                // comapct not supported for eip4844
873                return Ok(())
874            }
875
876            let mut buf = Vec::<u8>::new();
877            let len = reth_tx.to_compact(&mut buf);
878
879            let (actual_tx, _) = SeismicTxEnvelope::from_compact(&buf, len);
880            let expected_tx = SeismicTxEnvelope::from(reth_tx);
881
882            assert_eq!(actual_tx, expected_tx);
883        }
884    }
885}