Skip to content

Commit ff697e8

Browse files
karalabefjl
authored andcommitted
[release/1.4.17] core, eth, internal, miner: optimize txpool for quick ops
(cherry picked from commit 0ef327b)
1 parent df64a9f commit ff697e8

File tree

13 files changed

+810
-450
lines changed

13 files changed

+810
-450
lines changed

core/tx_list.go

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package core
18+
19+
import (
20+
"container/heap"
21+
"math"
22+
"math/big"
23+
"sort"
24+
25+
"github.com/ethereum/go-ethereum/core/types"
26+
)
27+
28+
// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for
29+
// retrieving sorted transactions from the possibly gapped future queue.
30+
type nonceHeap []uint64
31+
32+
func (h nonceHeap) Len() int { return len(h) }
33+
func (h nonceHeap) Less(i, j int) bool { return h[i] < h[j] }
34+
func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
35+
36+
func (h *nonceHeap) Push(x interface{}) {
37+
*h = append(*h, x.(uint64))
38+
}
39+
40+
func (h *nonceHeap) Pop() interface{} {
41+
old := *h
42+
n := len(old)
43+
x := old[n-1]
44+
*h = old[0 : n-1]
45+
return x
46+
}
47+
48+
// txList is a "list" of transactions belonging to an account, sorted by account
49+
// nonce. The same type can be used both for storing contiguous transactions for
50+
// the executable/pending queue; and for storing gapped transactions for the non-
51+
// executable/future queue, with minor behavoiral changes.
52+
type txList struct {
53+
strict bool // Whether nonces are strictly continuous or not
54+
items map[uint64]*types.Transaction // Hash map storing the transaction data
55+
cache types.Transactions // cache of the transactions already sorted
56+
57+
first uint64 // Nonce of the lowest stored transaction (strict mode)
58+
last uint64 // Nonce of the highest stored transaction (strict mode)
59+
index *nonceHeap // Heap of nonces of all teh stored transactions (non-strict mode)
60+
61+
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
62+
}
63+
64+
// newTxList create a new transaction list for maintaining nonce-indexable fast,
65+
// gapped, sortable transaction lists.
66+
func newTxList(strict bool) *txList {
67+
return &txList{
68+
strict: strict,
69+
items: make(map[uint64]*types.Transaction),
70+
first: math.MaxUint64,
71+
index: &nonceHeap{},
72+
costcap: new(big.Int),
73+
}
74+
}
75+
76+
// Add tries to inserts a new transaction into the list, returning whether the
77+
// transaction was acceped, and if yes, any previous transaction it replaced.
78+
//
79+
// In case of strict lists (contiguous nonces) the nonce boundaries are updated
80+
// appropriately with the new transaction. Otherwise (gapped nonces) the heap of
81+
// nonces is expanded with the new transaction.
82+
func (l *txList) Add(tx *types.Transaction) (bool, *types.Transaction) {
83+
// If an existing transaction is better, discard new one
84+
nonce := tx.Nonce()
85+
86+
old, ok := l.items[nonce]
87+
if ok && old.GasPrice().Cmp(tx.GasPrice()) >= 0 {
88+
return false, nil
89+
}
90+
// Otherwise insert the transaction and replace any previous one
91+
l.items[nonce] = tx
92+
if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
93+
l.costcap = cost
94+
}
95+
if l.strict {
96+
// In strict mode, maintain the nonce sequence boundaries
97+
if nonce < l.first {
98+
l.first = nonce
99+
}
100+
if nonce > l.last {
101+
l.last = nonce
102+
}
103+
} else {
104+
// In gapped mode, maintain the nonce heap
105+
heap.Push(l.index, nonce)
106+
}
107+
l.cache = nil
108+
109+
return true, old
110+
}
111+
112+
// Forward removes all transactions from the list with a nonce lower than the
113+
// provided threshold. Every removed transaction is returned for any post-removal
114+
// maintenance.
115+
func (l *txList) Forward(threshold uint64) types.Transactions {
116+
var removed types.Transactions
117+
118+
if l.strict {
119+
// In strict mode, push the lowest nonce forward to the threshold
120+
for l.first < threshold {
121+
if tx, ok := l.items[l.first]; ok {
122+
removed = append(removed, tx)
123+
}
124+
delete(l.items, l.first)
125+
l.first++
126+
}
127+
if l.first > l.last {
128+
l.last = l.first
129+
}
130+
} else {
131+
// In gapped mode, pop off heap items until the threshold is reached
132+
for l.index.Len() > 0 && (*l.index)[0] < threshold {
133+
nonce := heap.Pop(l.index).(uint64)
134+
removed = append(removed, l.items[nonce])
135+
delete(l.items, nonce)
136+
}
137+
}
138+
l.cache = nil
139+
140+
return removed
141+
}
142+
143+
// Filter removes all transactions from the list with a cost higher than the
144+
// provided threshold. Every removed transaction is returned for any post-removal
145+
// maintenance. Strict-mode invalidated transactions are also returned.
146+
//
147+
// This method uses the cached costcap to quickly decide if there's even a point
148+
// in calculating all the costs or if the balance covers all. If the threshold is
149+
// loewr than the costcap, the costcap will be reset to a new high after removing
150+
// expensive the too transactions.
151+
func (l *txList) Filter(threshold *big.Int) (types.Transactions, types.Transactions) {
152+
// If all transactions are blow the threshold, short circuit
153+
if l.costcap.Cmp(threshold) <= 0 {
154+
return nil, nil
155+
}
156+
l.costcap = new(big.Int).Set(threshold) // Lower the cap to the threshold
157+
158+
// Gather all the transactions needing deletion
159+
var removed types.Transactions
160+
for _, tx := range l.items {
161+
if cost := tx.Cost(); cost.Cmp(threshold) > 0 {
162+
removed = append(removed, tx)
163+
delete(l.items, tx.Nonce())
164+
}
165+
}
166+
// Readjust the nonce boundaries/indexes and gather invalidate tranactions
167+
var invalids types.Transactions
168+
if l.strict {
169+
// In strict mode iterate find the first gap and invalidate everything after it
170+
for i := l.first; i <= l.last; i++ {
171+
if _, ok := l.items[i]; !ok {
172+
// Gap found, invalidate all subsequent transactions
173+
for j := i + 1; j <= l.last; j++ {
174+
if tx, ok := l.items[j]; ok {
175+
invalids = append(invalids, tx)
176+
delete(l.items, j)
177+
}
178+
}
179+
// Reduce the highest transaction nonce and return
180+
l.last = i - 1
181+
break
182+
}
183+
}
184+
} else {
185+
// In gapped mode no transactions are invalid, but the heap is ruined
186+
l.index = &nonceHeap{}
187+
for nonce, _ := range l.items {
188+
*l.index = append(*l.index, nonce)
189+
}
190+
heap.Init(l.index)
191+
}
192+
l.cache = nil
193+
194+
return removed, invalids
195+
}
196+
197+
// Cap places a hard limit on the number of items, returning all transactions
198+
// exceeding tht limit.
199+
func (l *txList) Cap(threshold int) types.Transactions {
200+
// Short circuit if the number of items is under the limit
201+
if len(l.items) < threshold {
202+
return nil
203+
}
204+
// Otherwise gather and drop the highest nonce'd transactions
205+
var drops types.Transactions
206+
207+
if l.strict {
208+
// In strict mode, just gather top down from last to first
209+
for len(l.items) > threshold {
210+
if tx, ok := l.items[l.last]; ok {
211+
drops = append(drops, tx)
212+
delete(l.items, l.last)
213+
l.last--
214+
}
215+
}
216+
} else {
217+
// In gapped mode it's expensive: we need to sort and drop like that
218+
sort.Sort(*l.index)
219+
for size := len(l.items); size > threshold; size-- {
220+
drops = append(drops, l.items[(*l.index)[size-1]])
221+
delete(l.items, (*l.index)[size-1])
222+
*l.index = (*l.index)[:size-1]
223+
}
224+
heap.Init(l.index)
225+
}
226+
l.cache = nil
227+
228+
return drops
229+
}
230+
231+
// Remove deletes a transaction from the maintained list, returning whether the
232+
// transaction was found, and also returning any transaction invalidated due to
233+
// the deletion (strict mode only).
234+
func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
235+
nonce := tx.Nonce()
236+
if _, ok := l.items[nonce]; ok {
237+
// Remove the item and invalidate the sorted cache
238+
delete(l.items, nonce)
239+
l.cache = nil
240+
241+
// Remove all invalidated transactions (strict mode only!)
242+
invalids := make(types.Transactions, 0, l.last-nonce)
243+
if l.strict {
244+
for i := nonce + 1; i <= l.last; i++ {
245+
invalids = append(invalids, l.items[i])
246+
delete(l.items, i)
247+
}
248+
l.last = nonce - 1
249+
} else {
250+
// In gapped mode, remove the nonce from the index but honour the heap
251+
for i := 0; i < l.index.Len(); i++ {
252+
if (*l.index)[i] == nonce {
253+
heap.Remove(l.index, i)
254+
break
255+
}
256+
}
257+
}
258+
// Figure out the new highest nonce
259+
return true, invalids
260+
}
261+
return false, nil
262+
}
263+
264+
// Ready retrieves a sequentially increasing list of transactions starting at the
265+
// provided nonce that is ready for processing. The returned transactions will be
266+
// removed from the list.
267+
//
268+
// Note, all transactions with nonces lower that start will also be returned to
269+
// prevent getting into and invalid state. This is not something that should ever
270+
// happen but better to be self correcting than failing!
271+
func (l *txList) Ready(start uint64) types.Transactions {
272+
var txs types.Transactions
273+
if l.strict {
274+
// In strict mode make sure we have valid transaction, return all contiguous
275+
if l.first > start {
276+
return nil
277+
}
278+
for {
279+
if tx, ok := l.items[l.first]; ok {
280+
txs = append(txs, tx)
281+
delete(l.items, l.first)
282+
l.first++
283+
continue
284+
}
285+
break
286+
}
287+
} else {
288+
// In gapped mode, check the heap start and return all contiguous
289+
if l.index.Len() == 0 || (*l.index)[0] > start {
290+
return nil
291+
}
292+
next := (*l.index)[0]
293+
for l.index.Len() > 0 && (*l.index)[0] == next {
294+
txs = append(txs, l.items[next])
295+
delete(l.items, next)
296+
heap.Pop(l.index)
297+
next++
298+
}
299+
}
300+
l.cache = nil
301+
302+
return txs
303+
}
304+
305+
// Len returns the length of the transaction list.
306+
func (l *txList) Len() int {
307+
return len(l.items)
308+
}
309+
310+
// Empty returns whether the list of transactions is empty or not.
311+
func (l *txList) Empty() bool {
312+
return len(l.items) == 0
313+
}
314+
315+
// Flatten creates a nonce-sorted slice of transactions based on the loosely
316+
// sorted internal representation. The result of the sorting is cached in case
317+
// it's requested again before any modifications are made to the contents.
318+
func (l *txList) Flatten() types.Transactions {
319+
// If the sorting was not cached yet, create and cache it
320+
if l.cache == nil {
321+
l.cache = make(types.Transactions, 0, len(l.items))
322+
for _, tx := range l.items {
323+
l.cache = append(l.cache, tx)
324+
}
325+
sort.Sort(types.TxByNonce(l.cache))
326+
}
327+
// Copy the cache to prevent accidental modifications
328+
txs := make(types.Transactions, len(l.cache))
329+
copy(txs, l.cache)
330+
return txs
331+
}

core/tx_list_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package core
18+
19+
import (
20+
"math/big"
21+
"math/rand"
22+
"testing"
23+
24+
"github.com/ethereum/go-ethereum/core/types"
25+
"github.com/ethereum/go-ethereum/crypto"
26+
)
27+
28+
// Tests that transactions can be added to strict lists and list contents and
29+
// nonce boundaries are correctly maintained.
30+
func TestStrictTxListAdd(t *testing.T) {
31+
// Generate a list of transactions to insert
32+
key, _ := crypto.GenerateKey()
33+
34+
txs := make(types.Transactions, 1024)
35+
for i := 0; i < len(txs); i++ {
36+
txs[i] = transaction(uint64(i), new(big.Int), key)
37+
}
38+
// Insert the transactions in a random order
39+
list := newTxList(true)
40+
for _, v := range rand.Perm(len(txs)) {
41+
list.Add(txs[v])
42+
}
43+
// Verify internal state
44+
if list.first != 0 {
45+
t.Errorf("lowest nonce mismatch: have %d, want %d", list.first, 0)
46+
}
47+
if int(list.last) != len(txs)-1 {
48+
t.Errorf("highest nonce mismatch: have %d, want %d", list.last, len(txs)-1)
49+
}
50+
if len(list.items) != len(txs) {
51+
t.Errorf("transaction count mismatch: have %d, want %d", len(list.items), len(txs))
52+
}
53+
for i, tx := range txs {
54+
if list.items[tx.Nonce()] != tx {
55+
t.Errorf("item %d: transaction mismatch: have %v, want %v", i, list.items[tx.Nonce()], tx)
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)