1use core::fmt;
2use std::collections::BTreeMap;
3
4use alloy_consensus::Transaction;
5use alloy_primitives::Address;
6use alloy_rpc_types_txpool::{
7 TxpoolContent, TxpoolContentFrom, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus,
8};
9use async_trait::async_trait;
10use jsonrpsee::core::RpcResult;
11use reth_primitives_traits::NodePrimitives;
12use reth_rpc_api::TxPoolApiServer;
13use reth_rpc_convert::RpcConvert;
14use reth_rpc_eth_api::RpcTransaction;
15use reth_transaction_pool::{
16 AllPoolTransactions, PoolConsensusTx, PoolTransaction, TransactionPool,
17};
18use tracing::trace;
19
20#[derive(Clone)]
24pub struct TxPoolApi<Pool, Eth> {
25 pool: Pool,
27 converter: Eth,
28}
29
30impl<Pool, Eth> TxPoolApi<Pool, Eth> {
31 pub const fn new(pool: Pool, converter: Eth) -> Self {
33 Self { pool, converter }
34 }
35}
36
37impl<Pool, Eth> TxPoolApi<Pool, Eth>
38where
39 Pool: TransactionPool<Transaction: PoolTransaction<Consensus: Transaction>> + 'static,
40 Eth: RpcConvert<Primitives: NodePrimitives<SignedTx = PoolConsensusTx<Pool>>>,
41{
42 fn content(&self) -> Result<TxpoolContent<RpcTransaction<Eth::Network>>, Eth::Error> {
43 let AllPoolTransactions { pending, queued } = self.pool.all_transactions();
44
45 let mut content = TxpoolContent::default();
46 for tx in pending {
47 let sender = tx.transaction.sender();
48 self.insert_by_nonce(&tx.transaction, content.pending.entry(sender).or_default())?;
49 }
50 for tx in queued {
51 let sender = tx.transaction.sender();
52 self.insert_by_nonce(&tx.transaction, content.queued.entry(sender).or_default())?;
53 }
54
55 Ok(content)
56 }
57
58 fn content_from(
59 &self,
60 from: Address,
61 ) -> Result<TxpoolContentFrom<RpcTransaction<Eth::Network>>, Eth::Error> {
62 let mut content = TxpoolContentFrom::default();
63 let AllPoolTransactions { pending, queued } = self.pool.all_transactions_by_sender(from);
66 for tx in pending {
67 self.insert_by_nonce(&tx.transaction, &mut content.pending)?;
68 }
69 for tx in queued {
70 self.insert_by_nonce(&tx.transaction, &mut content.queued)?;
71 }
72
73 Ok(content)
74 }
75
76 #[inline]
78 fn insert_by_nonce(
79 &self,
80 tx: &Pool::Transaction,
81 txs: &mut BTreeMap<String, RpcTransaction<Eth::Network>>,
82 ) -> Result<(), Eth::Error> {
83 txs.insert(tx.nonce().to_string(), self.converter.fill_pending(tx.clone_into_consensus())?);
84
85 Ok(())
86 }
87}
88
89#[async_trait]
90impl<Pool, Eth> TxPoolApiServer<RpcTransaction<Eth::Network>> for TxPoolApi<Pool, Eth>
91where
92 Pool: TransactionPool<Transaction: PoolTransaction<Consensus: Transaction>> + 'static,
93 Eth: RpcConvert<Primitives: NodePrimitives<SignedTx = PoolConsensusTx<Pool>>> + 'static,
94{
95 async fn txpool_status(&self) -> RpcResult<TxpoolStatus> {
101 trace!(target: "rpc::eth", "Serving txpool_status");
102 let (pending, queued) = self.pool.pending_and_queued_txn_count();
103 Ok(TxpoolStatus { pending: pending as u64, queued: queued as u64 })
104 }
105
106 async fn txpool_inspect(&self) -> RpcResult<TxpoolInspect> {
113 trace!(target: "rpc::eth", "Serving txpool_inspect");
114
115 #[inline]
116 fn insert<T: PoolTransaction<Consensus: Transaction>>(
117 tx: &T,
118 inspect: &mut BTreeMap<Address, BTreeMap<String, TxpoolInspectSummary>>,
119 ) {
120 let entry = inspect.entry(tx.sender()).or_default();
121 let tx = tx.clone_into_consensus();
122 entry.insert(tx.nonce().to_string(), tx.into_inner().into());
123 }
124
125 let AllPoolTransactions { pending, queued } = self.pool.all_transactions();
126
127 Ok(TxpoolInspect {
128 pending: pending.iter().fold(Default::default(), |mut acc, tx| {
129 insert(&tx.transaction, &mut acc);
130 acc
131 }),
132 queued: queued.iter().fold(Default::default(), |mut acc, tx| {
133 insert(&tx.transaction, &mut acc);
134 acc
135 }),
136 })
137 }
138
139 async fn txpool_content_from(
145 &self,
146 from: Address,
147 ) -> RpcResult<TxpoolContentFrom<RpcTransaction<Eth::Network>>> {
148 trace!(target: "rpc::eth", ?from, "Serving txpool_contentFrom");
149 Ok(self.content_from(from).map_err(Into::into)?)
150 }
151
152 async fn txpool_content(&self) -> RpcResult<TxpoolContent<RpcTransaction<Eth::Network>>> {
158 trace!(target: "rpc::eth", "Serving txpool_content");
159 Ok(self.content().map_err(Into::into)?)
160 }
161}
162
163impl<Pool, Eth> fmt::Debug for TxPoolApi<Pool, Eth> {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_struct("TxpoolApi").finish_non_exhaustive()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::eth::helpers::types::EthRpcConverter;
173 use reth_chainspec::MAINNET;
174 use reth_rpc_eth_types::receipt::EthReceiptConverter;
175 use reth_transaction_pool::{
176 test_utils::{testing_pool, MockTransaction},
177 TransactionOrigin,
178 };
179
180 #[tokio::test]
181 async fn content_from_matches_content() {
182 let senders = [Address::with_last_byte(1), Address::with_last_byte(2)];
183
184 let pool = testing_pool();
185 for sender in senders {
186 for nonce in [0, 1, 9] {
188 let tx = MockTransaction::legacy()
189 .with_sender(sender)
190 .with_nonce(nonce)
191 .with_gas_price(100);
192 pool.add_transaction(TransactionOrigin::External, tx).await.unwrap();
193 }
194 }
195
196 let api =
197 TxPoolApi::new(pool, EthRpcConverter::new(EthReceiptConverter::new(MAINNET.clone())));
198 let mut content = api.content().unwrap();
199 assert_eq!((content.pending.len(), content.queued.len()), (senders.len(), senders.len()));
201
202 for sender in senders.into_iter().chain([Address::with_last_byte(3)]) {
204 assert_eq!(api.content_from(sender).unwrap(), content.remove_from(&sender));
205 }
206 }
207}