1use std::{
2 cmp::Reverse,
3 collections::{BTreeMap, VecDeque, hash_map::Entry},
4 sync::RwLock,
5 sync::atomic::{AtomicU64, Ordering},
6 time::{Duration, Instant},
7};
8
9use rustc_hash::{FxHashMap, FxHashSet};
10
11use crate::error::MempoolError;
12use ethrex_common::{
13 Address, H160, H256, U256,
14 types::{
15 BlobTuple, BlobsBundle, BlockHeader, ChainConfig, Fork, MempoolTransaction, Transaction,
16 TxType, kzg_commitment_to_versioned_hash,
17 },
18};
19use ethrex_crypto::NativeCrypto;
20use ethrex_storage::error::StoreError;
21use ethrex_vm::{intrinsic_gas_dimensions, intrinsic_gas_floor};
22use tracing::warn;
23
24pub const MAX_ALTERNATES_PER_HASH: usize = 8;
32
33pub const MAX_BLOB_MEMPOOL_SIZE: usize = 512;
48
49#[derive(Debug, Clone, Copy)]
55pub struct Alternate {
56 pub peer_id: H256,
57 pub tx_type: u8,
58 pub tx_size: usize,
59}
60
61#[derive(Debug, Default)]
62struct MempoolInner {
63 broadcast_pool: FxHashSet<H256>,
64 transaction_pool: FxHashMap<H256, MempoolTransaction>,
65 blobs_bundle_pool: FxHashMap<H256, BlobsBundle>,
66 in_flight_txs: FxHashSet<H256>,
70 alternates: FxHashMap<H256, (VecDeque<Alternate>, Instant)>,
79 blobs_bundle_by_versioned_hash: FxHashMap<H256, FxHashMap<H256, usize>>,
82 txs_by_sender_nonce: BTreeMap<(H160, u64), H256>,
83 txs_order: VecDeque<H256>,
84 max_mempool_size: usize,
85 max_blob_mempool_size: usize,
86 mempool_prune_threshold: usize,
88}
89
90impl MempoolInner {
91 fn new(max_mempool_size: usize) -> Self {
92 MempoolInner {
93 txs_order: VecDeque::with_capacity(max_mempool_size * 2),
94 transaction_pool: FxHashMap::with_capacity_and_hasher(
95 max_mempool_size,
96 Default::default(),
97 ),
98 max_mempool_size,
99 max_blob_mempool_size: MAX_BLOB_MEMPOOL_SIZE,
100 mempool_prune_threshold: max_mempool_size + max_mempool_size / 2,
101 ..Default::default()
102 }
103 }
104
105 fn remove_transaction_with_lock(&mut self, hash: &H256) -> Result<(), StoreError> {
107 let Some(tx) = self.transaction_pool.remove(hash) else {
108 return Ok(());
109 };
110 if matches!(tx.tx_type(), TxType::EIP4844) {
111 self.remove_blob_bundle(hash);
112 }
113
114 self.txs_by_sender_nonce.remove(&(tx.sender(), tx.nonce()));
115 self.broadcast_pool.remove(hash);
116
117 Ok(())
118 }
119
120 pub fn remove_blob_bundle(&mut self, hash: &H256) {
122 let Some(h) = self.blobs_bundle_pool.remove(hash) else {
123 return;
124 };
125
126 for commitment in &h.commitments {
127 let versioned_hash = kzg_commitment_to_versioned_hash(commitment);
128 if let Entry::Occupied(mut entry) =
129 self.blobs_bundle_by_versioned_hash.entry(versioned_hash)
130 {
131 let txn_to_bundle = entry.get_mut();
132 txn_to_bundle.remove(hash);
133 if txn_to_bundle.is_empty() {
134 entry.remove();
135 }
136 }
137 }
138 }
139
140 fn blob_tx_count(&self) -> usize {
143 self.blobs_bundle_pool.len()
144 }
145
146 fn regular_tx_count(&self) -> usize {
148 self.transaction_pool
153 .len()
154 .saturating_sub(self.blob_tx_count())
155 }
156
157 fn remove_oldest_regular_transaction(&mut self) -> Result<(), StoreError> {
161 while self.regular_tx_count() >= self.max_mempool_size {
162 if let Some(oldest_hash) = self.txs_order.pop_front() {
163 self.remove_transaction_with_lock(&oldest_hash)?;
164 } else {
165 warn!(
166 "Regular mempool is full but there are no transactions to remove, this should not happen and will make the mempool grow indefinitely"
167 );
168 break;
169 }
170 }
171
172 Ok(())
173 }
174
175 fn remove_worst_blob_transaction(&mut self) -> Result<(), StoreError> {
191 while self.blob_tx_count() > self.max_blob_mempool_size {
192 let mut min_nonce_by_sender: FxHashMap<Address, u64> = FxHashMap::default();
196 for tx in self
197 .blobs_bundle_pool
198 .keys()
199 .filter_map(|hash| self.transaction_pool.get(hash))
200 {
201 min_nonce_by_sender
202 .entry(tx.sender())
203 .and_modify(|n| *n = (*n).min(tx.nonce()))
204 .or_insert(tx.nonce());
205 }
206 let worst = self
210 .blobs_bundle_pool
211 .keys()
212 .filter_map(|hash| self.transaction_pool.get(hash).map(|tx| (*hash, tx)))
213 .max_by_key(|(_, tx)| {
214 let baseline = min_nonce_by_sender.get(&tx.sender()).copied().unwrap_or(0);
215 let offset = tx.nonce().saturating_sub(baseline);
216 (offset, Reverse(tx.max_fee_per_blob_gas()))
217 })
218 .map(|(hash, _)| hash);
219 match worst {
220 Some(hash) => self.remove_transaction_with_lock(&hash)?,
221 None => {
222 warn!(
223 "Blob mempool is over cap but no evictable blob transaction is present, this should not happen"
224 );
225 break;
226 }
227 }
228 }
229
230 Ok(())
231 }
232}
233
234#[derive(Debug, Default)]
235pub struct Mempool {
236 inner: RwLock<MempoolInner>,
237 tx_added: tokio::sync::Notify,
240 tx_seq: AtomicU64,
245}
246
247impl Mempool {
248 pub fn new(max_mempool_size: usize) -> Self {
249 Mempool {
250 inner: RwLock::new(MempoolInner::new(max_mempool_size)),
251 tx_added: tokio::sync::Notify::new(),
252 tx_seq: AtomicU64::new(0),
253 }
254 }
255
256 pub fn with_max_blob_mempool_size(self, max_blob_mempool_size: usize) -> Self {
259 if let Ok(mut inner) = self.inner.write() {
260 inner.max_blob_mempool_size = max_blob_mempool_size;
261 }
262 self
263 }
264
265 pub(crate) fn tx_added(&self) -> &tokio::sync::Notify {
266 &self.tx_added
267 }
268
269 pub(crate) fn tx_seq(&self) -> u64 {
270 self.tx_seq.load(Ordering::Acquire)
271 }
272
273 fn write(&self) -> Result<std::sync::RwLockWriteGuard<'_, MempoolInner>, StoreError> {
274 self.inner
275 .write()
276 .map_err(|error| StoreError::MempoolWriteLock(error.to_string()))
277 }
278
279 fn read(&self) -> Result<std::sync::RwLockReadGuard<'_, MempoolInner>, StoreError> {
280 self.inner
281 .read()
282 .map_err(|error| StoreError::MempoolReadLock(error.to_string()))
283 }
284
285 pub fn add_transaction(
287 &self,
288 hash: H256,
289 sender: Address,
290 transaction: MempoolTransaction,
291 ) -> Result<(), StoreError> {
292 let mut inner = self.write()?;
293 let is_blob = matches!(transaction.tx_type(), TxType::EIP4844);
294 if inner.txs_order.len() > inner.mempool_prune_threshold {
296 let txpool = core::mem::take(&mut inner.transaction_pool);
298 inner.txs_order.retain(|tx| txpool.contains_key(tx));
299 inner.transaction_pool = txpool;
300 }
301 if is_blob {
306 if inner.blob_tx_count() > inner.max_blob_mempool_size {
309 inner.remove_worst_blob_transaction()?;
310 }
311 } else {
312 if inner.regular_tx_count() >= inner.max_mempool_size {
318 inner.remove_oldest_regular_transaction()?;
319 }
320 inner.txs_order.push_back(hash);
321 }
322 inner
323 .txs_by_sender_nonce
324 .insert((sender, transaction.nonce()), hash);
325 inner.transaction_pool.insert(hash, transaction);
326 inner.broadcast_pool.insert(hash);
327 inner.alternates.remove(&hash);
328 drop(inner);
330 self.tx_seq.fetch_add(1, Ordering::Release);
336 self.tx_added.notify_waiters();
337
338 Ok(())
339 }
340
341 pub fn get_txs_for_broadcast(&self) -> Result<Vec<MempoolTransaction>, StoreError> {
342 let inner = self.read()?;
343 let txs = inner
344 .transaction_pool
345 .iter()
346 .filter_map(|(hash, tx)| {
347 if !inner.broadcast_pool.contains(hash) {
348 None
349 } else {
350 Some(tx.clone())
351 }
352 })
353 .collect::<Vec<_>>();
354 Ok(txs)
355 }
356
357 pub fn remove_broadcasted_txs(&self, hashes: &[H256]) -> Result<(), StoreError> {
358 let mut inner = self.write()?;
359 for hash in hashes {
360 inner.broadcast_pool.remove(hash);
361 }
362 Ok(())
363 }
364
365 pub fn blob_txs(&self) -> Result<Vec<(H256, Address, u64)>, StoreError> {
368 let inner = self.read()?;
369 Ok(inner
370 .blobs_bundle_pool
371 .keys()
372 .filter_map(|hash| {
373 inner
374 .transaction_pool
375 .get(hash)
376 .map(|tx| (*hash, tx.sender(), tx.nonce()))
377 })
378 .collect())
379 }
380
381 pub fn add_blobs_bundle(
383 &self,
384 tx_hash: H256,
385 blobs_bundle: BlobsBundle,
386 ) -> Result<(), StoreError> {
387 let mut mempool = self.write()?;
388 for (i, c) in blobs_bundle.commitments.iter().enumerate() {
389 let versioned_hash = kzg_commitment_to_versioned_hash(c);
390 mempool
391 .blobs_bundle_by_versioned_hash
392 .entry(versioned_hash)
393 .or_default()
394 .insert(tx_hash, i);
395 }
396 mempool.blobs_bundle_pool.insert(tx_hash, blobs_bundle);
397 Ok(())
398 }
399
400 pub fn get_blobs_bundle(&self, tx_hash: H256) -> Result<Option<BlobsBundle>, StoreError> {
402 Ok(self.read()?.blobs_bundle_pool.get(&tx_hash).cloned())
403 }
404
405 pub fn remove_transaction(&self, hash: &H256) -> Result<(), StoreError> {
407 let mut inner = self.write()?;
408 inner.remove_transaction_with_lock(hash)?;
409 Ok(())
410 }
411
412 pub fn filter_transactions(
415 &self,
416 filter: &PendingTxFilter,
417 ) -> Result<FxHashMap<Address, Vec<MempoolTransaction>>, StoreError> {
418 let filter_tx = |tx: &Transaction| -> bool {
419 let is_blob_tx = matches!(tx, Transaction::EIP4844Transaction(_));
421 if filter.only_plain_txs && is_blob_tx || filter.only_blob_txs && !is_blob_tx {
422 return false;
423 }
424
425 if let Some(min_tip) = filter.min_tip.map(U256::from) {
427 if tx
428 .effective_gas_tip(filter.base_fee)
429 .is_none_or(|tip| tip < min_tip)
430 {
431 return false;
432 }
433 } else if tx.effective_gas_tip(filter.base_fee).is_none() {
437 return false;
438 }
439
440 if is_blob_tx
442 && let Some(blob_fee) = filter.blob_fee
443 && tx
444 .max_fee_per_blob_gas()
445 .is_none_or(|fee| fee < blob_fee.into())
446 {
447 return false;
448 }
449 true
450 };
451 self.filter_transactions_with_filter_fn(&filter_tx)
452 }
453
454 pub fn get_all_txs_by_sender(
456 &self,
457 ) -> Result<FxHashMap<Address, Vec<MempoolTransaction>>, StoreError> {
458 let mut txs_by_sender: FxHashMap<Address, Vec<MempoolTransaction>> =
459 FxHashMap::with_capacity_and_hasher(128, Default::default());
460 let tx_pool = &self.read()?.transaction_pool;
461
462 for (_, tx) in tx_pool.iter() {
463 txs_by_sender
464 .entry(tx.sender())
465 .or_insert_with(|| Vec::with_capacity(128))
466 .push(tx.clone())
467 }
468
469 txs_by_sender.iter_mut().for_each(|(_, txs)| txs.sort());
470 Ok(txs_by_sender)
471 }
472
473 pub fn filter_transactions_with_filter_fn(
476 &self,
477 filter: &dyn Fn(&Transaction) -> bool,
478 ) -> Result<FxHashMap<Address, Vec<MempoolTransaction>>, StoreError> {
479 let mut txs_by_sender: FxHashMap<Address, Vec<MempoolTransaction>> =
480 FxHashMap::with_capacity_and_hasher(128, Default::default());
481 let tx_pool = &self.read()?.transaction_pool;
482
483 for (_, tx) in tx_pool.iter() {
484 if filter(tx) {
485 txs_by_sender
486 .entry(tx.sender())
487 .or_insert_with(|| Vec::with_capacity(128))
488 .push(tx.clone())
489 }
490 }
491
492 txs_by_sender.iter_mut().for_each(|(_, txs)| txs.sort());
493 Ok(txs_by_sender)
494 }
495
496 pub fn reserve_unknown_hashes(
515 &self,
516 hashes: &[H256],
517 types: &[u8],
518 sizes: &[usize],
519 announcer: H256,
520 ) -> Result<Vec<H256>, StoreError> {
521 debug_assert_eq!(hashes.len(), types.len());
522 debug_assert_eq!(hashes.len(), sizes.len());
523
524 let mut inner = self.write()?;
525
526 let unknown: Vec<H256> = hashes
527 .iter()
528 .filter(|hash| {
529 !inner.in_flight_txs.contains(hash) && !inner.transaction_pool.contains_key(hash)
530 })
531 .copied()
532 .collect();
533
534 inner.in_flight_txs.extend(unknown.iter().copied());
535
536 if hashes.len() > unknown.len() {
540 let unknown_set: FxHashSet<H256> = unknown.iter().copied().collect();
541 let now = Instant::now();
542 for (i, hash) in hashes.iter().enumerate() {
543 if unknown_set.contains(hash) || inner.transaction_pool.contains_key(hash) {
544 continue;
545 }
546 let alt = Alternate {
547 peer_id: announcer,
548 tx_type: types[i],
549 tx_size: sizes[i],
550 };
551 let entry = inner
552 .alternates
553 .entry(*hash)
554 .or_insert_with(|| (VecDeque::new(), now));
555 entry.1 = now;
556 if !entry.0.iter().any(|a| a.peer_id == announcer) {
557 if entry.0.len() >= MAX_ALTERNATES_PER_HASH {
558 entry.0.pop_front();
559 }
560 entry.0.push_back(alt);
561 }
562 }
563 }
564
565 Ok(unknown)
566 }
567
568 pub fn clear_in_flight_txs(&self, hashes: &[H256]) -> Result<(), StoreError> {
571 let mut inner = self.write()?;
572 for hash in hashes {
573 inner.in_flight_txs.remove(hash);
574 }
575 Ok(())
576 }
577
578 pub fn pop_alternate(&self, hash: H256) -> Result<Option<Alternate>, StoreError> {
583 let mut inner = self.write()?;
584 let Some(entry) = inner.alternates.get_mut(&hash) else {
585 return Ok(None);
586 };
587 let popped = entry.0.pop_front();
588 if entry.0.is_empty() {
589 inner.alternates.remove(&hash);
590 }
591 Ok(popped)
592 }
593
594 pub fn prune_alternates(&self, ttl: Duration) -> Result<(), StoreError> {
598 let mut inner = self.write()?;
599 let now = Instant::now();
600 inner
601 .alternates
602 .retain(|_, (_, last_seen)| now.saturating_duration_since(*last_seen) < ttl);
603 Ok(())
604 }
605
606 pub fn get_transaction_by_hash(
607 &self,
608 transaction_hash: H256,
609 ) -> Result<Option<Transaction>, StoreError> {
610 let tx = self
611 .read()?
612 .transaction_pool
613 .get(&transaction_hash)
614 .map(|e| e.transaction().clone());
615
616 Ok(tx)
617 }
618
619 pub fn get_nonce(&self, address: &Address) -> Result<Option<u64>, MempoolError> {
620 Ok(self
621 .read()?
622 .txs_by_sender_nonce
623 .range((*address, 0)..=(*address, u64::MAX))
624 .last()
625 .map(|((_address, nonce), _hash)| nonce + 1))
626 }
627
628 pub fn get_mempool_size(&self) -> Result<(u64, u64), MempoolError> {
629 let txs_size = {
630 let pool_lock = &self.read()?.transaction_pool;
631 pool_lock.len()
632 };
633 let blobs_size = {
634 let pool_lock = &self.read()?.blobs_bundle_pool;
635 pool_lock.len()
636 };
637
638 Ok((txs_size as u64, blobs_size as u64))
639 }
640
641 pub fn content(&self) -> Result<Vec<Transaction>, MempoolError> {
643 let pooled_transactions = &self.read()?.transaction_pool;
644 Ok(pooled_transactions
645 .values()
646 .map(MempoolTransaction::transaction)
647 .cloned()
648 .collect())
649 }
650
651 pub fn get_blobs_bundle_pool(&self) -> Result<Vec<BlobsBundle>, MempoolError> {
653 let blobs_bundle_pool = &self.read()?.blobs_bundle_pool;
654 Ok(blobs_bundle_pool.values().cloned().collect())
655 }
656
657 pub fn get_blobs_data_by_versioned_hashes(
659 &self,
660 versioned_hashes: &[H256],
661 ) -> Result<Vec<Option<BlobTuple>>, MempoolError> {
662 let mempool = self.read()?;
663 let blobs_bundle_pool = &mempool.blobs_bundle_pool;
664 let blobs_bundle_by_versioned_hash = &mempool.blobs_bundle_by_versioned_hash;
665 let mut res = vec![None; versioned_hashes.len()];
666 for (idx, vh) in versioned_hashes.iter().enumerate() {
667 if let Some((found_hash, inner_pos)) = blobs_bundle_by_versioned_hash
668 .get(vh)
669 .and_then(|h| h.iter().next())
670 {
671 res[idx] = blobs_bundle_pool
672 .get(found_hash)
673 .and_then(|b| b.get_blob_tuple_by_index(*inner_pos))
674 }
675 }
676 Ok(res)
677 }
678
679 pub fn status(&self) -> Result<u64, MempoolError> {
682 let pool_lock = &self.read()?.transaction_pool;
683
684 Ok(pool_lock.len() as u64)
685 }
686
687 pub fn contains_sender_nonce(
688 &self,
689 sender: Address,
690 nonce: u64,
691 received_hash: H256,
692 ) -> Result<Option<MempoolTransaction>, MempoolError> {
693 let Some(hash) = self
694 .read()?
695 .txs_by_sender_nonce
696 .get(&(sender, nonce))
697 .cloned()
698 else {
699 return Ok(None);
700 };
701 if hash == received_hash {
702 return Ok(None);
703 }
704
705 let transaction_pool = &self.read()?.transaction_pool;
706 let tx = transaction_pool.get(&hash).cloned();
707 Ok(tx)
708 }
709
710 pub fn contains_tx(&self, tx_hash: H256) -> Result<bool, MempoolError> {
711 let contains = self.read()?.transaction_pool.contains_key(&tx_hash);
712 Ok(contains)
713 }
714
715 pub fn find_tx_to_replace(
716 &self,
717 sender: Address,
718 nonce: u64,
719 tx: &Transaction,
720 ) -> Result<Option<H256>, MempoolError> {
721 let Some(tx_in_pool) = self.contains_sender_nonce(sender, nonce, tx.hash(&NativeCrypto))?
722 else {
723 return Ok(None);
724 };
725 let is_a_replacement_tx = {
726 let old_tx_max_fee_per_gas = tx_in_pool.max_fee_per_gas().unwrap_or_default();
728 let old_tx_max_priority_fee_per_gas = tx_in_pool.max_priority_fee().unwrap_or_default();
729 let new_tx_max_fee_per_gas = tx.max_fee_per_gas().unwrap_or_default();
730 let new_tx_max_priority_fee_per_gas = tx.max_priority_fee().unwrap_or_default();
731
732 let old_tx_gas_price = tx_in_pool.gas_price();
734 let new_tx_gas_price = tx.gas_price();
735
736 let old_tx_max_fee_per_blob = tx_in_pool.max_fee_per_blob_gas();
738 let new_tx_max_fee_per_blob = tx.max_fee_per_blob_gas();
739
740 let eip4844_higher_fees = if let (Some(old_blob_fee), Some(new_blob_fee)) =
741 (old_tx_max_fee_per_blob, new_tx_max_fee_per_blob)
742 {
743 new_blob_fee > old_blob_fee
744 } else {
745 true };
747
748 let eip1559_higher_fees = new_tx_max_fee_per_gas > old_tx_max_fee_per_gas
749 && new_tx_max_priority_fee_per_gas > old_tx_max_priority_fee_per_gas;
750 let legacy_higher_fees = new_tx_gas_price > old_tx_gas_price;
751
752 eip4844_higher_fees && (eip1559_higher_fees || legacy_higher_fees)
753 };
754
755 if !is_a_replacement_tx {
756 return Err(MempoolError::UnderpricedReplacement);
757 }
758
759 Ok(Some(tx_in_pool.hash(&NativeCrypto)))
760 }
761}
762
763#[derive(Debug, Default)]
768pub struct PendingTxFilter {
769 pub min_tip: Option<u64>,
773 pub base_fee: Option<u64>,
774 pub blob_fee: Option<u64>,
775 pub only_plain_txs: bool,
776 pub only_blob_txs: bool,
777}
778
779pub fn transaction_intrinsic_gas(
780 tx: &Transaction,
781 header: &BlockHeader,
782 config: &ChainConfig,
783) -> Result<u64, MempoolError> {
784 let fork = config.fork(header.timestamp);
797 let (regular, state) = intrinsic_gas_dimensions(tx, fork, header.gas_limit)
798 .map_err(|e| MempoolError::IntrinsicGasError(e.to_string()))?;
799 let intrinsic = regular
800 .checked_add(state)
801 .ok_or(MempoolError::TxGasOverflowError)?;
802 let calldata_floor = if fork >= Fork::Prague {
806 intrinsic_gas_floor(tx, fork).map_err(|e| MempoolError::IntrinsicGasError(e.to_string()))?
807 } else {
808 0
809 };
810 Ok(intrinsic.max(calldata_floor))
811}