@@ -104,6 +104,7 @@ var (
104104 pendingReplaceMeter = metrics .NewRegisteredMeter ("txpool/pending/replace" , nil )
105105 pendingRateLimitMeter = metrics .NewRegisteredMeter ("txpool/pending/ratelimit" , nil ) // Dropped due to rate limiting
106106 pendingNofundsMeter = metrics .NewRegisteredMeter ("txpool/pending/nofunds" , nil ) // Dropped due to out-of-funds
107+ pendingEvictionMeter = metrics .NewRegisteredMeter ("txpool/pending/eviction" , nil ) // Dropped due to lifetime
107108
108109 // Metrics for the queued pool
109110 queuedDiscardMeter = metrics .NewRegisteredMeter ("txpool/queued/discard" , nil )
@@ -178,6 +179,8 @@ type TxPoolConfig struct {
178179 AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
179180 GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
180181
182+ AccountPendingLimit uint64 // Number of executable transactions allowed per account
183+
181184 Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
182185}
183186
@@ -195,6 +198,8 @@ var DefaultTxPoolConfig = TxPoolConfig{
195198 AccountQueue : 64 ,
196199 GlobalQueue : 1024 ,
197200
201+ AccountPendingLimit : 1024 ,
202+
198203 Lifetime : 3 * time .Hour ,
199204}
200205
@@ -230,6 +235,10 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
230235 log .Warn ("Sanitizing invalid txpool global queue" , "provided" , conf .GlobalQueue , "updated" , DefaultTxPoolConfig .GlobalQueue )
231236 conf .GlobalQueue = DefaultTxPoolConfig .GlobalQueue
232237 }
238+ if conf .AccountPendingLimit < 1 {
239+ log .Warn ("Sanitizing invalid txpool account pending limit" , "provided" , conf .AccountPendingLimit , "updated" , DefaultTxPoolConfig .AccountPendingLimit )
240+ conf .AccountPendingLimit = DefaultTxPoolConfig .AccountPendingLimit
241+ }
233242 if conf .Lifetime < 1 {
234243 log .Warn ("Sanitizing invalid txpool lifetime" , "provided" , conf .Lifetime , "updated" , DefaultTxPoolConfig .Lifetime )
235244 conf .Lifetime = DefaultTxPoolConfig .Lifetime
@@ -422,6 +431,7 @@ func (pool *TxPool) loop() {
422431 // Handle inactive account transaction eviction
423432 case <- evict .C :
424433 pool .mu .Lock ()
434+ // Evict queued transactions
425435 for addr := range pool .queue {
426436 // Skip local transactions from the eviction mechanism
427437 if pool .locals .contains (addr ) {
@@ -431,12 +441,28 @@ func (pool *TxPool) loop() {
431441 if time .Since (pool .beats [addr ]) > pool .config .Lifetime {
432442 list := pool .queue [addr ].Flatten ()
433443 for _ , tx := range list {
434- log .Trace ("Evicting transaction due to timeout" , "account" , addr .Hex (), "hash" , tx .Hash ().Hex (), "lifetime sec" , time .Since (pool .beats [addr ]).Seconds (), "lifetime limit sec" , pool .config .Lifetime .Seconds ())
444+ log .Trace ("Evicting queued transaction due to timeout" , "account" , addr .Hex (), "hash" , tx .Hash ().Hex (), "lifetime sec" , time .Since (pool .beats [addr ]).Seconds (), "lifetime limit sec" , pool .config .Lifetime .Seconds ())
435445 pool .removeTx (tx .Hash (), true )
436446 }
437447 queuedEvictionMeter .Mark (int64 (len (list )))
438448 }
439449 }
450+ // Evict pending transactions
451+ for addr := range pool .pending {
452+ // Skip local transactions from the eviction mechanism
453+ if pool .locals .contains (addr ) {
454+ continue
455+ }
456+ // Any non-locals old enough should be removed
457+ if time .Since (pool .beats [addr ]) > pool .config .Lifetime {
458+ list := pool .pending [addr ].Flatten ()
459+ for _ , tx := range list {
460+ log .Trace ("Evicting pending transaction due to timeout" , "account" , addr .Hex (), "hash" , tx .Hash ().Hex (), "lifetime sec" , time .Since (pool .beats [addr ]).Seconds (), "lifetime limit sec" , pool .config .Lifetime .Seconds ())
461+ pool .removeTx (tx .Hash (), true )
462+ }
463+ pendingEvictionMeter .Mark (int64 (len (list )))
464+ }
465+ }
440466 pool .mu .Unlock ()
441467
442468 // Handle local transaction journal rotation
@@ -957,6 +983,15 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
957983 }
958984 list := pool .pending [addr ]
959985
986+ // Account pending list is full
987+ if uint64 (list .Len ()) >= pool .config .AccountPendingLimit {
988+ pool .all .Remove (hash )
989+ pool .calculateTxsLifecycle (types.Transactions {tx }, time .Now ())
990+ pool .priced .Removed (1 )
991+ pendingDiscardMeter .Mark (1 )
992+ return false
993+ }
994+
960995 inserted , old := list .Add (tx , pool .currentState , pool .config .PriceBump , pool .chainconfig , pool .currentHead )
961996 if ! inserted {
962997 // An older transaction was better, discard this
@@ -1153,6 +1188,12 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
11531188
11541189 addr , _ := types .Sender (pool .signer , tx ) // already validated during insertion
11551190
1191+ defer func (addr common.Address ) {
1192+ if pool .queue [addr ].Empty () && pool .pending [addr ].Empty () {
1193+ delete (pool .beats , addr )
1194+ }
1195+ }(addr )
1196+
11561197 // Remove it from the list of known transactions
11571198 pool .all .Remove (hash )
11581199 pool .calculateTxsLifecycle (types.Transactions {tx }, time .Now ())
@@ -1189,7 +1230,6 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
11891230 }
11901231 if future .Empty () {
11911232 delete (pool .queue , addr )
1192- delete (pool .beats , addr )
11931233 }
11941234 }
11951235}
@@ -1544,6 +1584,8 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
15441584 // Delete the entire queue entry if it became empty.
15451585 if list .Empty () {
15461586 delete (pool .queue , addr )
1587+ }
1588+ if pool .queue [addr ].Empty () && pool .pending [addr ].Empty () {
15471589 delete (pool .beats , addr )
15481590 }
15491591 }
@@ -1574,6 +1616,29 @@ func (pool *TxPool) executableTxFilter(costLimit *big.Int) func(tx *types.Transa
15741616// pending limit. The algorithm tries to reduce transaction counts by an approximately
15751617// equal number for all for accounts with many pending transactions.
15761618func (pool * TxPool ) truncatePending () {
1619+ // Truncate pending lists to max length
1620+ for addr , list := range pool .pending {
1621+ if list .Len () > int (pool .config .AccountPendingLimit ) {
1622+ caps := list .Cap (int (pool .config .AccountPendingLimit ))
1623+ for _ , tx := range caps {
1624+ // Drop the transaction from the global pools too
1625+ hash := tx .Hash ()
1626+ pool .all .Remove (hash )
1627+ pool .calculateTxsLifecycle (types.Transactions {tx }, time .Now ())
1628+
1629+ // Update the account nonce to the dropped transaction
1630+ // note: this will set pending nonce to the min nonce from the discarded txs
1631+ pool .pendingNonces .setIfLower (addr , tx .Nonce ())
1632+ log .Trace ("Removed pending transaction to comply with hard limit" , "hash" , hash .Hex ())
1633+ }
1634+ pool .priced .Removed (len (caps ))
1635+ pendingGauge .Dec (int64 (len (caps )))
1636+ if pool .locals .contains (addr ) {
1637+ localGauge .Dec (int64 (len (caps )))
1638+ }
1639+ }
1640+ }
1641+
15771642 pending := uint64 (0 )
15781643 for _ , list := range pool .pending {
15791644 pending += uint64 (list .Len ())
0 commit comments