55
66class Mempool :
77 def __init__ (self , max_size = 1000 , transactions_per_block = 100 ):
8- self ._pool = {}
9- self ._size = 0
8+ self ._list = [] # Single sorted list
109 self ._lock = threading .Lock ()
1110 self .max_size = max_size
1211 self .transactions_per_block = transactions_per_block
@@ -17,64 +16,62 @@ def add_transaction(self, tx):
1716 return False
1817
1918 with self ._lock :
20- existing = self ._pool .get (tx .sender , {}).get (tx .nonce )
19+ existing_idx = None
20+ i_min = 0
21+ i_max = len (self ._list )
22+
23+ for i , existing_tx in enumerate (self ._list ):
24+ if existing_tx .sender == tx .sender :
25+ if existing_tx .nonce == tx .nonce :
26+ existing_idx = i
27+ elif existing_tx .nonce < tx .nonce :
28+ # Must insert AFTER the largest lower-nonce transaction
29+ i_min = max (i_min , i + 1 )
30+ elif existing_tx .nonce > tx .nonce :
31+ # Must insert BEFORE the smallest higher-nonce transaction
32+ i_max = min (i_max , i )
2133
22- if existing :
23- if existing .tx_id == tx .tx_id :
34+ if existing_idx is not None :
35+ existing_tx = self ._list [existing_idx ]
36+ if existing_tx .tx_id == tx .tx_id :
2437 logger .warning ("Mempool: Duplicate transaction rejected %s" , tx .tx_id )
2538 return False
26- # Fix: Guard against older replacements (e.g. rejected block restore)
27- # Only allow overwrite if it's a genuinely newer replacement
28- if tx .timestamp <= existing .timestamp :
39+ if tx .timestamp <= existing_tx .timestamp :
2940 logger .warning ("Mempool: Ignoring older replacement %s" , tx .tx_id )
3041 return False
3142
43+ self ._list .pop (existing_idx )
44+ if i_max > existing_idx :
45+ i_max -= 1
46+ if i_min > existing_idx :
47+ i_min -= 1
3248 else :
33- if self ._size >= self .max_size :
49+ if len ( self ._list ) >= self .max_size :
3450 logger .warning ("Mempool: Full, rejecting transaction" )
3551 return False
36- self ._size += 1
37- self ._pool .setdefault (tx .sender , {})[tx .nonce ] = tx
38- return True
39-
40- def get_transactions_for_block (self ):
41- with self ._lock :
42- snapshot = {s : list (pool .values ()) for s , pool in self ._pool .items ()}
4352
44- for txs in snapshot .values ():
45- txs .sort (key = lambda t : t .nonce )
53+ i_min = min (i_min , i_max )
4654
47- selected = []
48- while len (selected ) < self .transactions_per_block :
49- best_tx = None
50- best_sender = None
55+ # Insert before the first tx in [i_min, i_max] that has a lower fee
56+ insert_idx = i_max
57+ for j in range (i_min , i_max ):
58+ if getattr (self ._list [j ], 'fee' , 0 ) < getattr (tx , 'fee' , 0 ):
59+ insert_idx = j
60+ break
5161
52- for sender , txs in snapshot .items ():
53- if txs :
54- current_criteria = (- getattr (txs [0 ], 'fee' , 0 ), txs [0 ].timestamp , sender , txs [0 ].nonce )
55- best_criteria = (- getattr (best_tx , 'fee' , 0 ), best_tx .timestamp , best_sender , best_tx .nonce ) if best_tx else None
56- if best_tx is None or current_criteria < best_criteria :
57- best_tx = txs [0 ]
58- best_sender = sender
59-
60- if not best_tx :
61- break
62-
63- selected .append (best_tx )
64- snapshot [best_sender ].pop (0 )
62+ self ._list .insert (insert_idx , tx )
63+ return True
6564
66- return selected
65+ def get_transactions_for_block (self ):
66+ with self ._lock :
67+ # O(1) retrieval! The list is strictly ordered upon insertion.
68+ return list (self ._list [:self .transactions_per_block ])
6769
6870 def remove_transactions (self , transactions ):
6971 with self ._lock :
70- for tx in transactions :
71- pool = self ._pool .get (tx .sender )
72- if pool and tx .nonce in pool :
73- del pool [tx .nonce ]
74- self ._size -= 1
75- if not pool :
76- del self ._pool [tx .sender ]
72+ keys_to_remove = {(tx .sender , tx .nonce ) for tx in transactions }
73+ self ._list = [tx for tx in self ._list if (tx .sender , tx .nonce ) not in keys_to_remove ]
7774
7875 def __len__ (self ):
7976 with self ._lock :
80- return self ._size
77+ return len ( self ._list )
0 commit comments