reth_seismic_rpc/eth/
transaction.rs

1//! Loads and formats Seismic transaction RPC response.
2
3use 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    /// Decodes and recovers the transaction and submits it to the pool.
33    ///
34    /// Returns the hash of the transaction.
35    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        // submit the transaction to the pool with a `Local` origin
42        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        // broadcast raw transaction to subscribers if there is any.
62        // TODO: maybe we need to broadcast the encoded tx instead of the recovered tx
63        // when other nodes receive the raw bytes the hash they recover needs to be
64        // type
65        // self.broadcast_raw_transaction(recovered.to);
66
67        let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
68
69        // submit the transaction to the pool with a `Local` origin
70        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, /* Assumed that the transaction has already been decrypted in
128                                     * the EthApiExt */
129        };
130        let Ok(tx) = request.build_typed_tx() else {
131            return Err(EthApiError::TransactionConversionError)
132        };
133
134        // Create an empty signature for the transaction.
135        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}