reth_seismic_rpc/eth/
transaction.rs1use super::ext::SeismicTransaction;
4use crate::{eth::SeismicNodeCore, utils::recover_typed_data_request, SeismicEthApi};
5use alloy_consensus::{transaction::Recovered, Transaction as _};
6use alloy_primitives::{Bytes, Signature, B256};
7use alloy_rpc_types_eth::{Transaction, TransactionInfo};
8use reth_node_api::FullNodeComponents;
9use reth_rpc_eth_api::{
10 helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
11 FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat,
12};
13use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
14use reth_seismic_primitives::{SeismicReceipt, SeismicTransactionSigned};
15use reth_storage_api::{
16 BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider,
17};
18use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
19use seismic_alloy_consensus::{Decodable712, SeismicTxEnvelope, TypedDataRequest};
20use seismic_alloy_network::{Network, Seismic};
21use seismic_alloy_rpc_types::SeismicTransactionRequest;
22
23impl<N> EthTransactions for SeismicEthApi<N>
24where
25 Self: LoadTransaction<Provider: BlockReaderIdExt>,
26 N: SeismicNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
27{
28 fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
29 self.inner.signers()
30 }
31
32 async fn send_raw_transaction(&self, tx: Bytes) -> Result<B256, Self::Error> {
36 let recovered = recover_raw_transaction(&tx)?;
37 tracing::debug!(target: "reth-seismic-rpc::eth", ?recovered, "serving seismic_eth_api::send_raw_transaction");
38
39 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
40
41 let hash = self
43 .pool()
44 .add_transaction(TransactionOrigin::Local, pool_transaction)
45 .await
46 .map_err(Self::Error::from_eth_err)?;
47
48 Ok(hash)
49 }
50}
51
52impl<N> SeismicTransaction for SeismicEthApi<N>
53where
54 Self: LoadTransaction<Provider: BlockReaderIdExt>,
55 N: SeismicNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
56 <<<SeismicEthApi<N> as RpcNodeCore>::Pool as TransactionPool>::Transaction as PoolTransaction>::Pooled: Decodable712,
57{
58 async fn send_typed_data_transaction(&self, tx: TypedDataRequest) -> Result<B256, Self::Error> {
59 let recovered = recover_typed_data_request(&tx)?;
60
61 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
68
69 let hash = self
71 .pool()
72 .add_transaction(TransactionOrigin::Local, pool_transaction)
73 .await
74 .map_err(Self::Error::from_eth_err)?;
75
76 Ok(hash)
77 }
78}
79
80impl<N> LoadTransaction for SeismicEthApi<N>
81where
82 Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt,
83 N: SeismicNodeCore<Provider: TransactionsProvider, Pool: TransactionPool>,
84 Self::Pool: TransactionPool,
85{
86}
87
88impl<N> TransactionCompat<SeismicTransactionSigned> for SeismicEthApi<N>
89where
90 N: FullNodeComponents<Provider: ReceiptProvider<Receipt = SeismicReceipt>>,
91{
92 type Transaction = <Seismic as Network>::TransactionResponse;
93 type Error = EthApiError;
94
95 fn fill(
96 &self,
97 tx: Recovered<SeismicTransactionSigned>,
98 tx_info: TransactionInfo,
99 ) -> Result<Self::Transaction, Self::Error> {
100 let tx = tx.convert::<SeismicTxEnvelope>();
101
102 let TransactionInfo {
103 block_hash, block_number, index: transaction_index, base_fee, ..
104 } = tx_info;
105
106 let effective_gas_price = base_fee
107 .map(|base_fee| {
108 tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128
109 })
110 .unwrap_or_else(|| tx.max_fee_per_gas());
111
112 Ok(Transaction::<SeismicTxEnvelope> {
113 inner: tx,
114 block_hash,
115 block_number,
116 transaction_index,
117 effective_gas_price: Some(effective_gas_price),
118 })
119 }
120
121 fn build_simulate_v1_transaction(
122 &self,
123 _request: alloy_rpc_types_eth::TransactionRequest,
124 ) -> Result<SeismicTransactionSigned, Self::Error> {
125 let request = SeismicTransactionRequest {
126 inner: _request,
127 seismic_elements: None, };
130 let Ok(tx) = request.build_typed_tx() else {
131 return Err(EthApiError::TransactionConversionError)
132 };
133
134 let signature = Signature::new(Default::default(), Default::default(), false);
136 Ok(SeismicTransactionSigned::new_unhashed(tx, signature))
137 }
138
139 fn otterscan_api_truncate_input(tx: &mut Self::Transaction) {
140 let input = match tx.inner.inner_mut() {
141 SeismicTxEnvelope::Legacy(tx) => &mut tx.tx_mut().input,
142 SeismicTxEnvelope::Eip1559(tx) => &mut tx.tx_mut().input,
143 SeismicTxEnvelope::Eip2930(tx) => &mut tx.tx_mut().input,
144 SeismicTxEnvelope::Eip4844(tx) => &mut tx.tx_mut().input().clone(),
145 SeismicTxEnvelope::Eip7702(tx) => &mut tx.tx_mut().input,
146 SeismicTxEnvelope::Seismic(tx) => &mut tx.tx_mut().input,
147 };
148 *input = input.slice(..4);
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use alloy_primitives::{Bytes, FixedBytes};
155 use reth_primitives_traits::SignedTransaction;
156 use reth_rpc_eth_types::utils::recover_raw_transaction;
157 use reth_seismic_primitives::SeismicTransactionSigned;
158 use std::str::FromStr;
159
160 #[test]
161 fn test_recover_raw_tx() {
162 let raw_tx = Bytes::from_str("0x4af8d18214043083057e4083029605943ab946eec2553114040de82d2e18798a51cf1e1487038d7ea4c68000a1028e76821eb4d77fd30223ca971c49738eb5b5b71eabe93f96b348fdce788ae5a08c7da3a99bf0f90d56551d99ea02b44e69e56c3bb999b8c98772ebb32aebcbd43b33e9e65a46333dfe6636f37f3009e93bad334235aec73bd54d11410e64eb2cab4da880a0e93185920818650416b4b0cc953c48f59fd9a29af4b7e1c4b1ac4824392f9220a079b76b064a83d423997b7234c575588f60da5d3e1e0561eff9804eb04c23789a").unwrap();
163 let recovered = recover_raw_transaction::<SeismicTransactionSigned>(&raw_tx).unwrap();
164 let expected = FixedBytes::<32>::from_str(
165 "d578c4f5e787b2994749e68e44860692480ace52b219bbc0119919561cbc29ea",
166 )
167 .unwrap();
168 assert_eq!(recovered.tx_hash(), &expected);
169 }
170}