Transaction Pool (Mempool)
Managing pending transactions before they're included in blocks.
Advanced⏱️ 50 min📚 Prerequisites: 1
Transaction Pool (Mempool)
The transaction pool (mempool) stores pending transactions waiting to be included in blocks. It's critical for blockchain performance.
Pool Structure
RUSTstruct TransactionPool { pending: Vec<Transaction>, by_sender: HashMap<String, Vec<Transaction>>, by_fee: BTreeMap<u64, Vec<Transaction>>, // Sorted by fee max_size: usize, }
Pool Operations
Adding Transactions
RUSTfn add_transaction(&mut self, tx: Transaction) -> Result<(), String> { // Validate transaction if !self.validate(&tx) { return Err(String::from("Invalid transaction")); } // Check nonce ordering if !self.check_nonce(&tx) { return Err(String::from("Invalid nonce")); } // Add to pool self.pending.push(tx); Ok(()) }
Selecting Transactions
RUSTfn select_for_block(&mut self, max_gas: u64) -> Vec<Transaction> { // Select transactions with highest fees // Respect gas limits // Maintain nonce ordering // Return selected transactions }
Transaction Ordering
- Fee priority: Higher fee = higher priority
- Nonce ordering: Must process in order per sender
- Gas limits: Respect block gas limit
Pool Management
- Eviction: Remove old/low-fee transactions
- Replacement: Replace with higher fee (same nonce)
- Cleanup: Remove included transactions
- Validation: Re-validate on state changes
Nonce Management
RUSTstruct NonceTracker { current_nonces: HashMap<String, u64>, pending_nonces: HashMap<String, HashSet<u64>>, } impl NonceTracker { fn can_add(&self, sender: &str, nonce: u64) -> bool { let current = self.current_nonces.get(sender).copied().unwrap_or(0); nonce == current || self.pending_nonces.get(sender) .map(|set| set.contains(&(nonce - 1))) .unwrap_or(false) } }
Performance Considerations
- Indexing: Fast lookups by sender, fee, etc.
- Sorting: Keep transactions sorted by priority
- Memory limits: Prevent pool from growing too large
- Concurrent access: Thread-safe operations
Code Examples
Transaction Pool
Basic transaction pool implementation
RUST
use std::collections::HashMap;
struct Transaction {
from: String,
to: String,
amount: u64,
fee: u64,
nonce: u64,
}
struct TransactionPool {
pending: Vec<Transaction>,
max_size: usize,
}
impl TransactionPool {
fn new(max_size: usize) -> Self {
TransactionPool {
pending: Vec::new(),
max_size,
}
}
fn add(&mut self, tx: Transaction) -> Result<(), String> {
if self.pending.len() >= self.max_size {
return Err(String::from("Pool is full"));
}
self.pending.push(tx);
Ok(())
}
fn select_best(&mut self, count: usize) -> Vec<Transaction> {
self.pending.sort_by(|a, b| b.fee.cmp(&a.fee));
self.pending.drain(..count.min(self.pending.len())).collect()
}
fn size(&self) -> usize {
self.pending.len()
}
}
fn main() {
let mut pool = TransactionPool::new(100);
pool.add(Transaction {
from: String::from("Alice"),
to: String::from("Bob"),
amount: 100,
fee: 10,
nonce: 1,
}).unwrap();
pool.add(Transaction {
from: String::from("Charlie"),
to: String::from("Dave"),
amount: 200,
fee: 20,
nonce: 1,
}).unwrap();
println!("Pool size: {}", pool.size());
let selected = pool.select_best(1);
println!("Selected transaction with fee: {}", selected[0].fee);
}Explanation:
A transaction pool stores pending transactions. It selects transactions based on priority (typically fee) for inclusion in blocks.
Fee Priority
Selecting transactions by fee
RUST
struct Transaction {
id: String,
fee: u64,
}
fn select_by_fee(transactions: &mut Vec<Transaction>, max_count: usize) -> Vec<Transaction> {
transactions.sort_by(|a, b| b.fee.cmp(&a.fee));
transactions.iter()
.take(max_count)
.cloned()
.collect()
}
fn main() {
let mut txs = vec![
Transaction { id: String::from("tx1"), fee: 5 },
Transaction { id: String::from("tx2"), fee: 20 },
Transaction { id: String::from("tx3"), fee: 10 },
Transaction { id: String::from("tx4"), fee: 15 },
];
let selected = select_by_fee(&mut txs, 2);
for tx in selected {
println!("Selected: {} (fee: {})", tx.id, tx.fee);
}
}Explanation:
Transactions are typically selected by fee priority. Higher fee transactions are included first, incentivizing users to pay higher fees for faster inclusion.
Exercises
Transaction Pool
Create a transaction pool that selects highest fee transactions!
Starter Code:
RUST
struct Transaction {
fee: u64,
data: String,
}
struct Pool {
transactions: Vec<Transaction>,
}
fn main() {
let mut pool = Pool::new();
pool.add(Transaction { fee: 10, data: String::from("tx1") });
pool.add(Transaction { fee: 30, data: String::from("tx2") });
pool.add(Transaction { fee: 20, data: String::from("tx3") });
let top = pool.select_top(2);
println!("Selected {} transactions", top.len());
}