17
17
package txpool
18
18
19
19
import (
20
+ "container/heap"
20
21
"errors"
21
22
"fmt"
22
23
"math"
87
88
// than some meaningful limit a user might use. This is not a consensus error
88
89
// making the transaction invalid, rather a DOS protection.
89
90
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" )
90
99
)
91
100
92
101
var (
@@ -639,9 +648,25 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
639
648
}
640
649
// Transactor should have enough funds to cover the costs
641
650
// 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 {
643
653
return core .ErrInsufficientFunds
644
654
}
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
+
645
670
// Ensure the transaction has more gas than the basic tx fee.
646
671
intrGas , err := core .IntrinsicGas (tx .Data (), tx .AccessList (), tx .To () == nil , true , pool .istanbul , pool .shanghai )
647
672
if err != nil {
@@ -678,6 +703,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
678
703
invalidTxMeter .Mark (1 )
679
704
return false , err
680
705
}
706
+
707
+ // already validated by this point
708
+ from , _ := types .Sender (pool .signer , tx )
709
+
681
710
// If the transaction pool is full, discard underpriced transactions
682
711
if uint64 (pool .all .Slots ()+ numSlots (tx )) > pool .config .GlobalSlots + pool .config .GlobalQueue {
683
712
// 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
686
715
underpricedTxMeter .Mark (1 )
687
716
return false , ErrUnderpriced
688
717
}
718
+
689
719
// We're about to replace a transaction. The reorg does a more thorough
690
720
// analysis of what to remove and how, but it runs async. We don't want to
691
721
// 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
706
736
overflowedTxMeter .Mark (1 )
707
737
return false , ErrTxPoolOverflow
708
738
}
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
+
711
760
// Kick out the underpriced remote transactions.
712
761
for _ , tx := range drop {
713
762
log .Trace ("Discarding freshly underpriced transaction" , "hash" , tx .Hash (), "gasTipCap" , tx .GasTipCap (), "gasFeeCap" , tx .GasFeeCap ())
714
763
underpricedTxMeter .Mark (1 )
715
- pool .removeTx (tx .Hash (), false )
764
+ dropped := pool .removeTx (tx .Hash (), false )
765
+ pool .changesSinceReorg += dropped
716
766
}
717
767
}
768
+
718
769
// Try to replace an existing transaction in the pending pool
719
- from , _ := types .Sender (pool .signer , tx ) // already validated
720
770
if list := pool .pending [from ]; list != nil && list .Overlaps (tx ) {
721
771
// Nonce already pending, check if required price bump is met
722
772
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
760
810
return replaced , nil
761
811
}
762
812
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
+
763
827
// enqueueTx inserts a new transaction into the non-executable transaction queue.
764
828
//
765
829
// Note, this method assumes the pool lock is held!
@@ -996,11 +1060,12 @@ func (pool *TxPool) Has(hash common.Hash) bool {
996
1060
997
1061
// removeTx removes a single transaction from the queue, moving all subsequent
998
1062
// 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 {
1000
1065
// Fetch the transaction we wish to delete
1001
1066
tx := pool .all .Get (hash )
1002
1067
if tx == nil {
1003
- return
1068
+ return 0
1004
1069
}
1005
1070
addr , _ := types .Sender (pool .signer , tx ) // already validated during insertion
1006
1071
@@ -1028,7 +1093,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
1028
1093
pool .pendingNonces .setIfLower (addr , tx .Nonce ())
1029
1094
// Reduce the pending counter
1030
1095
pendingGauge .Dec (int64 (1 + len (invalids )))
1031
- return
1096
+ return 1 + len ( invalids )
1032
1097
}
1033
1098
}
1034
1099
// Transaction is in the future queue
@@ -1042,6 +1107,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
1042
1107
delete (pool .beats , addr )
1043
1108
}
1044
1109
}
1110
+ return 0
1045
1111
}
1046
1112
1047
1113
// requestReset requests a pool reset to the new head block.
0 commit comments