Skip to main content

evm_sdk/
mempool.rs

1/// This module provides memory pool-related functionalities.
2use crate::Evm;
3use crate::types::EvmError;
4use ethers::providers::Middleware;
5use ethers::types::Bytes;
6use ethers::types::{Address, U256};
7use ethers::types::{Filter, Transaction, TxHash};
8use sha3::{Digest, Keccak256};
9use std::collections::{HashMap, HashSet};
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use tokio::time::{Duration, sleep};
13
14/// Represents a transaction in the mempool
15#[derive(Debug, Clone)]
16pub struct MempoolTransaction {
17    pub hash: TxHash,
18    pub from: Address,
19    pub to: Option<Address>,
20    pub value: U256,
21    pub transaction_type: Option<u64>,
22    pub gas_price: Option<U256>,                // legacy trade
23    pub max_fee_per_gas: Option<U256>,          // EIP-1559
24    pub max_priority_fee_per_gas: Option<U256>, // EIP-1559
25    pub gas: U256,
26    pub input: Bytes,
27    pub nonce: U256,
28    pub transaction: Transaction,
29    pub first_seen: u64,
30    pub last_seen: u64,
31    pub is_mev: bool,
32    pub bundle_hash: Option<TxHash>,
33    pub frontrunning_protection: bool,
34}
35
36/// Configuration for mempool monitoring
37#[derive(Debug, Clone)]
38pub struct MempoolConfig {
39    pub poll_interval: Duration,
40    pub max_transactions: usize,
41    pub track_pending: bool,
42    pub enable_mev_detection: bool,
43    pub max_reorg_depth: u64,
44    pub simulate_transactions: bool,
45    pub track_bundles: bool,
46}
47
48impl Default for MempoolConfig {
49    fn default() -> Self {
50        Self {
51            poll_interval: Duration::from_secs(2),
52            max_transactions: 10000,
53            track_pending: true,
54            enable_mev_detection: false,
55            max_reorg_depth: 5,
56            simulate_transactions: false,
57            track_bundles: false,
58        }
59    }
60}
61
62/// Listener for monitoring mempool transactions
63#[derive(Clone)]
64pub struct MempoolListener {
65    evm: Arc<Evm>,
66    config: MempoolConfig,
67    state: Arc<RwLock<MempoolState>>,
68}
69
70/// Internal state of the mempool
71#[derive(Debug)]
72struct MempoolState {
73    transactions: HashMap<TxHash, MempoolTransaction>,
74    pending_hashes: HashSet<TxHash>,
75    last_block_number: u64,
76    is_running: bool,
77    // Transaction package tracking
78    // bundle_hash -> [tx_hashes]
79    transaction_bundles: HashMap<TxHash, Vec<TxHash>>,
80}
81
82impl MempoolListener {
83    /// Creates a new MempoolListener with default configuration
84    pub fn new(evm: Arc<Evm>) -> Self {
85        Self::with_config(evm, MempoolConfig::default())
86    }
87
88    /// Creates a new MempoolListener with custom configuration
89    pub fn with_config(evm: Arc<Evm>, config: MempoolConfig) -> Self {
90        Self {
91            evm,
92            config,
93            state: Arc::new(RwLock::new(MempoolState {
94                transactions: HashMap::new(),
95                pending_hashes: HashSet::new(),
96                last_block_number: 0,
97                is_running: false,
98                transaction_bundles: HashMap::new(),
99            })),
100        }
101    }
102
103    /// Starts the mempool listener
104    ///
105    /// # Example
106    /// ```
107    /// let evm = Arc::new(Evm::new());
108    /// let listener = MempoolListener::new(evm);
109    /// listener.start().await?;
110    /// ```
111    pub async fn start(&self) -> Result<(), EvmError> {
112        let mut state = self.state.write().await;
113        if state.is_running {
114            return Err(EvmError::MempoolError(
115                "Mempool listener is already running".to_string(),
116            ));
117        }
118        state.is_running = true;
119        drop(state);
120
121        let listener = self.clone();
122        tokio::spawn(async move {
123            listener.run().await;
124        });
125
126        Ok(())
127    }
128
129    /// Stops the mempool listener
130    pub async fn stop(&self) {
131        let mut state = self.state.write().await;
132        state.is_running = false;
133    }
134
135    /// Main run loop
136    async fn run(&self) {
137        while self.is_running().await {
138            if let Err(e) = self.poll_mempool().await {
139                eprintln!("Error polling mempool: {}", e);
140            }
141            sleep(self.config.poll_interval).await;
142        }
143    }
144
145    /// Checks if the listener is running
146    async fn is_running(&self) -> bool {
147        self.state.read().await.is_running
148    }
149
150    /// Polls the mempool for pending transactions
151    async fn poll_mempool(&self) -> Result<(), EvmError> {
152        let current_block = self.evm.get_block_number().await?;
153        {
154            let mut state = self.state.write().await;
155            state.last_block_number = current_block;
156        }
157        let pending_txs = self.get_pending_transactions().await?;
158        self.update_mempool_state(pending_txs, current_block).await;
159        self.clean_confirmed_transactions().await?;
160        Ok(())
161    }
162
163    /// Retrieves pending transactions from the mempool using the standard JSON-RPC method.
164    /// This method queries the pending block to get transactions that are currently
165    ///
166    /// # Features
167    /// Timeout Control: 30-second timeout for pending block retrieval
168    /// Parallel Processing: Uses `tokio::spawn` to fetch transaction details concurrently
169    /// Retry Mechanism: 3 retry attempts with exponential backoff for failed requests
170    /// Error Resilience: Continues processing even if individual transactions fail
171    ///
172    /// # Workflow
173    /// 1. Fetches the pending block with timeout protection
174    /// 2. Spawns parallel tasks for each transaction hash
175    /// 3. Each task retries up to 3 times to get transaction details
176    /// 4. Filters out confirmed transactions and handles errors gracefully
177    /// 5. Collects all valid pending transactions
178    ///
179    /// # Returns
180    /// - `Ok(Vec<Transaction>)`: Vector of valid pending transactions
181    /// - `Err(EvmError)`: If the initial block fetch fails or times out
182    ///
183    /// # Example
184    /// ```rust
185    /// let pending_txs = listener.get_pending_transactions().await?;
186    /// for tx in pending_txs {
187    ///     println!("Pending TX: {:?} from {:?}", tx.hash, tx.from);
188    /// }
189    /// ```
190    ///
191    async fn get_pending_transactions(&self) -> Result<Vec<Transaction>, EvmError> {
192        // timeout control
193        let block = tokio::time::timeout(
194            Duration::from_secs(30),
195            self.evm
196                .client
197                .provider
198                .get_block(ethers::types::BlockId::Number(
199                    ethers::types::BlockNumber::Pending,
200                )),
201        )
202        .await
203        .map_err(|_| EvmError::RpcError("Timeout getting pending block".to_string()))?
204        .map_err(|e| EvmError::RpcError(format!("Failed to get pending block: {}", e)))?;
205        let mut pending_txs = Vec::new();
206        if let Some(block) = block {
207            // parallel tasks
208            let mut handles = Vec::new();
209            for tx_hash in block.transactions {
210                let provider = self.evm.client.provider.clone();
211                let handle = tokio::spawn(async move {
212                    // try again
213                    for attempt in 0..3 {
214                        match provider.get_transaction(tx_hash).await {
215                            Ok(Some(tx)) if tx.block_number.is_none() => return Some(tx),
216                            Ok(Some(_)) => return None, // confirmed transactions
217                            Ok(None) => return None,    // transaction does not exist
218                            Err(_) if attempt < 2 => {
219                                tokio::time::sleep(Duration::from_millis(100 * (attempt + 1)))
220                                    .await;
221                                continue;
222                            }
223                            Err(e) => {
224                                eprintln!(
225                                    "Failed to get transaction {} after {} attempts: {}",
226                                    tx_hash,
227                                    attempt + 1,
228                                    e
229                                );
230                                return None;
231                            }
232                        }
233                    }
234                    None
235                });
236                handles.push(handle);
237            }
238            // waiting for all tasks to complete
239            for handle in handles {
240                match handle.await {
241                    Ok(Some(tx)) => pending_txs.push(tx),
242                    Ok(None) => {} // Skip invalid transaction
243                    Err(e) => eprintln!("Task failed: {}", e),
244                }
245            }
246        }
247        Ok(pending_txs)
248    }
249
250    /// Updates the mempool state with new transactions
251    async fn update_mempool_state(&self, transactions: Vec<Transaction>, current_block: u64) {
252        let mut state = self.state.write().await;
253        // collect all new transactions for check package.
254        let new_transactions: Vec<Transaction> = transactions
255            .into_iter()
256            .filter(|tx| !state.transactions.contains_key(&tx.hash))
257            .collect();
258        // detect transaction packages
259        let bundles = if self.config.track_bundles {
260            Self::detect_transaction_bundles(&new_transactions)
261        } else {
262            HashMap::new()
263        };
264        // update transaction packages status
265        for (bundle_hash, tx_hashes) in bundles {
266            state
267                .transaction_bundles
268                .insert(bundle_hash, tx_hashes.clone());
269        }
270        // handle a single transaction
271        for tx in new_transactions {
272            if state.transactions.len() < self.config.max_transactions {
273                let is_mev = self.config.enable_mev_detection && Self::detect_mev_transaction(&tx);
274                let frontrunning_protection = Self::has_frontrunning_protection(&tx);
275                // Find the package to which the transaction belongs
276                let bundle_hash =
277                    Self::find_bundle_for_transaction(&tx, &state.transaction_bundles);
278                let mempool_tx = MempoolTransaction {
279                    hash: tx.hash,
280                    from: tx.from,
281                    to: tx.to,
282                    value: tx.value,
283                    transaction_type: tx.transaction_type.map(|v| v.as_u64()),
284                    gas_price: tx.gas_price,
285                    max_fee_per_gas: tx.max_fee_per_gas,
286                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
287                    gas: tx.gas,
288                    input: tx.input.clone(),
289                    nonce: tx.nonce,
290                    transaction: tx.clone(),
291                    first_seen: current_block,
292                    last_seen: current_block,
293                    is_mev,
294                    bundle_hash,
295                    frontrunning_protection,
296                };
297                state.transactions.insert(tx.hash, mempool_tx);
298                state.pending_hashes.insert(tx.hash);
299            }
300        }
301    }
302
303    /// Remove confirmed transactions and transaction packages from the memory pool.
304    async fn clean_confirmed_transactions(&self) -> Result<(), EvmError> {
305        let current_block = self.evm.get_block_number().await?;
306        let mut state = self.state.write().await;
307        let mut to_remove = Vec::new();
308        for (tx_hash, mempool_tx) in state.transactions.iter() {
309            if current_block.saturating_sub(mempool_tx.last_seen) > self.config.max_reorg_depth {
310                if let Ok(Some(receipt)) = self.evm.get_transaction_receipt(*tx_hash).await {
311                    if receipt.block_number.is_some() {
312                        to_remove.push(*tx_hash);
313                    }
314                }
315            }
316        }
317        for tx_hash in to_remove {
318            state.transactions.remove(&tx_hash);
319            state.pending_hashes.remove(&tx_hash);
320            state.transaction_bundles.retain(|_, tx_hashes| {
321                tx_hashes.retain(|h| h != &tx_hash);
322                !tx_hashes.is_empty()
323            });
324        }
325        Ok(())
326    }
327
328    /// Returns all pending transactions in the mempool
329    ///
330    /// # Example
331    /// ```
332    /// let transactions = listener.get_pending_transactions_list().await;
333    /// for tx in transactions {
334    ///     println!("Transaction hash: {:?}", tx.hash);
335    /// }
336    /// ```
337    pub async fn get_pending_transactions_list(&self) -> Vec<MempoolTransaction> {
338        let state = self.state.read().await;
339        state.transactions.values().cloned().collect()
340    }
341
342    /// Returns pending transactions sent by a specific address
343    ///
344    /// # Example
345    /// ```
346    /// let address: Address = "0x...".parse().unwrap();
347    /// let sender_txs = listener.get_transactions_by_sender(address).await;
348    /// ```
349    pub async fn get_transactions_by_sender(&self, address: Address) -> Vec<MempoolTransaction> {
350        let state = self.state.read().await;
351        state
352            .transactions
353            .values()
354            .filter(|tx| tx.from == address)
355            .cloned()
356            .collect()
357    }
358
359    /// Returns pending transactions sent to a specific address
360    ///
361    /// # Example
362    /// ```
363    /// let address: Address = "0x...".parse().unwrap();
364    /// let receiver_txs = listener.get_transactions_by_receiver(address).await;
365    /// ```
366    pub async fn get_transactions_by_receiver(&self, address: Address) -> Vec<MempoolTransaction> {
367        let state = self.state.read().await;
368        state
369            .transactions
370            .values()
371            .filter(|tx| tx.to == Some(address))
372            .cloned()
373            .collect()
374    }
375
376    /// Returns transactions with gas price above the specified threshold
377    ///
378    /// # Example
379    /// ```
380    /// let threshold = U256::from(100_000_000_000u64); // 100 Gwei
381    /// let high_gas_txs = listener.get_high_gas_transactions(threshold).await;
382    /// ```
383    pub async fn get_high_gas_transactions(&self, threshold: U256) -> Vec<MempoolTransaction> {
384        let state = self.state.read().await;
385        state
386            .transactions
387            .values()
388            .filter(|tx| tx.gas_price >= Some(threshold))
389            .cloned()
390            .collect()
391    }
392
393    /// Returns transactions with value above the specified threshold
394    ///
395    /// # Example
396    /// ```
397    /// let threshold = U256::from(1_000_000_000_000_000_000u64); // 1 ETH
398    /// let large_txs = listener.get_large_value_transactions(threshold).await;
399    /// ```
400    pub async fn get_large_value_transactions(&self, threshold: U256) -> Vec<MempoolTransaction> {
401        let state = self.state.read().await;
402        state
403            .transactions
404            .values()
405            .filter(|tx| tx.value >= threshold)
406            .cloned()
407            .collect()
408    }
409
410    /// Returns statistics about the current mempool state
411    ///
412    /// # Example
413    /// ```
414    /// let stats = listener.get_stats().await;
415    /// println!("Total transactions: {}", stats.total_transactions);
416    /// println!("Total value: {}", stats.total_value);
417    /// ```
418    pub async fn get_stats(&self) -> MempoolStats {
419        let state = self.state.read().await;
420        let total_txs = state.transactions.len();
421        let total_value = state
422            .transactions
423            .values()
424            .fold(U256::zero(), |acc, tx| acc + tx.value);
425        let total_gas_limit = state
426            .transactions
427            .values()
428            .fold(U256::zero(), |acc, tx| acc + tx.gas);
429        // Calculate average gas price (supports EIP-1559)
430        let (gas_sum, count) =
431            state
432                .transactions
433                .values()
434                .fold((U256::zero(), 0), |(sum, count), tx| {
435                    let effective_gas_price = tx
436                        .max_fee_per_gas
437                        .unwrap_or_else(|| tx.gas_price.unwrap_or_default());
438                    (sum + effective_gas_price, count + 1)
439                });
440        let avg_gas_price = if count > 0 {
441            gas_sum / U256::from(count)
442        } else {
443            U256::zero()
444        };
445        MempoolStats {
446            total_transactions: total_txs,
447            total_value,
448            total_gas: total_gas_limit,
449            average_gas_price: avg_gas_price,
450            last_block_number: state.last_block_number,
451            // eip1559 transactions, eth2.0 support
452            eip1559_transactions: state
453                .transactions
454                .values()
455                .filter(|tx| tx.transaction_type == Some(2))
456                .count(),
457            mev_transactions: state.transactions.values().filter(|tx| tx.is_mev).count(),
458            protected_transactions: state
459                .transactions
460                .values()
461                .filter(|tx| tx.frontrunning_protection)
462                .count(),
463        }
464    }
465
466    /// Checks if a specific transaction is in the mempool
467    ///
468    /// # Example
469    /// ```
470    /// let tx_hash: TxHash = "0x...".parse().unwrap();
471    /// if listener.contains_transaction(tx_hash).await {
472    ///     println!("Transaction is pending");
473    /// }
474    /// ```
475    pub async fn contains_transaction(&self, tx_hash: TxHash) -> bool {
476        let state = self.state.read().await;
477        state.transactions.contains_key(&tx_hash)
478    }
479
480    /// Returns details for a specific transaction
481    ///
482    /// # Example
483    /// ```
484    /// let tx_hash: TxHash = "0x...".parse().unwrap();
485    /// if let Some(tx) = listener.get_transaction_details(tx_hash).await {
486    ///     println!("From: {:?}, To: {:?}, Value: {}", tx.from, tx.to, tx.value);
487    /// }
488    /// ```
489    pub async fn get_transaction_details(&self, tx_hash: TxHash) -> Option<MempoolTransaction> {
490        let state = self.state.read().await;
491        state.transactions.get(&tx_hash).cloned()
492    }
493
494    fn detect_mev_transaction(tx: &Transaction) -> bool {
495        let input_str = hex::encode(&tx.input);
496        input_str.contains("0x6a761202")
497            || tx.value.is_zero() && !tx.input.is_empty()
498            || tx.gas_price.unwrap_or_default() > U256::from(100_000_000_000u64)
499    }
500
501    fn has_frontrunning_protection(tx: &Transaction) -> bool {
502        tx.max_priority_fee_per_gas.is_some() && // 使用 EIP-1559
503        tx.input.len() > 1000
504    }
505
506    /// get eip1559 transactions
507    pub async fn get_eip1559_transactions(&self) -> Vec<MempoolTransaction> {
508        let state = self.state.read().await;
509        state
510            .transactions
511            .values()
512            .filter(|tx| tx.transaction_type == Some(2))
513            .cloned()
514            .collect()
515    }
516
517    /// get mev transactions
518    pub async fn get_mev_transactions(&self) -> Vec<MempoolTransaction> {
519        let state = self.state.read().await;
520        state
521            .transactions
522            .values()
523            .filter(|tx| tx.is_mev)
524            .cloned()
525            .collect()
526    }
527
528    fn detect_transaction_bundles(transactions: &[Transaction]) -> HashMap<TxHash, Vec<TxHash>> {
529        let mut bundles = HashMap::new();
530        let mut by_sender: HashMap<Address, Vec<&Transaction>> = HashMap::new();
531        for tx in transactions {
532            by_sender.entry(tx.from).or_default().push(tx);
533        }
534        for (sender, txs) in by_sender {
535            let mut sorted_txs = txs;
536            sorted_txs.sort_by_key(|tx| tx.nonce);
537            let mut current_bundle = Vec::new();
538            for window in sorted_txs.windows(2) {
539                if let [prev, current] = window {
540                    if current.nonce == prev.nonce + U256::one() {
541                        if current_bundle.is_empty() {
542                            current_bundle.push(prev.hash);
543                        }
544                        current_bundle.push(current.hash);
545                    } else if !current_bundle.is_empty() {
546                        if current_bundle.len() > 1 {
547                            let bundle_hash = Self::calculate_bundle_hash(&current_bundle);
548                            bundles.insert(bundle_hash, current_bundle.clone());
549                        }
550                        current_bundle.clear();
551                    }
552                }
553            }
554            if current_bundle.len() > 1 {
555                let bundle_hash = Self::calculate_bundle_hash(&current_bundle);
556                bundles.insert(bundle_hash, current_bundle);
557            }
558        }
559        let mut by_contract: HashMap<Address, Vec<&Transaction>> = HashMap::new();
560        for tx in transactions {
561            if let Some(to) = tx.to {
562                by_contract.entry(to).or_default().push(tx);
563            }
564        }
565        for (contract, txs) in by_contract {
566            if txs.len() > 1 {
567                let mut by_selector: HashMap<Vec<u8>, Vec<TxHash>> = HashMap::new();
568                for tx in txs {
569                    if tx.input.len() >= 4 {
570                        let selector = tx.input.0[..4].to_vec();
571                        by_selector.entry(selector).or_default().push(tx.hash);
572                    }
573                }
574                for (_, tx_hashes) in by_selector {
575                    if tx_hashes.len() > 1 {
576                        let bundle_hash = Self::calculate_bundle_hash(&tx_hashes);
577                        bundles.insert(bundle_hash, tx_hashes);
578                    }
579                }
580            }
581        }
582        bundles
583    }
584
585    fn calculate_bundle_hash(tx_hashes: &[TxHash]) -> TxHash {
586        let mut hasher = Keccak256::new();
587        for tx_hash in tx_hashes {
588            hasher.update(tx_hash.as_bytes());
589        }
590        let result = hasher.finalize();
591        TxHash::from_slice(&result[..32])
592    }
593
594    fn find_bundle_for_transaction(
595        tx: &Transaction,
596        bundles: &HashMap<TxHash, Vec<TxHash>>,
597    ) -> Option<TxHash> {
598        for (bundle_hash, tx_hashes) in bundles {
599            if tx_hashes.contains(&tx.hash) {
600                return Some(*bundle_hash);
601            }
602        }
603        None
604    }
605
606    pub async fn get_bundle_transactions(&self, bundle_hash: TxHash) -> Vec<MempoolTransaction> {
607        let state = self.state.read().await;
608        if let Some(tx_hashes) = state.transaction_bundles.get(&bundle_hash) {
609            tx_hashes
610                .iter()
611                .filter_map(|tx_hash| state.transactions.get(tx_hash))
612                .cloned()
613                .collect()
614        } else {
615            Vec::new()
616        }
617    }
618
619    pub async fn get_all_bundles(&self) -> HashMap<TxHash, Vec<TxHash>> {
620        let state = self.state.read().await;
621        state.transaction_bundles.clone()
622    }
623}
624
625/// Statistics about the mempool state
626#[derive(Debug, Clone)]
627pub struct MempoolStats {
628    pub total_transactions: usize,
629    pub total_value: U256,
630    pub total_gas: U256,
631    pub average_gas_price: U256,
632    pub last_block_number: u64,
633    pub eip1559_transactions: usize,
634    pub mev_transactions: usize,
635    pub protected_transactions: usize,
636}
637
638/// managing mempool service
639#[derive(Clone)]
640pub struct MempoolService {
641    evm: Arc<Evm>,
642}
643
644impl MempoolService {
645    /// Creates a new MempoolService
646    pub fn new(evm: Arc<Evm>) -> Self {
647        Self { evm: evm }
648    }
649
650    /// Creates a new mempool listener
651    ///
652    /// # Example
653    /// ```
654    /// let service = MempoolService::new(evm);
655    /// let listener = service.create_listener();
656    /// ```
657    pub fn create_listener(&self) -> MempoolListener {
658        MempoolListener::new(self.evm.clone())
659    }
660
661    /// Creates a new mempool listener with custom configuration
662    ///
663    /// # Example
664    /// ```
665    /// let config = MempoolConfig {
666    ///     poll_interval: Duration::from_secs(5),
667    ///     max_transactions: 5000,
668    ///     track_pending: true,
669    /// };
670    /// let listener = service.create_listener_with_config(config);
671    /// ```
672    pub fn create_listener_with_config(&self, config: MempoolConfig) -> MempoolListener {
673        MempoolListener::with_config(self.evm.clone(), config)
674    }
675
676    /// Quickly gets the count of pending transactions
677    ///
678    /// # Example
679    /// ```
680    /// let count = service.get_pending_transaction_count().await?;
681    /// println!("Pending transactions: {}", count);
682    /// ```
683    pub async fn get_pending_transaction_count(&self) -> Result<usize, EvmError> {
684        let filter = Filter::new().from_block(ethers::types::BlockNumber::Latest);
685        let logs = self.evm.get_logs(filter).await?;
686        Ok(logs.len())
687    }
688
689    /// Gets the current suggested gas price
690    ///
691    /// # Example
692    /// ```
693    /// let gas_price = service.get_suggested_gas_price().await?;
694    /// println!("Suggested gas price: {}", gas_price);
695    /// ```
696    pub async fn get_suggested_gas_price(&self) -> Result<U256, EvmError> {
697        self.evm.get_gas_price().await
698    }
699}