reth_seismic_txpool/
transaction.rs1use alloy_consensus::{transaction::Recovered, BlobTransactionValidationError, Typed2718};
2use alloy_eips::{
3 eip2930::AccessList, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization,
4 Encodable2718,
5};
6use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256};
7use c_kzg::KzgSettings;
8use core::fmt::Debug;
9use reth_primitives_traits::{InMemorySize, SignedTransaction};
10use reth_seismic_primitives::SeismicTransactionSigned;
11use reth_transaction_pool::{
12 EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, PoolTransaction,
13};
14use seismic_alloy_consensus::SeismicTxEnvelope;
15use std::sync::Arc;
16
17#[derive(Debug, Clone, derive_more::Deref)]
19pub struct SeismicPooledTransaction<Cons = SeismicTransactionSigned, Pooled = SeismicTxEnvelope> {
20 #[deref]
21 inner: EthPooledTransaction<Cons>,
22 _pd: core::marker::PhantomData<Pooled>,
24}
25
26impl<Cons: SignedTransaction, Pooled> SeismicPooledTransaction<Cons, Pooled> {
27 pub fn new(transaction: Recovered<Cons>, encoded_length: usize) -> Self {
29 Self {
30 inner: EthPooledTransaction::new(transaction, encoded_length),
31 _pd: core::marker::PhantomData,
32 }
33 }
34}
35
36impl<Cons, Pooled> PoolTransaction for SeismicPooledTransaction<Cons, Pooled>
37where
38 Cons: SignedTransaction + From<Pooled>,
39 Pooled: SignedTransaction + TryFrom<Cons, Error: core::error::Error>,
40{
41 type TryFromConsensusError = <Pooled as TryFrom<Cons>>::Error;
42 type Consensus = Cons;
43 type Pooled = Pooled;
44
45 fn hash(&self) -> &TxHash {
46 self.inner.transaction.tx_hash()
47 }
48
49 fn sender(&self) -> Address {
50 self.inner.transaction.signer()
51 }
52
53 fn sender_ref(&self) -> &Address {
54 self.inner.transaction.signer_ref()
55 }
56
57 fn cost(&self) -> &U256 {
58 &self.inner.cost
59 }
60
61 fn encoded_length(&self) -> usize {
62 self.inner.encoded_length
63 }
64
65 fn clone_into_consensus(&self) -> Recovered<Self::Consensus> {
66 self.inner.transaction().clone()
67 }
68
69 fn into_consensus(self) -> Recovered<Self::Consensus> {
70 self.inner.transaction
71 }
72
73 fn from_pooled(tx: Recovered<Self::Pooled>) -> Self {
74 let encoded_len = tx.encode_2718_len();
75 Self::new(tx.convert(), encoded_len)
76 }
77}
78
79impl<Cons: Typed2718, Pooled> Typed2718 for SeismicPooledTransaction<Cons, Pooled> {
80 fn ty(&self) -> u8 {
81 self.inner.ty()
82 }
83}
84
85impl<Cons: InMemorySize, Pooled> InMemorySize for SeismicPooledTransaction<Cons, Pooled> {
86 fn size(&self) -> usize {
87 self.inner.size()
88 }
89}
90
91impl<Cons, Pooled> alloy_consensus::Transaction for SeismicPooledTransaction<Cons, Pooled>
92where
93 Cons: alloy_consensus::Transaction + SignedTransaction, Pooled: Debug + Send + Sync + 'static, {
97 fn chain_id(&self) -> Option<u64> {
98 self.inner.chain_id()
99 }
100 fn nonce(&self) -> u64 {
101 self.inner.nonce()
102 }
103 fn gas_limit(&self) -> u64 {
104 self.inner.gas_limit()
105 }
106 fn gas_price(&self) -> Option<u128> {
107 self.inner.gas_price()
108 }
109 fn max_fee_per_gas(&self) -> u128 {
110 self.inner.max_fee_per_gas()
111 }
112 fn max_priority_fee_per_gas(&self) -> Option<u128> {
113 self.inner.max_priority_fee_per_gas()
114 }
115 fn max_fee_per_blob_gas(&self) -> Option<u128> {
116 self.inner.max_fee_per_blob_gas()
117 }
118 fn value(&self) -> U256 {
119 self.inner.value()
120 }
121 fn input(&self) -> &Bytes {
122 self.inner.input()
123 }
124 fn access_list(&self) -> Option<&AccessList> {
125 self.inner.access_list()
126 }
127 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
128 self.inner.blob_versioned_hashes()
129 }
130 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
131 self.inner.authorization_list()
132 }
133 fn priority_fee_or_price(&self) -> u128 {
134 self.inner.priority_fee_or_price()
135 }
136 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
137 self.inner.effective_gas_price(base_fee)
138 }
139 fn is_dynamic_fee(&self) -> bool {
140 self.inner.is_dynamic_fee()
141 }
142 fn kind(&self) -> TxKind {
143 self.inner.kind()
144 }
145 fn is_create(&self) -> bool {
146 self.inner.is_create()
147 }
148}
149
150impl<Cons, Pooled> EthPoolTransaction for SeismicPooledTransaction<Cons, Pooled>
151where
152 Cons: SignedTransaction + From<Pooled>,
153 Pooled: SignedTransaction + TryFrom<Cons>,
154 <Pooled as TryFrom<Cons>>::Error: core::error::Error,
155{
156 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
157 EthBlobTransactionSidecar::None
158 }
159
160 fn try_into_pooled_eip4844(
161 self,
162 _sidecar: Arc<BlobTransactionSidecarVariant>,
163 ) -> Option<Recovered<Self::Pooled>> {
164 None
165 }
166
167 fn try_from_eip4844(
168 _tx: Recovered<Self::Consensus>,
169 _sidecar: BlobTransactionSidecarVariant,
170 ) -> Option<Self> {
171 None
172 }
173
174 fn validate_blob(
175 &self,
176 _sidecar: &BlobTransactionSidecarVariant,
177 _settings: &KzgSettings,
178 ) -> Result<(), BlobTransactionValidationError> {
179 Err(BlobTransactionValidationError::NotBlobTransaction(self.ty()))
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use crate::SeismicPooledTransaction;
186 use alloy_consensus::transaction::Recovered;
187 use alloy_eips::eip2718::Encodable2718;
188 use reth_primitives_traits::transaction::error::InvalidTransactionError;
189 use reth_provider::test_utils::MockEthProvider;
190 use reth_seismic_chainspec::SEISMIC_MAINNET;
191 use reth_seismic_primitives::test_utils::get_signed_seismic_tx;
192 use reth_transaction_pool::{
193 blobstore::InMemoryBlobStore, error::InvalidPoolTransactionError,
194 validate::EthTransactionValidatorBuilder, TransactionOrigin, TransactionValidationOutcome,
195 };
196
197 #[tokio::test]
198 async fn validate_seismic_transaction() {
199 let client = MockEthProvider::default().with_chain_spec(SEISMIC_MAINNET.clone());
201 let validator = EthTransactionValidatorBuilder::new(client)
202 .no_shanghai()
203 .no_cancun()
204 .build(InMemoryBlobStore::default());
205
206 let origin = TransactionOrigin::External;
208 let signer = Default::default();
209 let signed_seismic_tx = get_signed_seismic_tx();
210 let signed_recovered = Recovered::new_unchecked(signed_seismic_tx, signer);
211 let len = signed_recovered.encode_2718_len();
212 let pooled_tx: SeismicPooledTransaction =
213 SeismicPooledTransaction::new(signed_recovered, len);
214
215 let outcome = validator.validate_one(origin, pooled_tx);
216
217 match outcome {
218 TransactionValidationOutcome::Invalid(
219 _,
220 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(
221 _,
222 )),
223 ) => {
224 }
228 _ => panic!("Did not get expected outcome, got: {:?}", outcome),
229 }
230 }
231}