Skip to content

Commit bcb3087

Browse files
committed
Revert "core, txpool: less allocations when handling transactions (#21232)"
Reverting because this change started handling account balances as uint64 in the transaction pool, which is incorrect. This reverts commit af5c97a.
1 parent 967d8de commit bcb3087

File tree

6 files changed

+54
-151
lines changed

6 files changed

+54
-151
lines changed

common/math/integer.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package math
1818

1919
import (
2020
"fmt"
21-
"math/bits"
2221
"strconv"
2322
)
2423

@@ -88,12 +87,13 @@ func SafeSub(x, y uint64) (uint64, bool) {
8887

8988
// SafeAdd returns the result and whether overflow occurred.
9089
func SafeAdd(x, y uint64) (uint64, bool) {
91-
sum, carry := bits.Add64(x, y, 0)
92-
return sum, carry != 0
90+
return x + y, y > MaxUint64-x
9391
}
9492

9593
// SafeMul returns multiplication result and whether overflow occurred.
9694
func SafeMul(x, y uint64) (uint64, bool) {
97-
hi, lo := bits.Mul64(x, y)
98-
return lo, hi != 0
95+
if x == 0 || y == 0 {
96+
return 0, false
97+
}
98+
return x * y, y > MaxUint64/x
9999
}

core/tx_list.go

Lines changed: 30 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -99,30 +99,7 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
9999

100100
// Filter iterates over the list of transactions and removes all of them for which
101101
// the specified function evaluates to true.
102-
// Filter, as opposed to 'filter', re-initialises the heap after the operation is done.
103-
// If you want to do several consecutive filterings, it's therefore better to first
104-
// do a .filter(func1) followed by .Filter(func2) or reheap()
105102
func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
106-
removed := m.filter(filter)
107-
// If transactions were removed, the heap and cache are ruined
108-
if len(removed) > 0 {
109-
m.reheap()
110-
}
111-
return removed
112-
}
113-
114-
func (m *txSortedMap) reheap() {
115-
*m.index = make([]uint64, 0, len(m.items))
116-
for nonce := range m.items {
117-
*m.index = append(*m.index, nonce)
118-
}
119-
heap.Init(m.index)
120-
m.cache = nil
121-
}
122-
123-
// filter is identical to Filter, but **does not** regenerate the heap. This method
124-
// should only be used if followed immediately by a call to Filter or reheap()
125-
func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
126103
var removed types.Transactions
127104

128105
// Collect all the transactions to filter out
@@ -132,7 +109,14 @@ func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transac
132109
delete(m.items, nonce)
133110
}
134111
}
112+
// If transactions were removed, the heap and cache are ruined
135113
if len(removed) > 0 {
114+
*m.index = make([]uint64, 0, len(m.items))
115+
for nonce := range m.items {
116+
*m.index = append(*m.index, nonce)
117+
}
118+
heap.Init(m.index)
119+
136120
m.cache = nil
137121
}
138122
return removed
@@ -213,7 +197,10 @@ func (m *txSortedMap) Len() int {
213197
return len(m.items)
214198
}
215199

216-
func (m *txSortedMap) flatten() types.Transactions {
200+
// Flatten creates a nonce-sorted slice of transactions based on the loosely
201+
// sorted internal representation. The result of the sorting is cached in case
202+
// it's requested again before any modifications are made to the contents.
203+
func (m *txSortedMap) Flatten() types.Transactions {
217204
// If the sorting was not cached yet, create and cache it
218205
if m.cache == nil {
219206
m.cache = make(types.Transactions, 0, len(m.items))
@@ -222,27 +209,12 @@ func (m *txSortedMap) flatten() types.Transactions {
222209
}
223210
sort.Sort(types.TxByNonce(m.cache))
224211
}
225-
return m.cache
226-
}
227-
228-
// Flatten creates a nonce-sorted slice of transactions based on the loosely
229-
// sorted internal representation. The result of the sorting is cached in case
230-
// it's requested again before any modifications are made to the contents.
231-
func (m *txSortedMap) Flatten() types.Transactions {
232212
// Copy the cache to prevent accidental modifications
233-
cache := m.flatten()
234-
txs := make(types.Transactions, len(cache))
235-
copy(txs, cache)
213+
txs := make(types.Transactions, len(m.cache))
214+
copy(txs, m.cache)
236215
return txs
237216
}
238217

239-
// LastElement returns the last element of a flattened list, thus, the
240-
// transaction with the highest nonce
241-
func (m *txSortedMap) LastElement() *types.Transaction {
242-
cache := m.flatten()
243-
return cache[len(cache)-1]
244-
}
245-
246218
// txList is a "list" of transactions belonging to an account, sorted by account
247219
// nonce. The same type can be used both for storing contiguous transactions for
248220
// the executable/pending queue; and for storing gapped transactions for the non-
@@ -251,16 +223,17 @@ type txList struct {
251223
strict bool // Whether nonces are strictly continuous or not
252224
txs *txSortedMap // Heap indexed sorted hash map of the transactions
253225

254-
costcap uint64 // Price of the highest costing transaction (reset only if exceeds balance)
255-
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
226+
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
227+
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
256228
}
257229

258230
// newTxList create a new transaction list for maintaining nonce-indexable fast,
259231
// gapped, sortable transaction lists.
260232
func newTxList(strict bool) *txList {
261233
return &txList{
262-
strict: strict,
263-
txs: newTxSortedMap(),
234+
strict: strict,
235+
txs: newTxSortedMap(),
236+
costcap: new(big.Int),
264237
}
265238
}
266239

@@ -279,26 +252,17 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
279252
// If there's an older better transaction, abort
280253
old := l.txs.Get(tx.Nonce())
281254
if old != nil {
282-
// threshold = oldGP * (100 + priceBump) / 100
283-
a := big.NewInt(100 + int64(priceBump))
284-
a = a.Mul(a, old.GasPrice())
285-
b := big.NewInt(100)
286-
threshold := a.Div(a, b)
255+
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
287256
// Have to ensure that the new gas price is higher than the old gas
288257
// price as well as checking the percentage threshold to ensure that
289258
// this is accurate for low (Wei-level) gas price replacements
290259
if old.GasPriceCmp(tx) >= 0 || tx.GasPriceIntCmp(threshold) < 0 {
291260
return false, nil
292261
}
293262
}
294-
cost, overflow := tx.CostU64()
295-
if overflow {
296-
log.Warn("transaction cost overflown, txHash: %v txCost: %v", tx.Hash(), cost)
297-
return false, nil
298-
}
299263
// Otherwise overwrite the old transaction with the current one
300264
l.txs.Put(tx)
301-
if l.costcap < cost {
265+
if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
302266
l.costcap = cost
303267
}
304268
if gas := tx.Gas(); l.gascap < gas {
@@ -323,35 +287,29 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
323287
// a point in calculating all the costs or if the balance covers all. If the threshold
324288
// is lower than the costgas cap, the caps will be reset to a new high after removing
325289
// the newly invalidated transactions.
326-
func (l *txList) Filter(costLimit uint64, gasLimit uint64) (types.Transactions, types.Transactions) {
290+
func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
327291
// If all transactions are below the threshold, short circuit
328-
if l.costcap <= costLimit && l.gascap <= gasLimit {
292+
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
329293
return nil, nil
330294
}
331-
l.costcap = costLimit // Lower the caps to the thresholds
295+
l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds
332296
l.gascap = gasLimit
333297

334298
// Filter out all the transactions above the account's funds
335-
removed := l.txs.filter(func(tx *types.Transaction) bool {
336-
cost, _ := tx.CostU64()
337-
return cost > costLimit || tx.Gas() > gasLimit
338-
})
299+
removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit })
339300

340-
if len(removed) == 0 {
341-
return nil, nil
342-
}
343-
var invalids types.Transactions
344301
// If the list was strict, filter anything above the lowest nonce
345-
if l.strict {
302+
var invalids types.Transactions
303+
304+
if l.strict && len(removed) > 0 {
346305
lowest := uint64(math.MaxUint64)
347306
for _, tx := range removed {
348307
if nonce := tx.Nonce(); lowest > nonce {
349308
lowest = nonce
350309
}
351310
}
352-
invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
311+
invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
353312
}
354-
l.txs.reheap()
355313
return removed, invalids
356314
}
357315

@@ -405,12 +363,6 @@ func (l *txList) Flatten() types.Transactions {
405363
return l.txs.Flatten()
406364
}
407365

408-
// LastElement returns the last element of a flattened list, thus, the
409-
// transaction with the highest nonce
410-
func (l *txList) LastElement() *types.Transaction {
411-
return l.txs.LastElement()
412-
}
413-
414366
// priceHeap is a heap.Interface implementation over transactions for retrieving
415367
// price-sorted transactions to discard when the pool fills up.
416368
type priceHeap []*types.Transaction
@@ -543,29 +495,8 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
543495
// Discard finds a number of most underpriced transactions, removes them from the
544496
// priced list and returns them for further removal from the entire pool.
545497
func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions {
546-
// If we have some local accountset, those will not be discarded
547-
if !local.empty() {
548-
// In case the list is filled to the brim with 'local' txs, we do this
549-
// little check to avoid unpacking / repacking the heap later on, which
550-
// is very expensive
551-
discardable := 0
552-
for _, tx := range *l.items {
553-
if !local.containsTx(tx) {
554-
discardable++
555-
}
556-
if discardable >= slots {
557-
break
558-
}
559-
}
560-
if slots > discardable {
561-
slots = discardable
562-
}
563-
}
564-
if slots == 0 {
565-
return nil
566-
}
567-
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
568-
save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep
498+
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
499+
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
569500

570501
for len(*l.items) > 0 && slots > 0 {
571502
// Discard stale transactions if found during cleanup

core/tx_list_test.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package core
1818

1919
import (
20+
"math/big"
2021
"math/rand"
2122
"testing"
2223

@@ -50,22 +51,20 @@ func TestStrictTxListAdd(t *testing.T) {
5051
}
5152
}
5253

53-
func BenchmarkTxListAdd(b *testing.B) {
54+
func BenchmarkTxListAdd(t *testing.B) {
5455
// Generate a list of transactions to insert
5556
key, _ := crypto.GenerateKey()
5657

57-
txs := make(types.Transactions, 2000)
58+
txs := make(types.Transactions, 100000)
5859
for i := 0; i < len(txs); i++ {
5960
txs[i] = transaction(uint64(i), 0, key)
6061
}
6162
// Insert the transactions in a random order
62-
b.ResetTimer()
63-
priceLimit := DefaultTxPoolConfig.PriceLimit
64-
for i := 0; i < b.N; i++ {
65-
list := newTxList(true)
66-
for _, v := range rand.Perm(len(txs)) {
67-
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
68-
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
69-
}
63+
list := newTxList(true)
64+
priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit))
65+
t.ResetTimer()
66+
for _, v := range rand.Perm(len(txs)) {
67+
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
68+
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
7069
}
7170
}

core/tx_pool.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -543,11 +543,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
543543
}
544544
// Transactor should have enough funds to cover the costs
545545
// cost == V + GP * GL
546-
cost, overflow := tx.CostU64()
547-
if overflow {
548-
return ErrInsufficientFunds
549-
}
550-
if pool.currentState.GetBalance(from).Uint64() < cost {
546+
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
551547
return ErrInsufficientFunds
552548
}
553549
// Ensure the transaction has more gas than the basic tx fee.
@@ -1063,8 +1059,8 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
10631059

10641060
// Update all accounts to the latest known pending nonce
10651061
for addr, list := range pool.pending {
1066-
highestPending := list.LastElement()
1067-
pool.pendingNonces.set(addr, highestPending.Nonce()+1)
1062+
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
1063+
pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1)
10681064
}
10691065
pool.mu.Unlock()
10701066

@@ -1194,7 +1190,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
11941190
}
11951191
log.Trace("Removed old queued transactions", "count", len(forwards))
11961192
// Drop all transactions that are too costly (low balance or out of gas)
1197-
drops, _ := list.Filter(pool.currentState.GetBalance(addr).Uint64(), pool.currentMaxGas)
1193+
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
11981194
for _, tx := range drops {
11991195
hash := tx.Hash()
12001196
pool.all.Remove(hash)
@@ -1386,7 +1382,7 @@ func (pool *TxPool) demoteUnexecutables() {
13861382
log.Trace("Removed old pending transaction", "hash", hash)
13871383
}
13881384
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
1389-
drops, invalids := list.Filter(pool.currentState.GetBalance(addr).Uint64(), pool.currentMaxGas)
1385+
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
13901386
for _, tx := range drops {
13911387
hash := tx.Hash()
13921388
log.Trace("Removed unpayable pending transaction", "hash", hash)
@@ -1461,10 +1457,6 @@ func (as *accountSet) contains(addr common.Address) bool {
14611457
return exist
14621458
}
14631459

1464-
func (as *accountSet) empty() bool {
1465-
return len(as.accounts) == 0
1466-
}
1467-
14681460
// containsTx checks if the sender of a given tx is within the set. If the sender
14691461
// cannot be derived, this method returns false.
14701462
func (as *accountSet) containsTx(tx *types.Transaction) bool {

core/tx_pool_test.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,15 +1890,11 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
18901890
}
18911891

18921892
// Benchmarks the speed of batched transaction insertion.
1893-
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, false) }
1894-
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, false) }
1895-
func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, false) }
1893+
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) }
1894+
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) }
1895+
func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000) }
18961896

1897-
func BenchmarkPoolBatchLocalInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, true) }
1898-
func BenchmarkPoolBatchLocalInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, true) }
1899-
func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, true) }
1900-
1901-
func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
1897+
func benchmarkPoolBatchInsert(b *testing.B, size int) {
19021898
// Generate a batch of transactions to enqueue into the pool
19031899
pool, key := setupTxPool()
19041900
defer pool.Stop()
@@ -1916,10 +1912,6 @@ func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
19161912
// Benchmark importing the transactions into the queue
19171913
b.ResetTimer()
19181914
for _, batch := range batches {
1919-
if local {
1920-
pool.AddLocals(batch)
1921-
} else {
1922-
pool.AddRemotes(batch)
1923-
}
1915+
pool.AddRemotes(batch)
19241916
}
19251917
}

0 commit comments

Comments
 (0)