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#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
26pub struct TxpoolInspectSummary {
27 pub to: Option<Address>,
29 pub value: U256,
31 pub gas: u64,
33 pub gas_price: u128,
35}
36
37impl TxpoolInspectSummary {
38 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
56struct TxpoolInspectSummaryVisitor;
58
59impl 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
121impl<'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
129impl 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 pub pending: BTreeMap<Address, BTreeMap<String, T>>,
150 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 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 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 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 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 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 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 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 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 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
253pub struct TxpoolContentFrom<T = Transaction> {
254 pub pending: BTreeMap<String, T>,
256 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#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
279pub struct TxpoolInspect {
280 pub pending: BTreeMap<Address, BTreeMap<String, TxpoolInspectSummary>>,
282 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}