1use 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
38use alloy_consensus::TxEip4844Variant;
40use alloy_evm::FromTxWithEncoded;
41
42#[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 #[cfg_attr(feature = "serde", serde(skip))]
52 hash: OnceLock<TxHash>,
53 signature: Signature,
55 #[deref]
57 #[as_ref]
58 transaction: SeismicTypedTransaction,
59}
60
61impl SeismicTransactionSigned {
62 pub fn new(transaction: SeismicTypedTransaction, signature: Signature, hash: B256) -> Self {
64 Self { hash: hash.into(), signature, transaction }
65 }
66
67 #[inline]
69 pub fn into_transaction(self) -> SeismicTypedTransaction {
70 self.transaction
71 }
72
73 #[inline]
75 pub const fn transaction(&self) -> &SeismicTypedTransaction {
76 &self.transaction
77 }
78
79 pub fn split(self) -> (SeismicTypedTransaction, Signature) {
81 (self.transaction, self.signature)
82 }
83
84 pub fn new_unhashed(transaction: SeismicTypedTransaction, signature: Signature) -> Self {
88 Self { hash: Default::default(), signature, transaction }
89 }
90
91 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
206impl 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; 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 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 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 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 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 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
715fn 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#[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 #[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 #[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 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 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}