1717package txpool
1818
1919import (
20+ "container/heap"
2021 "errors"
2122 "fmt"
2223 "math"
8788 // than some meaningful limit a user might use. This is not a consensus error
8889 // making the transaction invalid, rather a DOS protection.
8990 ErrOversizedData = errors .New ("oversized data" )
91+
92+ // ErrFutureReplacePending is returned if a future transaction replaces a pending
93+ // transaction. Future transactions should only be able to replace other future transactions.
94+ ErrFutureReplacePending = errors .New ("future transaction tries to replace pending" )
95+
96+ // ErrOverdraft is returned if a transaction would cause the senders balance to go negative
97+ // thus invalidating a potential large number of transactions.
98+ ErrOverdraft = errors .New ("transaction would cause overdraft" )
9099)
91100
92101var (
@@ -639,9 +648,25 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
639648 }
640649 // Transactor should have enough funds to cover the costs
641650 // cost == V + GP * GL
642- if pool .currentState .GetBalance (from ).Cmp (tx .Cost ()) < 0 {
651+ balance := pool .currentState .GetBalance (from )
652+ if balance .Cmp (tx .Cost ()) < 0 {
643653 return core .ErrInsufficientFunds
644654 }
655+
656+ // Verify that replacing transactions will not result in overdraft
657+ list := pool .pending [from ]
658+ if list != nil { // Sender already has pending txs
659+ sum := new (big.Int ).Add (tx .Cost (), list .totalcost )
660+ if repl := list .txs .Get (tx .Nonce ()); repl != nil {
661+ // Deduct the cost of a transaction replaced by this
662+ sum .Sub (sum , repl .Cost ())
663+ }
664+ if balance .Cmp (sum ) < 0 {
665+ log .Trace ("Replacing transactions would overdraft" , "sender" , from , "balance" , pool .currentState .GetBalance (from ), "required" , sum )
666+ return ErrOverdraft
667+ }
668+ }
669+
645670 // Ensure the transaction has more gas than the basic tx fee.
646671 intrGas , err := core .IntrinsicGas (tx .Data (), tx .AccessList (), tx .To () == nil , true , pool .istanbul , pool .shanghai )
647672 if err != nil {
@@ -678,6 +703,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
678703 invalidTxMeter .Mark (1 )
679704 return false , err
680705 }
706+
707+ // already validated by this point
708+ from , _ := types .Sender (pool .signer , tx )
709+
681710 // If the transaction pool is full, discard underpriced transactions
682711 if uint64 (pool .all .Slots ()+ numSlots (tx )) > pool .config .GlobalSlots + pool .config .GlobalQueue {
683712 // If the new transaction is underpriced, don't accept it
@@ -686,6 +715,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
686715 underpricedTxMeter .Mark (1 )
687716 return false , ErrUnderpriced
688717 }
718+
689719 // We're about to replace a transaction. The reorg does a more thorough
690720 // analysis of what to remove and how, but it runs async. We don't want to
691721 // do too many replacements between reorg-runs, so we cap the number of
@@ -706,17 +736,37 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
706736 overflowedTxMeter .Mark (1 )
707737 return false , ErrTxPoolOverflow
708738 }
709- // Bump the counter of rejections-since-reorg
710- pool .changesSinceReorg += len (drop )
739+
740+ // If the new transaction is a future transaction it should never churn pending transactions
741+ if pool .isFuture (from , tx ) {
742+ var replacesPending bool
743+ for _ , dropTx := range drop {
744+ dropSender , _ := types .Sender (pool .signer , dropTx )
745+ if list := pool .pending [dropSender ]; list != nil && list .Overlaps (dropTx ) {
746+ replacesPending = true
747+ break
748+ }
749+ }
750+ // Add all transactions back to the priced queue
751+ if replacesPending {
752+ for _ , dropTx := range drop {
753+ heap .Push (& pool .priced .urgent , dropTx )
754+ }
755+ log .Trace ("Discarding future transaction replacing pending tx" , "hash" , hash )
756+ return false , ErrFutureReplacePending
757+ }
758+ }
759+
711760 // Kick out the underpriced remote transactions.
712761 for _ , tx := range drop {
713762 log .Trace ("Discarding freshly underpriced transaction" , "hash" , tx .Hash (), "gasTipCap" , tx .GasTipCap (), "gasFeeCap" , tx .GasFeeCap ())
714763 underpricedTxMeter .Mark (1 )
715- pool .removeTx (tx .Hash (), false )
764+ dropped := pool .removeTx (tx .Hash (), false )
765+ pool .changesSinceReorg += dropped
716766 }
717767 }
768+
718769 // Try to replace an existing transaction in the pending pool
719- from , _ := types .Sender (pool .signer , tx ) // already validated
720770 if list := pool .pending [from ]; list != nil && list .Overlaps (tx ) {
721771 // Nonce already pending, check if required price bump is met
722772 inserted , old := list .Add (tx , pool .config .PriceBump )
@@ -760,6 +810,20 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
760810 return replaced , nil
761811}
762812
813+ // isFuture reports whether the given transaction is immediately executable.
814+ func (pool * TxPool ) isFuture (from common.Address , tx * types.Transaction ) bool {
815+ list := pool .pending [from ]
816+ if list == nil {
817+ return pool .pendingNonces .get (from ) != tx .Nonce ()
818+ }
819+ // Sender has pending transactions.
820+ if old := list .txs .Get (tx .Nonce ()); old != nil {
821+ return false // It replaces a pending transaction.
822+ }
823+ // Not replacing, check if parent nonce exists in pending.
824+ return list .txs .Get (tx .Nonce ()- 1 ) == nil
825+ }
826+
763827// enqueueTx inserts a new transaction into the non-executable transaction queue.
764828//
765829// Note, this method assumes the pool lock is held!
@@ -996,11 +1060,12 @@ func (pool *TxPool) Has(hash common.Hash) bool {
9961060
9971061// removeTx removes a single transaction from the queue, moving all subsequent
9981062// transactions back to the future queue.
999- func (pool * TxPool ) removeTx (hash common.Hash , outofbound bool ) {
1063+ // Returns the number of transactions removed from the pending queue.
1064+ func (pool * TxPool ) removeTx (hash common.Hash , outofbound bool ) int {
10001065 // Fetch the transaction we wish to delete
10011066 tx := pool .all .Get (hash )
10021067 if tx == nil {
1003- return
1068+ return 0
10041069 }
10051070 addr , _ := types .Sender (pool .signer , tx ) // already validated during insertion
10061071
@@ -1028,7 +1093,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
10281093 pool .pendingNonces .setIfLower (addr , tx .Nonce ())
10291094 // Reduce the pending counter
10301095 pendingGauge .Dec (int64 (1 + len (invalids )))
1031- return
1096+ return 1 + len ( invalids )
10321097 }
10331098 }
10341099 // Transaction is in the future queue
@@ -1042,6 +1107,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
10421107 delete (pool .beats , addr )
10431108 }
10441109 }
1110+ return 0
10451111}
10461112
10471113// requestReset requests a pool reset to the new head block.
0 commit comments