Skip to content

Commit d7721de

Browse files
karalabefjl
authored andcommitted
[release/1.4.17] core: add upper bound on the queued transctions
(cherry picked from commit a183ea2)
1 parent ddcf02b commit d7721de

File tree

3 files changed

+203
-33
lines changed

3 files changed

+203
-33
lines changed

core/tx_list.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ func (h *nonceHeap) Pop() interface{} {
5252
type txList struct {
5353
strict bool // Whether nonces are strictly continuous or not
5454
items map[uint64]*types.Transaction // Hash map storing the transaction data
55-
cache types.Transactions // cache of the transactions already sorted
55+
cache types.Transactions // Cache of the transactions already sorted
5656

5757
first uint64 // Nonce of the lowest stored transaction (strict mode)
5858
last uint64 // Nonce of the highest stored transaction (strict mode)
59-
index *nonceHeap // Heap of nonces of all teh stored transactions (non-strict mode)
59+
index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode)
6060

6161
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
6262
}
@@ -73,8 +73,8 @@ func newTxList(strict bool) *txList {
7373
}
7474
}
7575

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.
76+
// Add tries to insert a new transaction into the list, returning whether the
77+
// transaction was accepted, and if yes, any previous transaction it replaced.
7878
//
7979
// In case of strict lists (contiguous nonces) the nonce boundaries are updated
8080
// appropriately with the new transaction. Otherwise (gapped nonces) the heap of
@@ -146,10 +146,10 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
146146
//
147147
// This method uses the cached costcap to quickly decide if there's even a point
148148
// 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
149+
// lower than the costcap, the costcap will be reset to a new high after removing
150150
// expensive the too transactions.
151151
func (l *txList) Filter(threshold *big.Int) (types.Transactions, types.Transactions) {
152-
// If all transactions are blow the threshold, short circuit
152+
// If all transactions are below the threshold, short circuit
153153
if l.costcap.Cmp(threshold) <= 0 {
154154
return nil, nil
155155
}
@@ -195,7 +195,7 @@ func (l *txList) Filter(threshold *big.Int) (types.Transactions, types.Transacti
195195
}
196196

197197
// Cap places a hard limit on the number of items, returning all transactions
198-
// exceeding tht limit.
198+
// exceeding that limit.
199199
func (l *txList) Cap(threshold int) types.Transactions {
200200
// Short circuit if the number of items is under the limit
201201
if len(l.items) < threshold {
@@ -239,8 +239,9 @@ func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
239239
l.cache = nil
240240

241241
// Remove all invalidated transactions (strict mode only!)
242-
invalids := make(types.Transactions, 0, l.last-nonce)
242+
var invalids types.Transactions
243243
if l.strict {
244+
invalids = make(types.Transactions, 0, l.last-nonce)
244245
for i := nonce + 1; i <= l.last; i++ {
245246
invalids = append(invalids, l.items[i])
246247
delete(l.items, i)
@@ -255,7 +256,6 @@ func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
255256
}
256257
}
257258
}
258-
// Figure out the new highest nonce
259259
return true, invalids
260260
}
261261
return false, nil
@@ -265,7 +265,7 @@ func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
265265
// provided nonce that is ready for processing. The returned transactions will be
266266
// removed from the list.
267267
//
268-
// Note, all transactions with nonces lower that start will also be returned to
268+
// Note, all transactions with nonces lower than start will also be returned to
269269
// prevent getting into and invalid state. This is not something that should ever
270270
// happen but better to be self correcting than failing!
271271
func (l *txList) Ready(start uint64) types.Transactions {

core/tx_pool.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"math/big"
23+
"sort"
2324
"sync"
2425
"time"
2526

@@ -44,8 +45,11 @@ var (
4445
ErrNegativeValue = errors.New("Negative value")
4546
)
4647

47-
const (
48-
maxQueued = 64 // max limit of queued txs per address
48+
var (
49+
maxQueuedPerAccount = uint64(64) // Max limit of queued transactions per address
50+
maxQueuedInTotal = uint64(65536) // Max limit of queued transactions from all accounts
51+
maxQueuedLifetime = 3 * time.Hour // Max amount of time transactions from idle accounts are queued
52+
evictionInterval = time.Minute // Time interval to check for evictable transactions
4953
)
5054

5155
type stateFn func() (*state.StateDB, error)
@@ -71,8 +75,10 @@ type TxPool struct {
7175
pending map[common.Address]*txList // All currently processable transactions
7276
queue map[common.Address]*txList // Queued but non-processable transactions
7377
all map[common.Hash]*types.Transaction // All transactions to allow lookups
78+
beats map[common.Address]time.Time // Last heartbeat from each known account
7479

75-
wg sync.WaitGroup // for shutdown sync
80+
wg sync.WaitGroup // for shutdown sync
81+
quit chan struct{}
7682

7783
homestead bool
7884
}
@@ -83,17 +89,20 @@ func NewTxPool(config *ChainConfig, eventMux *event.TypeMux, currentStateFn stat
8389
pending: make(map[common.Address]*txList),
8490
queue: make(map[common.Address]*txList),
8591
all: make(map[common.Hash]*types.Transaction),
92+
beats: make(map[common.Address]time.Time),
8693
eventMux: eventMux,
8794
currentState: currentStateFn,
8895
gasLimit: gasLimitFn,
8996
minGasPrice: new(big.Int),
9097
pendingState: nil,
9198
localTx: newTxSet(),
9299
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
100+
quit: make(chan struct{}),
93101
}
94102

95-
pool.wg.Add(1)
103+
pool.wg.Add(2)
96104
go pool.eventLoop()
105+
go pool.expirationLoop()
97106

98107
return pool
99108
}
@@ -154,6 +163,7 @@ func (pool *TxPool) resetState() {
154163

155164
func (pool *TxPool) Stop() {
156165
pool.events.Unsubscribe()
166+
close(pool.quit)
157167
pool.wg.Wait()
158168
glog.V(logger.Info).Infoln("Transaction pool stopped")
159169
}
@@ -290,7 +300,7 @@ func (pool *TxPool) add(tx *types.Transaction) error {
290300
if pool.all[hash] != nil {
291301
return fmt.Errorf("Known transaction: %x", hash[:4])
292302
}
293-
// Otherwise ensure basic validation passes nd queue it up
303+
// Otherwise ensure basic validation passes and queue it up
294304
if err := pool.validateTx(tx); err != nil {
295305
return err
296306
}
@@ -308,7 +318,7 @@ func (pool *TxPool) add(tx *types.Transaction) error {
308318
return nil
309319
}
310320

311-
// enqueueTx inserts a new transction into the non-executable transaction queue.
321+
// enqueueTx inserts a new transaction into the non-executable transaction queue.
312322
//
313323
// Note, this method assumes the pool lock is held!
314324
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) {
@@ -355,6 +365,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
355365
pool.all[hash] = tx // Failsafe to work around direct pending inserts (tests)
356366

357367
// Set the potentially new pending nonce and notify any subsystems of the new tx
368+
pool.beats[addr] = time.Now()
358369
pool.pendingState.SetNonce(addr, list.last+1)
359370
go pool.eventMux.Post(TxPreEvent{tx})
360371
}
@@ -412,8 +423,8 @@ func (pool *TxPool) RemoveBatch(txs types.Transactions) {
412423
}
413424
}
414425

415-
// removeTx iterates removes a single transaction from the queue, moving all
416-
// subsequent transactions back to the future queue.
426+
// removeTx removes a single transaction from the queue, moving all subsequent
427+
// transactions back to the future queue.
417428
func (pool *TxPool) removeTx(hash common.Hash) {
418429
// Fetch the transaction we wish to delete
419430
tx, ok := pool.all[hash]
@@ -431,6 +442,8 @@ func (pool *TxPool) removeTx(hash common.Hash) {
431442
// If no more transactions are left, remove the list and reset the nonce
432443
if pending.Empty() {
433444
delete(pool.pending, addr)
445+
delete(pool.beats, addr)
446+
434447
pool.pendingState.SetNonce(addr, tx.Nonce())
435448
} else {
436449
// Otherwise update the nonce and postpone any invalidated transactions
@@ -465,6 +478,8 @@ func (pool *TxPool) promoteExecutables() {
465478
return
466479
}
467480
// Iterate over all accounts and promote any executable transactions
481+
queued := uint64(0)
482+
468483
for addr, list := range pool.queue {
469484
// Drop all transactions that are deemed too old (low nonce)
470485
for _, tx := range list.Forward(state.GetNonce(addr)) {
@@ -489,17 +504,51 @@ func (pool *TxPool) promoteExecutables() {
489504
pool.promoteTx(addr, tx.Hash(), tx)
490505
}
491506
// Drop all transactions over the allowed limit
492-
for _, tx := range list.Cap(maxQueued) {
507+
for _, tx := range list.Cap(int(maxQueuedPerAccount)) {
493508
if glog.V(logger.Core) {
494509
glog.Infof("Removed cap-exceeding queued transaction: %v", tx)
495510
}
496511
delete(pool.all, tx.Hash())
497512
}
513+
queued += uint64(list.Len())
514+
498515
// Delete the entire queue entry if it became empty.
499516
if list.Empty() {
500517
delete(pool.queue, addr)
501518
}
502519
}
520+
// If we've queued more transactions than the hard limit, drop oldest ones
521+
if queued > maxQueuedInTotal {
522+
// Sort all accounts with queued transactions by heartbeat
523+
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
524+
for addr, _ := range pool.queue {
525+
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
526+
}
527+
sort.Sort(addresses)
528+
529+
// Drop transactions until the total is below the limit
530+
for drop := queued - maxQueuedInTotal; drop > 0; {
531+
addr := addresses[len(addresses)-1]
532+
list := pool.queue[addr.address]
533+
534+
addresses = addresses[:len(addresses)-1]
535+
536+
// Drop all transactions if they are less than the overflow
537+
if size := uint64(list.Len()); size <= drop {
538+
for _, tx := range list.Flatten() {
539+
pool.removeTx(tx.Hash())
540+
}
541+
drop -= size
542+
continue
543+
}
544+
// Otherwise drop only last few transactions
545+
txs := list.Flatten()
546+
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
547+
pool.removeTx(txs[i].Hash())
548+
drop--
549+
}
550+
}
551+
}
503552
}
504553

505554
// demoteUnexecutables removes invalid and processed transactions from the pools
@@ -540,10 +589,51 @@ func (pool *TxPool) demoteUnexecutables() {
540589
// Delete the entire queue entry if it became empty.
541590
if list.Empty() {
542591
delete(pool.pending, addr)
592+
delete(pool.beats, addr)
543593
}
544594
}
545595
}
546596

597+
// expirationLoop is a loop that periodically iterates over all accounts with
598+
// queued transactions and drop all that have been inactive for a prolonged amount
599+
// of time.
600+
func (pool *TxPool) expirationLoop() {
601+
defer pool.wg.Done()
602+
603+
evict := time.NewTicker(evictionInterval)
604+
defer evict.Stop()
605+
606+
for {
607+
select {
608+
case <-evict.C:
609+
pool.mu.Lock()
610+
for addr := range pool.queue {
611+
if time.Since(pool.beats[addr]) > maxQueuedLifetime {
612+
for _, tx := range pool.queue[addr].Flatten() {
613+
pool.removeTx(tx.Hash())
614+
}
615+
}
616+
}
617+
pool.mu.Unlock()
618+
619+
case <-pool.quit:
620+
return
621+
}
622+
}
623+
}
624+
625+
// addressByHeartbeat is an account address tagged with its last activity timestamp.
626+
type addressByHeartbeat struct {
627+
address common.Address
628+
heartbeat time.Time
629+
}
630+
631+
type addresssByHeartbeat []addressByHeartbeat
632+
633+
func (a addresssByHeartbeat) Len() int { return len(a) }
634+
func (a addresssByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
635+
func (a addresssByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
636+
547637
// txSet represents a set of transaction hashes in which entries
548638
// are automatically dropped after txSetDuration time
549639
type txSet struct {

0 commit comments

Comments
 (0)