cfx_rpc_eth_types/
tx_pool.rs

1use crate::Transaction;
2use cfx_rpc_cfx_types::TransactionStatus;
3use cfx_types::{Address, U256, U64};
4use serde::{
5    de::{self, Deserializer, Visitor},
6    Deserialize, Serialize,
7};
8use std::{collections::BTreeMap, fmt, str::FromStr};
9
10#[derive(Default, Serialize, Clone)]
11#[serde(rename_all = "camelCase")]
12pub struct AccountPendingTransactions {
13    pub pending_transactions: Vec<Transaction>,
14    pub first_tx_status: Option<TransactionStatus>,
15    pub pending_count: U64,
16}
17
18#[derive(Default, Serialize, Deserialize, Clone)]
19pub struct TxpoolStatus {
20    pub pending: U64,
21    pub queued: U64,
22}
23
24/// Transaction summary as found in the Txpool Inspection property.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
26pub struct TxpoolInspectSummary {
27    /// Recipient (None when contract creation)
28    pub to: Option<Address>,
29    /// Transferred value
30    pub value: U256,
31    /// Gas amount
32    pub gas: u64,
33    /// Gas Price
34    pub gas_price: u128,
35}
36
37impl TxpoolInspectSummary {
38    /// Extracts the [`TxpoolInspectSummary`] from a transaction.
39    pub fn from_tx(tx: Transaction) -> Self {
40        Self {
41            to: tx.to,
42            value: tx.value,
43            gas: tx.gas.as_u64(),
44            gas_price: tx
45                .max_fee_per_gas
46                .unwrap_or(tx.gas_price.unwrap_or_default())
47                .as_u128(),
48        }
49    }
50}
51
52impl From<Transaction> for TxpoolInspectSummary {
53    fn from(value: Transaction) -> Self { Self::from_tx(value) }
54}
55
56/// Visitor struct for TxpoolInspectSummary.
57struct TxpoolInspectSummaryVisitor;
58
59/// Walk through the deserializer to parse a txpool inspection summary into the
60/// `TxpoolInspectSummary` struct.
61impl Visitor<'_> for TxpoolInspectSummaryVisitor {
62    type Value = TxpoolInspectSummary;
63
64    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        formatter.write_str("to: value wei + gasLimit gas × gas_price wei")
66    }
67
68    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
69    where E: de::Error {
70        let addr_split: Vec<&str> = value.split(": ").collect();
71        if addr_split.len() != 2 {
72            return Err(de::Error::custom(
73                "invalid format for TxpoolInspectSummary: to",
74            ));
75        }
76        let value_split: Vec<&str> = addr_split[1].split(" wei + ").collect();
77        if value_split.len() != 2 {
78            return Err(de::Error::custom(
79                "invalid format for TxpoolInspectSummary: gasLimit",
80            ));
81        }
82        let gas_split: Vec<&str> = value_split[1].split(" gas × ").collect();
83        if gas_split.len() != 2 {
84            return Err(de::Error::custom(
85                "invalid format for TxpoolInspectSummary: gas",
86            ));
87        }
88        let gas_price_split: Vec<&str> = gas_split[1].split(" wei").collect();
89        if gas_price_split.len() != 2 {
90            return Err(de::Error::custom(
91                "invalid format for TxpoolInspectSummary: gas_price",
92            ));
93        }
94        let to = match addr_split[0] {
95            "" | "0x" | "contract creation" => None,
96            addr => Some(
97                Address::from_str(addr.trim_start_matches("0x"))
98                    .map_err(de::Error::custom)?,
99            ),
100        };
101        let value =
102            U256::from_dec_str(value_split[0]).map_err(de::Error::custom)?;
103        let gas = u64::from_str(gas_split[0]).map_err(de::Error::custom)?;
104        let gas_price =
105            u128::from_str(gas_price_split[0]).map_err(de::Error::custom)?;
106
107        Ok(TxpoolInspectSummary {
108            to,
109            value,
110            gas,
111            gas_price,
112        })
113    }
114
115    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
116    where E: de::Error {
117        self.visit_str(&value)
118    }
119}
120
121/// Implement the `Deserialize` trait for `TxpoolInspectSummary` struct.
122impl<'de> Deserialize<'de> for TxpoolInspectSummary {
123    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124    where D: Deserializer<'de> {
125        deserializer.deserialize_str(TxpoolInspectSummaryVisitor)
126    }
127}
128
129/// Implement the `Serialize` trait for `TxpoolInspectSummary` struct so that
130/// the format matches the one from geth.
131impl Serialize for TxpoolInspectSummary {
132    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133    where S: serde::Serializer {
134        let formatted_to = self.to.map_or_else(
135            || "contract creation".to_string(),
136            |to| format!("{to:?}"),
137        );
138        let formatted = format!(
139            "{}: {} wei + {} gas × {} wei",
140            formatted_to, self.value, self.gas, self.gas_price
141        );
142        serializer.serialize_str(&formatted)
143    }
144}
145
146#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
147pub struct TxpoolContent<T = Transaction> {
148    /// pending tx
149    pub pending: BTreeMap<Address, BTreeMap<String, T>>,
150    /// queued tx
151    pub queued: BTreeMap<Address, BTreeMap<String, T>>,
152}
153
154impl<T> Default for TxpoolContent<T> {
155    fn default() -> Self {
156        Self {
157            pending: BTreeMap::new(),
158            queued: BTreeMap::new(),
159        }
160    }
161}
162
163impl<T> TxpoolContent<T> {
164    /// Removes the transactions from the given sender
165    pub fn remove_from(&mut self, sender: &Address) -> TxpoolContentFrom<T> {
166        TxpoolContentFrom {
167            pending: self.pending.remove(sender).unwrap_or_default(),
168            queued: self.queued.remove(sender).unwrap_or_default(),
169        }
170    }
171
172    /// Returns an iterator over references to all pending transactions
173    pub fn pending_transactions(&self) -> impl Iterator<Item = &T> {
174        self.pending
175            .values()
176            .flat_map(|nonce_map| nonce_map.values())
177    }
178
179    /// Returns an iterator over references to all queued transactions
180    pub fn queued_transactions(&self) -> impl Iterator<Item = &T> {
181        self.queued
182            .values()
183            .flat_map(|nonce_map| nonce_map.values())
184    }
185
186    /// Returns an iterator over references to all pending transactions from a
187    /// specific sender
188    pub fn pending_transactions_from(
189        &self, sender: &Address,
190    ) -> impl Iterator<Item = &T> {
191        self.pending
192            .get(sender)
193            .into_iter()
194            .flat_map(|nonce_map| nonce_map.values())
195    }
196
197    /// Returns an iterator over references to all queued transactions from a
198    /// specific sender
199    pub fn queued_transactions_from(
200        &self, sender: &Address,
201    ) -> impl Iterator<Item = &T> {
202        self.queued
203            .get(sender)
204            .into_iter()
205            .flat_map(|nonce_map| nonce_map.values())
206    }
207}
208
209impl<T> TxpoolContent<T> {
210    /// Returns an iterator that consumes and yields all pending transactions
211    pub fn into_pending_transactions(self) -> impl Iterator<Item = T> {
212        self.pending
213            .into_values()
214            .flat_map(|nonce_map| nonce_map.into_values())
215    }
216
217    /// Returns an iterator that consumes and yields all queued transactions
218    pub fn into_queued_transactions(self) -> impl Iterator<Item = T> {
219        self.queued
220            .into_values()
221            .flat_map(|nonce_map| nonce_map.into_values())
222    }
223
224    /// Returns an iterator that consumes and yields all pending transactions
225    /// from a specific sender
226    pub fn into_pending_transactions_from(
227        mut self, sender: &Address,
228    ) -> impl Iterator<Item = T> {
229        self.pending
230            .remove(sender)
231            .into_iter()
232            .flat_map(|nonce_map| nonce_map.into_values())
233    }
234
235    /// Returns an iterator that consumes and yields all queued transactions
236    /// from a specific sender
237    pub fn into_queued_transactions_from(
238        mut self, sender: &Address,
239    ) -> impl Iterator<Item = T> {
240        self.queued
241            .remove(sender)
242            .into_iter()
243            .flat_map(|nonce_map| nonce_map.into_values())
244    }
245}
246
247/// Transaction Pool Content From
248///
249/// Same as [TxpoolContent] but for a specific address.
250///
251/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_contentFrom) for more details
252#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
253pub struct TxpoolContentFrom<T = Transaction> {
254    /// pending tx
255    pub pending: BTreeMap<String, T>,
256    /// queued tx
257    pub queued: BTreeMap<String, T>,
258}
259
260impl<T> Default for TxpoolContentFrom<T> {
261    fn default() -> Self {
262        Self {
263            pending: BTreeMap::new(),
264            queued: BTreeMap::new(),
265        }
266    }
267}
268
269/// Transaction Pool Inspect
270///
271/// The inspect inspection property can be queried to list a textual summary
272/// of all the transactions currently pending for inclusion in the next
273/// block(s), as well as the ones that are being scheduled for future execution
274/// only. This is a method specifically tailored to developers to quickly see
275/// the transactions in the pool and find any potential issues.
276///
277/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) for more details
278#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
279pub struct TxpoolInspect {
280    /// pending tx
281    pub pending: BTreeMap<Address, BTreeMap<String, TxpoolInspectSummary>>,
282    /// queued tx
283    pub queued: BTreeMap<Address, BTreeMap<String, TxpoolInspectSummary>>,
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289    use similar_asserts::assert_eq;
290
291    #[test]
292    fn serde_txpool_inspect() {
293        let txpool_inspect_json = r#"
294{
295  "pending": {
296    "0x0512261a7486b1e29704ac49a5eb355b6fd86872": {
297      "124930": "0x000000000000000000000000000000000000007E: 0 wei + 100187 gas × 20000000000 wei"
298    },
299    "0x201354729f8d0f8b64e9a0c353c672c6a66b3857": {
300      "252350": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65792 gas × 2000000000 wei",
301      "252351": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65792 gas × 2000000000 wei",
302      "252352": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65780 gas × 2000000000 wei",
303      "252353": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65780 gas × 2000000000 wei"
304    },
305    "0x00000000863B56a3C1f0F1be8BC4F8b7BD78F57a": {
306      "40": "contract creation: 0 wei + 612412 gas × 6000000000 wei"
307    }
308  },
309  "queued": {
310    "0x0f87ffcd71859233eb259f42b236c8e9873444e3": {
311      "7": "0x3479BE69e07E838D9738a301Bb0c89e8EA2Bef4a: 1000000000000000 wei + 21000 gas × 10000000000 wei",
312      "8": "0x73Aaf691bc33fe38f86260338EF88f9897eCaa4F: 1000000000000000 wei + 21000 gas × 10000000000 wei"
313    },
314    "0x307e8f249bcccfa5b245449256c5d7e6e079943e": {
315      "3": "0x73Aaf691bc33fe38f86260338EF88f9897eCaa4F: 10000000000000000 wei + 21000 gas × 10000000000 wei"
316    }
317  }
318}"#;
319        let deserialized: TxpoolInspect =
320            serde_json::from_str(txpool_inspect_json).unwrap();
321        assert_eq!(deserialized, expected_txpool_inspect());
322
323        let serialized = serde_json::to_string(&deserialized).unwrap();
324        let deserialized2: TxpoolInspect =
325            serde_json::from_str(&serialized).unwrap();
326        assert_eq!(deserialized2, deserialized);
327    }
328
329    #[test]
330    fn serde_txpool_status() {
331        let txpool_status_json = r#"
332{
333  "pending": "0x23",
334  "queued": "0x20"
335}"#;
336        let deserialized: TxpoolStatus =
337            serde_json::from_str(txpool_status_json).unwrap();
338        let serialized: String =
339            serde_json::to_string_pretty(&deserialized).unwrap();
340        assert_eq!(txpool_status_json.trim(), serialized);
341    }
342
343    fn expected_txpool_inspect() -> TxpoolInspect {
344        let mut pending_map = BTreeMap::new();
345        let mut pending_map_inner = BTreeMap::new();
346        pending_map_inner.insert(
347            "124930".to_string(),
348            TxpoolInspectSummary {
349                to: Some(
350                    Address::from_str(
351                        "000000000000000000000000000000000000007E",
352                    )
353                    .unwrap(),
354                ),
355                value: U256::from(0u128),
356                gas: 100187,
357                gas_price: 20000000000u128,
358            },
359        );
360        pending_map.insert(
361            Address::from_str("0512261a7486b1e29704ac49a5eb355b6fd86872")
362                .unwrap(),
363            pending_map_inner.clone(),
364        );
365        pending_map_inner.clear();
366        pending_map_inner.insert(
367            "252350".to_string(),
368            TxpoolInspectSummary {
369                to: Some(
370                    Address::from_str(
371                        "d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF",
372                    )
373                    .unwrap(),
374                ),
375                value: U256::from(0u128),
376                gas: 65792,
377                gas_price: 2000000000u128,
378            },
379        );
380        pending_map_inner.insert(
381            "252351".to_string(),
382            TxpoolInspectSummary {
383                to: Some(
384                    Address::from_str(
385                        "d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF",
386                    )
387                    .unwrap(),
388                ),
389                value: U256::from(0u128),
390                gas: 65792,
391                gas_price: 2000000000u128,
392            },
393        );
394        pending_map_inner.insert(
395            "252352".to_string(),
396            TxpoolInspectSummary {
397                to: Some(
398                    Address::from_str(
399                        "d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF",
400                    )
401                    .unwrap(),
402                ),
403                value: U256::from(0u128),
404                gas: 65780,
405                gas_price: 2000000000u128,
406            },
407        );
408        pending_map_inner.insert(
409            "252353".to_string(),
410            TxpoolInspectSummary {
411                to: Some(
412                    Address::from_str(
413                        "d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF",
414                    )
415                    .unwrap(),
416                ),
417                value: U256::from(0u128),
418                gas: 65780,
419                gas_price: 2000000000u128,
420            },
421        );
422        pending_map.insert(
423            Address::from_str("201354729f8d0f8b64e9a0c353c672c6a66b3857")
424                .unwrap(),
425            pending_map_inner.clone(),
426        );
427        pending_map_inner.clear();
428        pending_map_inner.insert(
429            "40".to_string(),
430            TxpoolInspectSummary {
431                to: None,
432                value: U256::from(0u128),
433                gas: 612412,
434                gas_price: 6000000000u128,
435            },
436        );
437        pending_map.insert(
438            Address::from_str("00000000863B56a3C1f0F1be8BC4F8b7BD78F57a")
439                .unwrap(),
440            pending_map_inner,
441        );
442        let mut queued_map = BTreeMap::new();
443        let mut queued_map_inner = BTreeMap::new();
444        queued_map_inner.insert(
445            "7".to_string(),
446            TxpoolInspectSummary {
447                to: Some(
448                    Address::from_str(
449                        "3479BE69e07E838D9738a301Bb0c89e8EA2Bef4a",
450                    )
451                    .unwrap(),
452                ),
453                value: U256::from(1000000000000000u128),
454                gas: 21000,
455                gas_price: 10000000000u128,
456            },
457        );
458        queued_map_inner.insert(
459            "8".to_string(),
460            TxpoolInspectSummary {
461                to: Some(
462                    Address::from_str(
463                        "73Aaf691bc33fe38f86260338EF88f9897eCaa4F",
464                    )
465                    .unwrap(),
466                ),
467                value: U256::from(1000000000000000u128),
468                gas: 21000,
469                gas_price: 10000000000u128,
470            },
471        );
472        queued_map.insert(
473            Address::from_str("0f87ffcd71859233eb259f42b236c8e9873444e3")
474                .unwrap(),
475            queued_map_inner.clone(),
476        );
477        queued_map_inner.clear();
478        queued_map_inner.insert(
479            "3".to_string(),
480            TxpoolInspectSummary {
481                to: Some(
482                    Address::from_str(
483                        "73Aaf691bc33fe38f86260338EF88f9897eCaa4F",
484                    )
485                    .unwrap(),
486                ),
487                value: U256::from(10000000000000000u128),
488                gas: 21000,
489                gas_price: 10000000000u128,
490            },
491        );
492        queued_map.insert(
493            Address::from_str("307e8f249bcccfa5b245449256c5d7e6e079943e")
494                .unwrap(),
495            queued_map_inner,
496        );
497
498        TxpoolInspect {
499            pending: pending_map,
500            queued: queued_map,
501        }
502    }
503}