reth_rpc/eth/helpers/
transaction.rs

1//! Contains RPC handler implementations specific to transactions
2
3use crate::EthApi;
4use alloy_primitives::{Bytes, B256};
5use reth_provider::{BlockReader, BlockReaderIdExt, ProviderTx, TransactionsProvider};
6use reth_rpc_eth_api::{
7    helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
8    FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt,
9};
10use reth_rpc_eth_types::utils::{recover_raw_transaction, recover_typed_data_request};
11use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
12
13impl<Provider, Pool, Network, EvmConfig> EthTransactions
14    for EthApi<Provider, Pool, Network, EvmConfig>
15where
16    Self: LoadTransaction<Provider: BlockReaderIdExt>,
17    Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>,
18{
19    #[inline]
20    fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
21        self.inner.signers()
22    }
23
24    /// Decodes and recovers the transaction and submits it to the pool.
25    ///
26    /// Returns the hash of the transaction.
27    async fn send_raw_transaction(&self, tx: Bytes) -> Result<B256, Self::Error> {
28        let recovered = recover_raw_transaction(&tx)?;
29
30        // broadcast raw transaction to subscribers if there is any.
31        self.broadcast_raw_transaction(tx);
32
33        let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
34
35        // submit the transaction to the pool with a `Local` origin
36        let hash = self
37            .pool()
38            .add_transaction(TransactionOrigin::Local, pool_transaction)
39            .await
40            .map_err(Self::Error::from_eth_err)?;
41
42        Ok(hash)
43    }
44
45    /// Decodes and recovers the transaction and submits it to the pool.
46    ///
47    /// Returns the hash of the transaction.
48    async fn send_typed_data_transaction(
49        &self,
50        tx: alloy_eips::eip712::TypedDataRequest,
51    ) -> Result<B256, Self::Error> {
52        let recovered = recover_typed_data_request(&tx)?;
53
54        // broadcast raw transaction to subscribers if there is any.
55        // TODO: maybe we need to broadcast the encoded tx instead of the recovered tx
56        // when other nodes receive the raw bytes the hash they recover needs to be
57        // type
58        // self.broadcast_raw_transaction(recovered.to);
59
60        let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
61
62        // submit the transaction to the pool with a `Local` origin
63        let hash = self
64            .pool()
65            .add_transaction(TransactionOrigin::Local, pool_transaction)
66            .await
67            .map_err(Self::Error::from_eth_err)?;
68
69        Ok(hash)
70    }
71}
72
73impl<Provider, Pool, Network, EvmConfig> LoadTransaction
74    for EthApi<Provider, Pool, Network, EvmConfig>
75where
76    Self: SpawnBlocking
77        + FullEthApiTypes
78        + RpcNodeCoreExt<Provider: TransactionsProvider, Pool: TransactionPool>,
79    Provider: BlockReader,
80{
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT;
87    use alloy_primitives::{hex_literal::hex, Bytes};
88    use reth_chainspec::ChainSpecProvider;
89    use reth_evm_ethereum::EthEvmConfig;
90    use reth_network_api::noop::NoopNetwork;
91    use reth_provider::test_utils::NoopProvider;
92    use reth_rpc_eth_api::helpers::EthTransactions;
93    use reth_rpc_eth_types::{
94        EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
95    };
96    use reth_rpc_server_types::constants::{
97        DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
98    };
99    use reth_tasks::pool::BlockingTaskPool;
100    use reth_transaction_pool::{test_utils::testing_pool, TransactionPool};
101
102    #[tokio::test]
103    async fn send_raw_transaction() {
104        let noop_provider = NoopProvider::default();
105        let noop_network_provider = NoopNetwork::default();
106
107        let pool = testing_pool();
108
109        let evm_config = EthEvmConfig::new(noop_provider.chain_spec());
110        let cache = EthStateCache::spawn(noop_provider.clone(), Default::default());
111        let fee_history_cache = FeeHistoryCache::new(FeeHistoryCacheConfig::default());
112        let eth_api = EthApi::new(
113            noop_provider.clone(),
114            pool.clone(),
115            noop_network_provider,
116            cache.clone(),
117            GasPriceOracle::new(noop_provider, Default::default(), cache.clone()),
118            ETHEREUM_BLOCK_GAS_LIMIT,
119            DEFAULT_MAX_SIMULATE_BLOCKS,
120            DEFAULT_ETH_PROOF_WINDOW,
121            BlockingTaskPool::build().expect("failed to build tracing pool"),
122            fee_history_cache,
123            evm_config,
124            DEFAULT_PROOF_PERMITS,
125        );
126
127        // https://etherscan.io/tx/0xa694b71e6c128a2ed8e2e0f6770bddbe52e3bb8f10e8472f9a79ab81497a8b5d
128        let tx_1 = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3"));
129
130        let tx_1_result = eth_api.send_raw_transaction(tx_1).await.unwrap();
131        assert_eq!(
132            pool.len(),
133            1,
134            "expect 1 transactions in the pool, but pool size is {}",
135            pool.len()
136        );
137
138        // https://etherscan.io/tx/0x48816c2f32c29d152b0d86ff706f39869e6c1f01dc2fe59a3c1f9ecf39384694
139        let tx_2 = Bytes::from(hex!("02f9043c018202b7843b9aca00850c807d37a08304d21d94ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b881bc16d674ec80000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000063e2d99f00000000000000000000000000000000000000000000000000000000000000030b000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000065717fe021ea67801d1088cc80099004b05b64600000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95fd5965fd1f1a6f0d4600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000428dca9537116148616a5a3e44035af17238fe9dc080a0c6ec1e41f5c0b9511c49b171ad4e04c6bb419c74d99fe9891d74126ec6e4e879a032069a753d7a2cfa158df95421724d24c0e9501593c09905abf3699b4a4405ce"));
140
141        let tx_2_result = eth_api.send_raw_transaction(tx_2).await.unwrap();
142        assert_eq!(
143            pool.len(),
144            2,
145            "expect 2 transactions in the pool, but pool size is {}",
146            pool.len()
147        );
148
149        assert!(pool.get(&tx_1_result).is_some(), "tx1 not found in the pool");
150        assert!(pool.get(&tx_2_result).is_some(), "tx2 not found in the pool");
151    }
152}