Skip to content

Commit fcb814a

Browse files
refactor(mempool): implement single sorted list architecture for O(1) mining
1 parent ce2cd52 commit fcb814a

1 file changed

Lines changed: 41 additions & 44 deletions

File tree

minichain/mempool.py

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
class 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

Comments
 (0)