Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

Commit e04b3aa

Browse files
committed
Fix TransactionQueue's non-stable sorting
Closes #730
1 parent 721b9f6 commit e04b3aa

File tree

1 file changed

+62
-18
lines changed

1 file changed

+62
-18
lines changed

ethereum/transaction_queue.py

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,51 @@
22
heapq.heaptop = lambda x: x[0]
33
PRIO_INFINITY = -2**100
44

5+
class OrderableTx(object):
6+
7+
def __init__(self, prio, counter, tx):
8+
self.prio = prio
9+
self.counter = counter
10+
self.tx = tx
11+
12+
def __lt__(self, other):
13+
if self.prio < other.prio:
14+
return True
15+
elif self.prio == other.prio:
16+
return self.counter < other.counter
17+
else:
18+
return False
19+
20+
521
class TransactionQueue():
622

723
def __init__(self):
24+
self.counter = 0
825
self.txs = []
926
self.aside = []
10-
self.last_max_gas = 2**100
1127

1228
def __len__(self):
1329
return len(self.txs)
1430

1531
def add_transaction(self, tx, force=False):
1632
prio = PRIO_INFINITY if force else -tx.gasprice
17-
heapq.heappush(self.txs, (prio, tx))
33+
heapq.heappush(self.txs, OrderableTx(prio, self.counter, tx))
34+
self.counter += 1
1835

1936
def pop_transaction(self, max_gas=9999999999, max_seek_depth=16, min_gasprice=0):
20-
while len(self.aside) and max_gas >= heapq.heaptop(self.aside)[0]:
21-
tx = heapq.heappop(self.aside)[1]
22-
heapq.heappush(self.txs, (-tx.gasprice, tx))
37+
while len(self.aside) and max_gas >= heapq.heaptop(self.aside).prio:
38+
item = heapq.heappop(self.aside)
39+
item.prio = -item.tx.gasprice
40+
heapq.heappush(self.txs, item)
2341
for i in range(min(len(self.txs), max_seek_depth)):
24-
prio, tx = heapq.heaptop(self.txs)
25-
if tx.startgas > max_gas:
42+
item = heapq.heaptop(self.txs)
43+
if item.tx.startgas > max_gas:
2644
heapq.heappop(self.txs)
27-
heapq.heappush(self.aside, (tx.startgas, tx))
28-
elif tx.gasprice >= min_gasprice or prio == PRIO_INFINITY:
45+
item.prio = item.tx.startgas
46+
heapq.heappush(self.aside, item)
47+
elif item.tx.gasprice >= min_gasprice or prio == PRIO_INFINITY:
2948
heapq.heappop(self.txs)
30-
return tx
49+
return item.tx
3150
else:
3251
return None
3352
return None
@@ -40,15 +59,15 @@ def peek(self, num=None):
4059

4160
def diff(self, txs):
4261
remove_hashes = [tx.hash for tx in txs]
43-
keep = [(prio, tx) for (prio, tx) in self.txs if tx.hash not in remove_hashes]
62+
keep = [item for item in self.txs if item.tx.hash not in remove_hashes]
4463
q = TransactionQueue()
4564
q.txs = keep
4665
return q
4766

4867

49-
def make_test_tx(s=100000, g=50, data=''):
68+
def make_test_tx(s=100000, g=50, data='', nonce=0):
5069
from ethereum.transactions import Transaction
51-
return Transaction(nonce=0, startgas=s, gasprice=g,
70+
return Transaction(nonce=nonce, startgas=s, gasprice=g,
5271
value=0, data=data, to='\x35' * 20)
5372

5473

@@ -89,11 +108,36 @@ def test_diff():
89108
q1.add_transaction(tx)
90109
q2 = q1.diff([tx2])
91110
assert len(q2) == 3
92-
assert tx1 in [tx for (_, tx) in q2.txs]
93-
assert tx3 in [tx for (_, tx) in q2.txs]
94-
assert tx4 in [tx for (_, tx) in q2.txs]
111+
assert tx1 in [item.tx for item in q2.txs]
112+
assert tx3 in [item.tx for item in q2.txs]
113+
assert tx4 in [item.tx for item in q2.txs]
95114

96115
q3 = q2.diff([tx4])
97116
assert len(q3) == 2
98-
assert tx1 in [tx for (_, tx) in q3.txs]
99-
assert tx3 in [tx for (_, tx) in q3.txs]
117+
assert tx1 in [item.tx for item in q3.txs]
118+
assert tx3 in [item.tx for item in q3.txs]
119+
120+
121+
def test_orderable_tx():
122+
assert OrderableTx(-1, 0, None) < OrderableTx(0, 0, None)
123+
assert OrderableTx(-1, 0, None) < OrderableTx(-1, 1, None)
124+
assert not OrderableTx(1, 0, None) < OrderableTx(-1, 0, None)
125+
assert not OrderableTx(1, 1, None) < OrderableTx(-1, 0, None)
126+
127+
128+
def test_ordering_for_same_prio():
129+
q = TransactionQueue()
130+
count = 10
131+
# Add <count> transactions to the queue, all with the same
132+
# startgas/gasprice but with sequential nonces.
133+
for i in range(count):
134+
q.add_transaction(make_test_tx(nonce=i))
135+
136+
expected_nonce_order = range(count)
137+
nonces = []
138+
for i in range(count):
139+
tx = q.pop_transaction()
140+
nonces.append(tx.nonce)
141+
# Since they have the same gasprice they should have the same priority and
142+
# thus be popped in the order they were inserted.
143+
assert nonces == expected_nonce_order

0 commit comments

Comments
 (0)