all: EIP-1559 tx pool support (#22898)
This pull request implements EIP-1559 compatible transaction pool with dual heap eviction ordering. It is based on #22791 The eviction ordering scheme and the reasoning behind it is described here: https://gist.github.com/zsfelfoldi/9607ad248707a925b701f49787904fd6
This commit is contained in:
parent
ee35ddc8fd
commit
966ee3ae6d
202
core/tx_list.go
202
core/tx_list.go
@ -21,6 +21,7 @@ import (
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@ -279,15 +280,23 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
|
||||
// If there's an older better transaction, abort
|
||||
old := l.txs.Get(tx.Nonce())
|
||||
if old != nil {
|
||||
// threshold = oldGP * (100 + priceBump) / 100
|
||||
if old.FeeCapCmp(tx) >= 0 || old.TipCmp(tx) >= 0 {
|
||||
return false, nil
|
||||
}
|
||||
// thresholdFeeCap = oldFC * (100 + priceBump) / 100
|
||||
a := big.NewInt(100 + int64(priceBump))
|
||||
a = a.Mul(a, old.GasPrice())
|
||||
aFeeCap := new(big.Int).Mul(a, old.FeeCap())
|
||||
aTip := a.Mul(a, old.Tip())
|
||||
|
||||
// thresholdTip = oldTip * (100 + priceBump) / 100
|
||||
b := big.NewInt(100)
|
||||
threshold := a.Div(a, b)
|
||||
// Have to ensure that the new gas price is higher than the old gas
|
||||
// price as well as checking the percentage threshold to ensure that
|
||||
thresholdFeeCap := aFeeCap.Div(aFeeCap, b)
|
||||
thresholdTip := aTip.Div(aTip, b)
|
||||
|
||||
// Have to ensure that either the new fee cap or tip is higher than the
|
||||
// old ones as well as checking the percentage threshold to ensure that
|
||||
// this is accurate for low (Wei-level) gas price replacements
|
||||
if old.GasPriceCmp(tx) >= 0 || tx.GasPriceIntCmp(threshold) < 0 {
|
||||
if tx.FeeCapIntCmp(thresholdFeeCap) < 0 || tx.TipIntCmp(thresholdTip) < 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
@ -406,34 +415,54 @@ func (l *txList) LastElement() *types.Transaction {
|
||||
}
|
||||
|
||||
// priceHeap is a heap.Interface implementation over transactions for retrieving
|
||||
// price-sorted transactions to discard when the pool fills up.
|
||||
type priceHeap []*types.Transaction
|
||||
// price-sorted transactions to discard when the pool fills up. If baseFee is set
|
||||
// then the heap is sorted based on the effective tip based on the given base fee.
|
||||
// If baseFee is nil then the sorting is based on feeCap.
|
||||
type priceHeap struct {
|
||||
baseFee *big.Int // heap should always be re-sorted after baseFee is changed
|
||||
list []*types.Transaction
|
||||
}
|
||||
|
||||
func (h priceHeap) Len() int { return len(h) }
|
||||
func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h *priceHeap) Len() int { return len(h.list) }
|
||||
func (h *priceHeap) Swap(i, j int) { h.list[i], h.list[j] = h.list[j], h.list[i] }
|
||||
|
||||
func (h priceHeap) Less(i, j int) bool {
|
||||
// Sort primarily by price, returning the cheaper one
|
||||
switch h[i].GasPriceCmp(h[j]) {
|
||||
func (h *priceHeap) Less(i, j int) bool {
|
||||
switch h.cmp(h.list[i], h.list[j]) {
|
||||
case -1:
|
||||
return true
|
||||
case 1:
|
||||
return false
|
||||
default:
|
||||
return h.list[i].Nonce() > h.list[j].Nonce()
|
||||
}
|
||||
// If the prices match, stabilize via nonces (high nonce is worse)
|
||||
return h[i].Nonce() > h[j].Nonce()
|
||||
}
|
||||
|
||||
func (h *priceHeap) cmp(a, b *types.Transaction) int {
|
||||
if h.baseFee != nil {
|
||||
// Compare effective tips if baseFee is specified
|
||||
if c := a.EffectiveTipCmp(b, h.baseFee); c != 0 {
|
||||
return c
|
||||
}
|
||||
}
|
||||
// Compare fee caps if baseFee is not specified or effective tips are equal
|
||||
if c := a.FeeCapCmp(b); c != 0 {
|
||||
return c
|
||||
}
|
||||
// Compare tips if effective tips and fee caps are equal
|
||||
return a.TipCmp(b)
|
||||
}
|
||||
|
||||
func (h *priceHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(*types.Transaction))
|
||||
tx := x.(*types.Transaction)
|
||||
h.list = append(h.list, tx)
|
||||
}
|
||||
|
||||
func (h *priceHeap) Pop() interface{} {
|
||||
old := *h
|
||||
old := h.list
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
old[n-1] = nil
|
||||
*h = old[0 : n-1]
|
||||
h.list = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
@ -441,17 +470,29 @@ func (h *priceHeap) Pop() interface{} {
|
||||
// contents in a price-incrementing way. It's built opon the all transactions
|
||||
// in txpool but only interested in the remote part. It means only remote transactions
|
||||
// will be considered for tracking, sorting, eviction, etc.
|
||||
//
|
||||
// Two heaps are used for sorting: the urgent heap (based on effective tip in the next
|
||||
// block) and the floating heap (based on feeCap). Always the bigger heap is chosen for
|
||||
// eviction. Transactions evicted from the urgent heap are first demoted into the floating heap.
|
||||
// In some cases (during a congestion, when blocks are full) the urgent heap can provide
|
||||
// better candidates for inclusion while in other cases (at the top of the baseFee peak)
|
||||
// the floating heap is better. When baseFee is decreasing they behave similarly.
|
||||
type txPricedList struct {
|
||||
all *txLookup // Pointer to the map of all transactions
|
||||
remotes *priceHeap // Heap of prices of all the stored **remote** transactions
|
||||
stales int // Number of stale price points to (re-heap trigger)
|
||||
all *txLookup // Pointer to the map of all transactions
|
||||
urgent, floating priceHeap // Heaps of prices of all the stored **remote** transactions
|
||||
stales int // Number of stale price points to (re-heap trigger)
|
||||
}
|
||||
|
||||
const (
|
||||
// urgentRatio : floatingRatio is the capacity ratio of the two queues
|
||||
urgentRatio = 4
|
||||
floatingRatio = 1
|
||||
)
|
||||
|
||||
// newTxPricedList creates a new price-sorted transaction heap.
|
||||
func newTxPricedList(all *txLookup) *txPricedList {
|
||||
return &txPricedList{
|
||||
all: all,
|
||||
remotes: new(priceHeap),
|
||||
all: all,
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,7 +501,8 @@ func (l *txPricedList) Put(tx *types.Transaction, local bool) {
|
||||
if local {
|
||||
return
|
||||
}
|
||||
heap.Push(l.remotes, tx)
|
||||
// Insert every new transaction to the urgent heap first; Discard will balance the heaps
|
||||
heap.Push(&l.urgent, tx)
|
||||
}
|
||||
|
||||
// Removed notifies the prices transaction list that an old transaction dropped
|
||||
@ -469,58 +511,43 @@ func (l *txPricedList) Put(tx *types.Transaction, local bool) {
|
||||
func (l *txPricedList) Removed(count int) {
|
||||
// Bump the stale counter, but exit if still too low (< 25%)
|
||||
l.stales += count
|
||||
if l.stales <= len(*l.remotes)/4 {
|
||||
if l.stales <= (len(l.urgent.list)+len(l.floating.list))/4 {
|
||||
return
|
||||
}
|
||||
// Seems we've reached a critical number of stale transactions, reheap
|
||||
l.Reheap()
|
||||
}
|
||||
|
||||
// Cap finds all the transactions below the given price threshold, drops them
|
||||
// from the priced list and returns them for further removal from the entire pool.
|
||||
//
|
||||
// Note: only remote transactions will be considered for eviction.
|
||||
func (l *txPricedList) Cap(threshold *big.Int) types.Transactions {
|
||||
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
|
||||
for len(*l.remotes) > 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
cheapest := (*l.remotes)[0]
|
||||
if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated
|
||||
heap.Pop(l.remotes)
|
||||
l.stales--
|
||||
continue
|
||||
}
|
||||
// Stop the discards if we've reached the threshold
|
||||
if cheapest.GasPriceIntCmp(threshold) >= 0 {
|
||||
break
|
||||
}
|
||||
heap.Pop(l.remotes)
|
||||
drop = append(drop, cheapest)
|
||||
}
|
||||
return drop
|
||||
}
|
||||
|
||||
// Underpriced checks whether a transaction is cheaper than (or as cheap as) the
|
||||
// lowest priced (remote) transaction currently being tracked.
|
||||
func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
|
||||
// Note: with two queues, being underpriced is defined as being worse than the worst item
|
||||
// in all non-empty queues if there is any. If both queues are empty then nothing is underpriced.
|
||||
return (l.underpricedFor(&l.urgent, tx) || len(l.urgent.list) == 0) &&
|
||||
(l.underpricedFor(&l.floating, tx) || len(l.floating.list) == 0) &&
|
||||
(len(l.urgent.list) != 0 || len(l.floating.list) != 0)
|
||||
}
|
||||
|
||||
// underpricedFor checks whether a transaction is cheaper than (or as cheap as) the
|
||||
// lowest priced (remote) transaction in the given heap.
|
||||
func (l *txPricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool {
|
||||
// Discard stale price points if found at the heap start
|
||||
for len(*l.remotes) > 0 {
|
||||
head := []*types.Transaction(*l.remotes)[0]
|
||||
for len(h.list) > 0 {
|
||||
head := h.list[0]
|
||||
if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated
|
||||
l.stales--
|
||||
heap.Pop(l.remotes)
|
||||
heap.Pop(h)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
// Check if the transaction is underpriced or not
|
||||
if len(*l.remotes) == 0 {
|
||||
if len(h.list) == 0 {
|
||||
return false // There is no remote transaction at all.
|
||||
}
|
||||
// If the remote transaction is even cheaper than the
|
||||
// cheapest one tracked locally, reject it.
|
||||
cheapest := []*types.Transaction(*l.remotes)[0]
|
||||
return cheapest.GasPriceCmp(tx) >= 0
|
||||
return h.cmp(h.list[0], tx) >= 0
|
||||
}
|
||||
|
||||
// Discard finds a number of most underpriced transactions, removes them from the
|
||||
@ -529,21 +556,36 @@ func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
|
||||
// Note local transaction won't be considered for eviction.
|
||||
func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) {
|
||||
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
|
||||
for len(*l.remotes) > 0 && slots > 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
tx := heap.Pop(l.remotes).(*types.Transaction)
|
||||
if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
|
||||
l.stales--
|
||||
continue
|
||||
for slots > 0 {
|
||||
if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio || floatingRatio == 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
tx := heap.Pop(&l.urgent).(*types.Transaction)
|
||||
if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
|
||||
l.stales--
|
||||
continue
|
||||
}
|
||||
// Non stale transaction found, move to floating heap
|
||||
heap.Push(&l.floating, tx)
|
||||
} else {
|
||||
if len(l.floating.list) == 0 {
|
||||
// Stop if both heaps are empty
|
||||
break
|
||||
}
|
||||
// Discard stale transactions if found during cleanup
|
||||
tx := heap.Pop(&l.floating).(*types.Transaction)
|
||||
if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
|
||||
l.stales--
|
||||
continue
|
||||
}
|
||||
// Non stale transaction found, discard it
|
||||
drop = append(drop, tx)
|
||||
slots -= numSlots(tx)
|
||||
}
|
||||
// Non stale transaction found, discard it
|
||||
drop = append(drop, tx)
|
||||
slots -= numSlots(tx)
|
||||
}
|
||||
// If we still can't make enough room for the new transaction
|
||||
if slots > 0 && !force {
|
||||
for _, tx := range drop {
|
||||
heap.Push(l.remotes, tx)
|
||||
heap.Push(&l.urgent, tx)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@ -552,12 +594,32 @@ func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool)
|
||||
|
||||
// Reheap forcibly rebuilds the heap based on the current remote transaction set.
|
||||
func (l *txPricedList) Reheap() {
|
||||
reheap := make(priceHeap, 0, l.all.RemoteCount())
|
||||
|
||||
l.stales, l.remotes = 0, &reheap
|
||||
start := time.Now()
|
||||
l.stales = 0
|
||||
l.urgent.list = make([]*types.Transaction, 0, l.all.RemoteCount())
|
||||
l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
|
||||
*l.remotes = append(*l.remotes, tx)
|
||||
l.urgent.list = append(l.urgent.list, tx)
|
||||
return true
|
||||
}, false, true) // Only iterate remotes
|
||||
heap.Init(l.remotes)
|
||||
heap.Init(&l.urgent)
|
||||
|
||||
// balance out the two heaps by moving the worse half of transactions into the
|
||||
// floating heap
|
||||
// Note: Discard would also do this before the first eviction but Reheap can do
|
||||
// is more efficiently. Also, Underpriced would work suboptimally the first time
|
||||
// if the floating queue was empty.
|
||||
floatingCount := len(l.urgent.list) * floatingRatio / (urgentRatio + floatingRatio)
|
||||
l.floating.list = make([]*types.Transaction, floatingCount)
|
||||
for i := 0; i < floatingCount; i++ {
|
||||
l.floating.list[i] = heap.Pop(&l.urgent).(*types.Transaction)
|
||||
}
|
||||
heap.Init(&l.floating)
|
||||
reheapTimer.Update(time.Since(start))
|
||||
}
|
||||
|
||||
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
|
||||
// necessary to call right before SetBaseFee when processing a new block.
|
||||
func (l *txPricedList) SetBaseFee(baseFee *big.Int) {
|
||||
l.urgent.baseFee = baseFee
|
||||
l.Reheap()
|
||||
}
|
||||
|
@ -83,6 +83,10 @@ var (
|
||||
// than some meaningful limit a user might use. This is not a consensus error
|
||||
// making the transaction invalid, rather a DOS protection.
|
||||
ErrOversizedData = errors.New("oversized data")
|
||||
|
||||
// ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a
|
||||
// transaction with a tip higher than the total fee cap.
|
||||
ErrTipAboveFeeCap = errors.New("tip higher than fee cap")
|
||||
)
|
||||
|
||||
var (
|
||||
@ -115,6 +119,8 @@ var (
|
||||
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
|
||||
localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
|
||||
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
|
||||
|
||||
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
|
||||
)
|
||||
|
||||
// TxStatus is the current status of a transaction as seen by the pool.
|
||||
@ -165,7 +171,7 @@ var DefaultTxPoolConfig = TxPoolConfig{
|
||||
PriceBump: 10,
|
||||
|
||||
AccountSlots: 16,
|
||||
GlobalSlots: 4096,
|
||||
GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio
|
||||
AccountQueue: 64,
|
||||
GlobalQueue: 1024,
|
||||
|
||||
@ -230,6 +236,7 @@ type TxPool struct {
|
||||
|
||||
istanbul bool // Fork indicator whether we are in the istanbul stage.
|
||||
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
|
||||
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
|
||||
|
||||
currentState *state.StateDB // Current state in the blockchain head
|
||||
pendingNonces *txNoncer // Pending state tracking virtual nonces
|
||||
@ -426,10 +433,18 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
old := pool.gasPrice
|
||||
pool.gasPrice = price
|
||||
for _, tx := range pool.priced.Cap(price) {
|
||||
pool.removeTx(tx.Hash(), false)
|
||||
// if the min miner fee increased, remove transactions below the new threshold
|
||||
if price.Cmp(old) > 0 {
|
||||
// pool.priced is sorted by FeeCap, so we have to iterate through pool.all instead
|
||||
drop := pool.all.RemotesBelowTip(price)
|
||||
for _, tx := range drop {
|
||||
pool.removeTx(tx.Hash(), false)
|
||||
}
|
||||
pool.priced.Removed(len(drop))
|
||||
}
|
||||
|
||||
log.Info("Transaction pool price threshold updated", "price", price)
|
||||
}
|
||||
|
||||
@ -527,6 +542,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
if !pool.eip2718 && tx.Type() != types.LegacyTxType {
|
||||
return ErrTxTypeNotSupported
|
||||
}
|
||||
// Reject dynamic fee transactions until EIP-1559 activates.
|
||||
if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
|
||||
return ErrTxTypeNotSupported
|
||||
}
|
||||
// Reject transactions over defined size to prevent DOS attacks
|
||||
if uint64(tx.Size()) > txMaxSize {
|
||||
return ErrOversizedData
|
||||
@ -540,13 +559,17 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
if pool.currentMaxGas < tx.Gas() {
|
||||
return ErrGasLimit
|
||||
}
|
||||
// Ensure feeCap is less than or equal to tip.
|
||||
if tx.FeeCapIntCmp(tx.Tip()) < 0 {
|
||||
return ErrTipAboveFeeCap
|
||||
}
|
||||
// Make sure the transaction is signed properly.
|
||||
from, err := types.Sender(pool.signer, tx)
|
||||
if err != nil {
|
||||
return ErrInvalidSender
|
||||
}
|
||||
// Drop non-local transactions under our own minimal accepted gas price
|
||||
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 {
|
||||
// Drop non-local transactions under our own minimal accepted gas price or tip
|
||||
if !local && tx.TipIntCmp(pool.gasPrice) < 0 {
|
||||
return ErrUnderpriced
|
||||
}
|
||||
// Ensure the transaction adheres to nonce ordering
|
||||
@ -598,7 +621,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
|
||||
if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue {
|
||||
// If the new transaction is underpriced, don't accept it
|
||||
if !isLocal && pool.priced.Underpriced(tx) {
|
||||
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
|
||||
log.Trace("Discarding underpriced transaction", "hash", hash, "tip", tx.Tip(), "feeCap", tx.FeeCap())
|
||||
underpricedTxMeter.Mark(1)
|
||||
return false, ErrUnderpriced
|
||||
}
|
||||
@ -615,7 +638,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
|
||||
}
|
||||
// Kick out the underpriced remote transactions.
|
||||
for _, tx := range drop {
|
||||
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
|
||||
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "tip", tx.Tip(), "feeCap", tx.FeeCap())
|
||||
underpricedTxMeter.Mark(1)
|
||||
pool.removeTx(tx.Hash(), false)
|
||||
}
|
||||
@ -1088,6 +1111,9 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
// because of another transaction (e.g. higher gas price).
|
||||
if reset != nil {
|
||||
pool.demoteUnexecutables()
|
||||
if reset.newHead != nil {
|
||||
pool.priced.SetBaseFee(reset.newHead.BaseFee)
|
||||
}
|
||||
}
|
||||
// Ensure pool.queue and pool.pending sizes stay within the configured limits.
|
||||
pool.truncatePending()
|
||||
@ -1205,6 +1231,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
|
||||
pool.istanbul = pool.chainconfig.IsIstanbul(next)
|
||||
pool.eip2718 = pool.chainconfig.IsBerlin(next)
|
||||
pool.eip1559 = pool.chainconfig.IsLondon(next)
|
||||
}
|
||||
|
||||
// promoteExecutables moves transactions that have become processable from the
|
||||
@ -1408,6 +1435,10 @@ func (pool *TxPool) truncateQueue() {
|
||||
// demoteUnexecutables removes invalid and processed transactions from the pools
|
||||
// executable/pending queue and any subsequent transactions that become unexecutable
|
||||
// are moved back into the future queue.
|
||||
//
|
||||
// Note: transactions are not marked as removed in the priced list because re-heaping
|
||||
// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful
|
||||
// to trigger a re-heap is this function
|
||||
func (pool *TxPool) demoteUnexecutables() {
|
||||
// Iterate over all accounts and demote any non-executable transactions
|
||||
for addr, list := range pool.pending {
|
||||
@ -1427,7 +1458,6 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(olds) + len(drops))
|
||||
pendingNofundsMeter.Mark(int64(len(drops)))
|
||||
|
||||
for _, tx := range invalids {
|
||||
@ -1709,6 +1739,18 @@ func (t *txLookup) RemoteToLocals(locals *accountSet) int {
|
||||
return migrated
|
||||
}
|
||||
|
||||
// RemotesBelowTip finds all remote transactions below the given tip threshold.
|
||||
func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
|
||||
found := make(types.Transactions, 0, 128)
|
||||
t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
|
||||
if tx.TipIntCmp(threshold) < 0 {
|
||||
found = append(found, tx)
|
||||
}
|
||||
return true
|
||||
}, false, true) // Only iterate remotes
|
||||
return found
|
||||
}
|
||||
|
||||
// numSlots calculates the number of slots needed for a single transaction.
|
||||
func numSlots(tx *types.Transaction) int {
|
||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||
|
@ -37,13 +37,23 @@ import (
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// testTxPoolConfig is a transaction pool configuration without stateful disk
|
||||
// sideeffects used during testing.
|
||||
var testTxPoolConfig TxPoolConfig
|
||||
var (
|
||||
// testTxPoolConfig is a transaction pool configuration without stateful disk
|
||||
// sideeffects used during testing.
|
||||
testTxPoolConfig TxPoolConfig
|
||||
|
||||
// eip1559Config is a chain config with EIP-1559 enabled at block 0.
|
||||
eip1559Config *params.ChainConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTxPoolConfig = DefaultTxPoolConfig
|
||||
testTxPoolConfig.Journal = ""
|
||||
|
||||
cpy := *params.TestChainConfig
|
||||
eip1559Config = &cpy
|
||||
eip1559Config.BerlinBlock = common.Big0
|
||||
eip1559Config.LondonBlock = common.Big0
|
||||
}
|
||||
|
||||
type testBlockChain struct {
|
||||
@ -87,12 +97,31 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key
|
||||
return tx
|
||||
}
|
||||
|
||||
func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||
tx, _ := types.SignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.DynamicFeeTx{
|
||||
ChainID: params.TestChainConfig.ChainID,
|
||||
Nonce: nonce,
|
||||
Tip: tip,
|
||||
FeeCap: gasFee,
|
||||
Gas: gaslimit,
|
||||
To: &common.Address{},
|
||||
Value: big.NewInt(100),
|
||||
Data: nil,
|
||||
AccessList: nil,
|
||||
})
|
||||
return tx
|
||||
}
|
||||
|
||||
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
|
||||
return setupTxPoolWithConfig(params.TestChainConfig)
|
||||
}
|
||||
|
||||
func setupTxPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
|
||||
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
pool := NewTxPool(testTxPoolConfig, config, blockchain)
|
||||
|
||||
return pool, key
|
||||
}
|
||||
@ -108,7 +137,7 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||
return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
|
||||
}
|
||||
pool.priced.Reheap()
|
||||
priced, remote := pool.priced.remotes.Len(), pool.all.RemoteCount()
|
||||
priced, remote := pool.priced.urgent.Len()+pool.priced.floating.Len(), pool.all.RemoteCount()
|
||||
if priced != remote {
|
||||
return fmt.Errorf("total priced transaction count %d != %d", priced, remote)
|
||||
}
|
||||
@ -233,6 +262,18 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testAddBalance(pool *TxPool, addr common.Address, amount *big.Int) {
|
||||
pool.mu.Lock()
|
||||
pool.currentState.AddBalance(addr, amount)
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
||||
func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) {
|
||||
pool.mu.Lock()
|
||||
pool.currentState.SetNonce(addr, nonce)
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestInvalidTransactions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -242,19 +283,19 @@ func TestInvalidTransactions(t *testing.T) {
|
||||
tx := transaction(0, 100, key)
|
||||
from, _ := deriveSender(tx)
|
||||
|
||||
pool.currentState.AddBalance(from, big.NewInt(1))
|
||||
testAddBalance(pool, from, big.NewInt(1))
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) {
|
||||
t.Error("expected", ErrInsufficientFunds)
|
||||
}
|
||||
|
||||
balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()))
|
||||
pool.currentState.AddBalance(from, balance)
|
||||
testAddBalance(pool, from, balance)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) {
|
||||
t.Error("expected", ErrIntrinsicGas, "got", err)
|
||||
}
|
||||
|
||||
pool.currentState.SetNonce(from, 1)
|
||||
pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff))
|
||||
testSetNonce(pool, from, 1)
|
||||
testAddBalance(pool, from, big.NewInt(0xffffffffffffff))
|
||||
tx = transaction(0, 100000, key)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) {
|
||||
t.Error("expected", ErrNonceTooLow)
|
||||
@ -278,7 +319,7 @@ func TestTransactionQueue(t *testing.T) {
|
||||
|
||||
tx := transaction(0, 100, key)
|
||||
from, _ := deriveSender(tx)
|
||||
pool.currentState.AddBalance(from, big.NewInt(1000))
|
||||
testAddBalance(pool, from, big.NewInt(1000))
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
pool.enqueueTx(tx.Hash(), tx, false, true)
|
||||
@ -289,7 +330,7 @@ func TestTransactionQueue(t *testing.T) {
|
||||
|
||||
tx = transaction(1, 100, key)
|
||||
from, _ = deriveSender(tx)
|
||||
pool.currentState.SetNonce(from, 2)
|
||||
testSetNonce(pool, from, 2)
|
||||
pool.enqueueTx(tx.Hash(), tx, false, true)
|
||||
|
||||
<-pool.requestPromoteExecutables(newAccountSet(pool.signer, from))
|
||||
@ -311,7 +352,7 @@ func TestTransactionQueue2(t *testing.T) {
|
||||
tx2 := transaction(10, 100, key)
|
||||
tx3 := transaction(11, 100, key)
|
||||
from, _ := deriveSender(tx1)
|
||||
pool.currentState.AddBalance(from, big.NewInt(1000))
|
||||
testAddBalance(pool, from, big.NewInt(1000))
|
||||
pool.reset(nil, nil)
|
||||
|
||||
pool.enqueueTx(tx1.Hash(), tx1, false, true)
|
||||
@ -335,12 +376,25 @@ func TestTransactionNegativeValue(t *testing.T) {
|
||||
|
||||
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
||||
from, _ := deriveSender(tx)
|
||||
pool.currentState.AddBalance(from, big.NewInt(1))
|
||||
testAddBalance(pool, from, big.NewInt(1))
|
||||
if err := pool.AddRemote(tx); err != ErrNegativeValue {
|
||||
t.Error("expected", ErrNegativeValue, "got", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionTipAboveFeeCap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
||||
|
||||
if err := pool.AddRemote(tx); err != ErrTipAboveFeeCap {
|
||||
t.Error("expected", ErrTipAboveFeeCap, "got", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionChainFork(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -428,7 +482,7 @@ func TestTransactionMissingNonce(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(addr, big.NewInt(100000000000000))
|
||||
testAddBalance(pool, addr, big.NewInt(100000000000000))
|
||||
tx := transaction(1, 100000, key)
|
||||
if _, err := pool.add(tx, false); err != nil {
|
||||
t.Error("didn't expect error", err)
|
||||
@ -452,8 +506,8 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.SetNonce(addr, n)
|
||||
pool.currentState.AddBalance(addr, big.NewInt(100000000000000))
|
||||
testSetNonce(pool, addr, n)
|
||||
testAddBalance(pool, addr, big.NewInt(100000000000000))
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
tx := transaction(n, 100000, key)
|
||||
@ -461,7 +515,7 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
// simulate some weird re-order of transactions and missing nonce(s)
|
||||
pool.currentState.SetNonce(addr, n-1)
|
||||
testSetNonce(pool, addr, n-1)
|
||||
<-pool.requestReset(nil, nil)
|
||||
if fn := pool.Nonce(addr); fn != n-1 {
|
||||
t.Errorf("expected nonce to be %d, got %d", n-1, fn)
|
||||
@ -478,7 +532,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000))
|
||||
testAddBalance(pool, account, big.NewInt(1000))
|
||||
|
||||
// Add some pending and some queued transactions
|
||||
var (
|
||||
@ -526,7 +580,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
}
|
||||
// Reduce the balance of the account, and check that invalidated transactions are dropped
|
||||
pool.currentState.AddBalance(account, big.NewInt(-650))
|
||||
testAddBalance(pool, account, big.NewInt(-650))
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
||||
@ -592,7 +646,7 @@ func TestTransactionPostponing(t *testing.T) {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
|
||||
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100))
|
||||
}
|
||||
// Add a batch consecutive pending transactions for validation
|
||||
txs := []*types.Transaction{}
|
||||
@ -635,7 +689,7 @@ func TestTransactionPostponing(t *testing.T) {
|
||||
}
|
||||
// Reduce the balance of the account, and check that transactions are reorganised
|
||||
for _, addr := range accs {
|
||||
pool.currentState.AddBalance(addr, big.NewInt(-1))
|
||||
testAddBalance(pool, addr, big.NewInt(-1))
|
||||
}
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
@ -696,7 +750,7 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
@ -750,7 +804,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
// Keep queuing up transactions and make sure all above a limit are dropped
|
||||
for i := uint64(1); i <= testTxPoolConfig.AccountQueue+5; i++ {
|
||||
@ -805,7 +859,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||
keys := make([]*ecdsa.PrivateKey, 5)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
local := keys[len(keys)-1]
|
||||
|
||||
@ -897,8 +951,8 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
local, _ := crypto.GenerateKey()
|
||||
remote, _ := crypto.GenerateKey()
|
||||
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Add the two transactions and ensure they both are queued up
|
||||
if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil {
|
||||
@ -1031,7 +1085,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
@ -1081,7 +1135,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 5)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions
|
||||
nonces := make(map[common.Address]uint64)
|
||||
@ -1120,7 +1174,7 @@ func TestTransactionAllowedTxSize(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000000))
|
||||
|
||||
// Compute maximal data size for transactions (lower bound).
|
||||
//
|
||||
@ -1184,7 +1238,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||
// Create a number of test accounts and fund them
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(addr, big.NewInt(1000000))
|
||||
testAddBalance(pool, addr, big.NewInt(1000000))
|
||||
|
||||
txs := types.Transactions{}
|
||||
for j := 0; j < int(config.GlobalSlots)*2; j++ {
|
||||
@ -1217,7 +1271,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 5)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions
|
||||
nonces := make(map[common.Address]uint64)
|
||||
@ -1267,7 +1321,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions, both pending and queued
|
||||
txs := types.Transactions{}
|
||||
@ -1367,8 +1421,135 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that setting the transaction pool gas price to a higher value correctly
|
||||
// discards everything cheaper (legacy & dynamic fee) than that and moves any
|
||||
// gapped transactions back from the pending pool to the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions, both pending and queued
|
||||
txs := types.Transactions{}
|
||||
|
||||
txs = append(txs, pricedTransaction(0, 100000, big.NewInt(2), keys[0]))
|
||||
txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0]))
|
||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0]))
|
||||
|
||||
txs = append(txs, dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]))
|
||||
txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(3), big.NewInt(2), keys[1]))
|
||||
txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(3), big.NewInt(2), keys[1]))
|
||||
|
||||
txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(2), big.NewInt(2), keys[2]))
|
||||
txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2]))
|
||||
txs = append(txs, dynamicFeeTx(3, 100000, big.NewInt(2), big.NewInt(2), keys[2]))
|
||||
|
||||
ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[3])
|
||||
|
||||
// Import the batch and that both pending and queued transactions match up
|
||||
pool.AddRemotesSync(txs)
|
||||
pool.AddLocal(ltx)
|
||||
|
||||
pending, queued := pool.Stats()
|
||||
if pending != 7 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7)
|
||||
}
|
||||
if queued != 3 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||
}
|
||||
if err := validateEvents(events, 7); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Reprice the pool and check that underpriced transactions get dropped
|
||||
pool.SetGasPrice(big.NewInt(2))
|
||||
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if queued != 5 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Check that we can't add the old transactions back
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
||||
if err := pool.AddRemote(tx); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||
if err := pool.AddRemote(tx); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
||||
if err := pool.AddRemote(tx); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// However we can add local underpriced transactions
|
||||
tx = dynamicFeeTx(1, 100000, big.NewInt(1), big.NewInt(1), keys[3])
|
||||
if err := pool.AddLocal(tx); err != nil {
|
||||
t.Fatalf("failed to add underpriced local transaction: %v", err)
|
||||
}
|
||||
if pending, _ = pool.Stats(); pending != 3 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||
}
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("post-reprice local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// And we can fill gaps with properly priced transactions
|
||||
tx = pricedTransaction(1, 100000, big.NewInt(2), keys[0])
|
||||
if err := pool.AddRemote(tx); err != nil {
|
||||
t.Fatalf("failed to add pending transaction: %v", err)
|
||||
}
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1])
|
||||
if err := pool.AddRemote(tx); err != nil {
|
||||
t.Fatalf("failed to add pending transaction: %v", err)
|
||||
}
|
||||
tx = dynamicFeeTx(2, 100000, big.NewInt(2), big.NewInt(2), keys[2])
|
||||
if err := pool.AddRemote(tx); err != nil {
|
||||
t.Fatalf("failed to add queued transaction: %v", err)
|
||||
}
|
||||
if err := validateEvents(events, 5); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that setting the transaction pool gas price to a higher value does not
|
||||
// remove local transactions.
|
||||
// remove local transactions (legacy & dynamic fee).
|
||||
func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -1376,14 +1557,14 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain)
|
||||
defer pool.Stop()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 3)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000*1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000*1000000))
|
||||
}
|
||||
// Create transaction (both pending and queued) with a linearly growing gasprice
|
||||
for i := uint64(0); i < 500; i++ {
|
||||
@ -1397,9 +1578,20 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
if err := pool.AddLocal(queuedTx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add pending dynamic fee transaction.
|
||||
pendingTx = dynamicFeeTx(i, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1])
|
||||
if err := pool.AddLocal(pendingTx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Add queued dynamic fee transaction.
|
||||
queuedTx = dynamicFeeTx(i+501, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1])
|
||||
if err := pool.AddLocal(queuedTx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
pending, queued := pool.Stats()
|
||||
expPending, expQueued := 500, 500
|
||||
expPending, expQueued := 1000, 1000
|
||||
validate := func() {
|
||||
pending, queued = pool.Stats()
|
||||
if pending != expPending {
|
||||
@ -1454,7 +1646,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions, both pending and queued
|
||||
txs := types.Transactions{}
|
||||
@ -1560,7 +1752,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 2)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Fill up the entire queue with the same transaction price points
|
||||
txs := types.Transactions{}
|
||||
@ -1601,6 +1793,173 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that when the pool reaches its global transaction limit, underpriced
|
||||
// transactions (legacy & dynamic fee) are gradually shifted out for more
|
||||
// expensive ones and any gapped pending transactions are moved into the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
pool.config.GlobalSlots = 2
|
||||
pool.config.GlobalQueue = 2
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
|
||||
// Generate and queue a batch of transactions, both pending and queued
|
||||
txs := types.Transactions{}
|
||||
|
||||
txs = append(txs, dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0]))
|
||||
txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[0]))
|
||||
txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(2), big.NewInt(1), keys[1]))
|
||||
|
||||
ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[2])
|
||||
|
||||
// Import the batch and that both pending and queued transactions match up
|
||||
pool.AddRemotes(txs) // Pend K0:0, K0:1; Que K1:1
|
||||
pool.AddLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1
|
||||
|
||||
pending, queued := pool.Stats()
|
||||
if pending != 3 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||
}
|
||||
if queued != 1 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
if err := validateEvents(events, 3); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// Ensure that adding an underpriced transaction fails
|
||||
tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||
if err := pool.AddRemote(tx); err != ErrUnderpriced { // Pend K0:0, K0:1, K2:0; Que K1:1
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
|
||||
// Ensure that adding high priced transactions drops cheap ones, but not own
|
||||
tx = pricedTransaction(0, 100000, big.NewInt(2), keys[1])
|
||||
if err := pool.AddRemote(tx); err != nil { // +K1:0, -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que -
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
|
||||
tx = pricedTransaction(2, 100000, big.NewInt(3), keys[1])
|
||||
if err := pool.AddRemote(tx); err != nil { // +K1:2, -K0:1 => Pend K0:0 K1:0, K2:0; Que K1:2
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
tx = dynamicFeeTx(3, 100000, big.NewInt(4), big.NewInt(1), keys[1])
|
||||
if err := pool.AddRemote(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding local transactions can push out even higher priced ones
|
||||
ltx = dynamicFeeTx(1, 100000, big.NewInt(0), big.NewInt(0), keys[2])
|
||||
if err := pool.AddLocal(ltx); err != nil {
|
||||
t.Fatalf("failed to append underpriced local transaction: %v", err)
|
||||
}
|
||||
ltx = dynamicFeeTx(0, 100000, big.NewInt(0), big.NewInt(0), keys[3])
|
||||
if err := pool.AddLocal(ltx); err != nil {
|
||||
t.Fatalf("failed to add new underpriced local transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 3 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||
}
|
||||
if queued != 1 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether highest fee cap transaction is retained after a batch of high effective
|
||||
// tip transactions are added and vice versa
|
||||
func TestDualHeapEviction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
pool.config.GlobalSlots = 10
|
||||
pool.config.GlobalQueue = 10
|
||||
|
||||
var (
|
||||
highTip, highCap *types.Transaction
|
||||
baseFee int
|
||||
)
|
||||
|
||||
check := func(tx *types.Transaction, name string) {
|
||||
if pool.all.GetRemote(tx.Hash()) == nil {
|
||||
t.Fatalf("highest %s transaction evicted from the pool", name)
|
||||
}
|
||||
}
|
||||
|
||||
add := func(urgent bool) {
|
||||
txs := make([]*types.Transaction, 20)
|
||||
for i := range txs {
|
||||
// Create a test accounts and fund it
|
||||
key, _ := crypto.GenerateKey()
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000))
|
||||
if urgent {
|
||||
txs[i] = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key)
|
||||
highTip = txs[i]
|
||||
} else {
|
||||
txs[i] = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key)
|
||||
highCap = txs[i]
|
||||
}
|
||||
}
|
||||
pool.AddRemotes(txs)
|
||||
pending, queued := pool.Stats()
|
||||
if pending+queued != 20 {
|
||||
t.Fatalf("transaction count mismatch: have %d, want %d", pending+queued, 10)
|
||||
}
|
||||
}
|
||||
|
||||
add(false)
|
||||
for baseFee = 0; baseFee <= 1000; baseFee += 100 {
|
||||
pool.priced.SetBaseFee(big.NewInt(int64(baseFee)))
|
||||
add(true)
|
||||
check(highCap, "fee cap")
|
||||
add(false)
|
||||
check(highTip, "effective tip")
|
||||
}
|
||||
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects duplicate transactions.
|
||||
func TestTransactionDeduplication(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -1614,7 +1973,7 @@ func TestTransactionDeduplication(t *testing.T) {
|
||||
|
||||
// Create a test account to add transactions with
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Create a batch of transactions and add a few of them
|
||||
txs := make([]*types.Transaction, 16)
|
||||
@ -1685,7 +2044,7 @@ func TestTransactionReplacement(t *testing.T) {
|
||||
|
||||
// Create a test account to add transactions with
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||
price := int64(100)
|
||||
@ -1746,6 +2105,116 @@ func TestTransactionReplacement(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects replacement dynamic fee transactions that don't
|
||||
// meet the minimum price bump required.
|
||||
func TestTransactionReplacementDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
pool, key := setupTxPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||
feeCap := int64(100)
|
||||
feeCapThreshold := (feeCap * (100 + int64(testTxPoolConfig.PriceBump))) / 100
|
||||
tip := int64(60)
|
||||
tipThreshold := (tip * (100 + int64(testTxPoolConfig.PriceBump))) / 100
|
||||
|
||||
// Run the following identical checks for both the pending and queue pools:
|
||||
// 1. Send initial tx => accept
|
||||
// 2. Don't bump tip or fee cap => discard
|
||||
// 3. Bump both more than min => accept
|
||||
// 4. Check events match expected (2 new executable txs during pending, 0 during queue)
|
||||
// 5. Send new tx with larger tip and feeCap => accept
|
||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||
// 8. Bump tip min for acceptance => discard
|
||||
// 9. Bump feecap min for acceptance => discard
|
||||
// 10. Bump feecap and tip min for acceptance => accept
|
||||
// 11. Check events match expected (2 new executable txs during pending, 0 during queue)
|
||||
stages := []string{"pending", "queued"}
|
||||
for _, stage := range stages {
|
||||
// Since state is empty, 0 nonce txs are "executable" and can go
|
||||
// into pending immediately. 2 nonce txs are "happed
|
||||
nonce := uint64(0)
|
||||
if stage == "queued" {
|
||||
nonce = 2
|
||||
}
|
||||
|
||||
// 1. Send initial tx => accept
|
||||
tx := dynamicFeeTx(nonce, 100000, big.NewInt(2), big.NewInt(1), key)
|
||||
if err := pool.addRemoteSync(tx); err != nil {
|
||||
t.Fatalf("failed to add original cheap %s transaction: %v", stage, err)
|
||||
}
|
||||
// 2. Don't bump tip or feecap => discard
|
||||
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
||||
if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
|
||||
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
|
||||
}
|
||||
// 3. Bump both more than min => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(3), big.NewInt(2), key)
|
||||
if err := pool.AddRemote(tx); err != nil {
|
||||
t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err)
|
||||
}
|
||||
// 4. Check events match expected (2 new executable txs during pending, 0 during queue)
|
||||
count := 2
|
||||
if stage == "queued" {
|
||||
count = 0
|
||||
}
|
||||
if err := validateEvents(events, count); err != nil {
|
||||
t.Fatalf("cheap %s replacement event firing failed: %v", stage, err)
|
||||
}
|
||||
// 5. Send new tx with larger tip and feeCap => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCap), big.NewInt(tip), key)
|
||||
if err := pool.addRemoteSync(tx); err != nil {
|
||||
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
||||
}
|
||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCap), big.NewInt(tipThreshold-1), key)
|
||||
if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
|
||||
}
|
||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(tip), key)
|
||||
if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
|
||||
}
|
||||
// 8. Bump tip min for acceptance => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCap), big.NewInt(tipThreshold), key)
|
||||
if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
|
||||
}
|
||||
// 9. Bump fee cap min for acceptance => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(tip), key)
|
||||
if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
|
||||
}
|
||||
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(tipThreshold), key)
|
||||
if err := pool.AddRemote(tx); err != nil {
|
||||
t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err)
|
||||
}
|
||||
// 11. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||
count = 2
|
||||
if stage == "queued" {
|
||||
count = 0
|
||||
}
|
||||
if err := validateEvents(events, count); err != nil {
|
||||
t.Fatalf("replacement %s event firing failed: %v", stage, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that local transactions are journaled to disk, but remote transactions
|
||||
// get discarded between restarts.
|
||||
func TestTransactionJournaling(t *testing.T) { testTransactionJournaling(t, false) }
|
||||
@ -1781,8 +2250,8 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
local, _ := crypto.GenerateKey()
|
||||
remote, _ := crypto.GenerateKey()
|
||||
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Add three local and a remote transactions and ensure they are queued up
|
||||
if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil {
|
||||
@ -1875,7 +2344,7 @@ func TestTransactionStatusCheck(t *testing.T) {
|
||||
keys := make([]*ecdsa.PrivateKey, 3)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Generate and queue a batch of transactions, both pending and queued
|
||||
txs := types.Transactions{}
|
||||
@ -1945,7 +2414,7 @@ func benchmarkPendingDemotion(b *testing.B, size int) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
tx := transaction(uint64(i), 100000, key)
|
||||
@ -1970,7 +2439,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
tx := transaction(uint64(1+i), 100000, key)
|
||||
@ -1998,7 +2467,7 @@ func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
batches := make([]types.Transactions, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
@ -2039,13 +2508,13 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
pool, _ := setupTxPool()
|
||||
pool.currentState.AddBalance(account, big.NewInt(100000000))
|
||||
testAddBalance(pool, account, big.NewInt(100000000))
|
||||
for _, local := range locals {
|
||||
pool.AddLocal(local)
|
||||
}
|
||||
b.StartTimer()
|
||||
// Assign a high enough balance for testing
|
||||
pool.currentState.AddBalance(remoteAddr, big.NewInt(100000000))
|
||||
testAddBalance(pool, remoteAddr, big.NewInt(100000000))
|
||||
for i := 0; i < len(remotes); i++ {
|
||||
pool.AddRemotes([]*types.Transaction{remotes[i]})
|
||||
}
|
||||
|
@ -300,33 +300,60 @@ func (tx *Transaction) Cost() *big.Int {
|
||||
return total
|
||||
}
|
||||
|
||||
// EffectiveTip returns the effective miner tip for the given base fee.
|
||||
// Returns error in case of a negative effective miner tip.
|
||||
func (tx *Transaction) EffectiveTip(baseFee *big.Int) (*big.Int, error) {
|
||||
if baseFee == nil {
|
||||
return tx.Tip(), nil
|
||||
}
|
||||
feeCap := tx.FeeCap()
|
||||
if feeCap.Cmp(baseFee) == -1 {
|
||||
return nil, ErrFeeCapTooLow
|
||||
}
|
||||
return math.BigMin(tx.Tip(), feeCap.Sub(feeCap, baseFee)), nil
|
||||
}
|
||||
|
||||
// RawSignatureValues returns the V, R, S signature values of the transaction.
|
||||
// The return values should not be modified by the caller.
|
||||
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
|
||||
return tx.inner.rawSignatureValues()
|
||||
}
|
||||
|
||||
// GasPriceCmp compares the gas prices of two transactions.
|
||||
func (tx *Transaction) GasPriceCmp(other *Transaction) int {
|
||||
return tx.inner.gasPrice().Cmp(other.inner.gasPrice())
|
||||
// FeeCapCmp compares the fee cap of two transactions.
|
||||
func (tx *Transaction) FeeCapCmp(other *Transaction) int {
|
||||
return tx.inner.feeCap().Cmp(other.inner.feeCap())
|
||||
}
|
||||
|
||||
// GasPriceIntCmp compares the gas price of the transaction against the given price.
|
||||
func (tx *Transaction) GasPriceIntCmp(other *big.Int) int {
|
||||
return tx.inner.gasPrice().Cmp(other)
|
||||
// FeeCapIntCmp compares the fee cap of the transaction against the given fee cap.
|
||||
func (tx *Transaction) FeeCapIntCmp(other *big.Int) int {
|
||||
return tx.inner.feeCap().Cmp(other)
|
||||
}
|
||||
|
||||
// TipCmp compares the tip of two transactions.
|
||||
func (tx *Transaction) TipCmp(other *Transaction) int {
|
||||
return tx.inner.tip().Cmp(other.inner.tip())
|
||||
}
|
||||
|
||||
// TipIntCmp compares the tip of the transaction against the given tip.
|
||||
func (tx *Transaction) TipIntCmp(other *big.Int) int {
|
||||
return tx.inner.tip().Cmp(other)
|
||||
}
|
||||
|
||||
// EffectiveTip returns the effective miner tip for the given base fee.
|
||||
// Note: if the effective tip is negative, this method returns both error
|
||||
// the actual negative value, _and_ ErrFeeCapTooLow
|
||||
func (tx *Transaction) EffectiveTip(baseFee *big.Int) (*big.Int, error) {
|
||||
if baseFee == nil {
|
||||
return tx.Tip(), nil
|
||||
}
|
||||
var err error
|
||||
feeCap := tx.FeeCap()
|
||||
if feeCap.Cmp(baseFee) == -1 {
|
||||
err = ErrFeeCapTooLow
|
||||
}
|
||||
return math.BigMin(tx.Tip(), feeCap.Sub(feeCap, baseFee)), err
|
||||
}
|
||||
|
||||
// EffectiveTipValue is identical to EffectiveTip, but does not return an
|
||||
// error in case the effective tip is negative
|
||||
func (tx *Transaction) EffectiveTipValue(baseFee *big.Int) *big.Int {
|
||||
effectiveTip, _ := tx.EffectiveTip(baseFee)
|
||||
return effectiveTip
|
||||
}
|
||||
|
||||
// EffectiveTipCmp compares the effective tip of two transactions assuming the given base fee.
|
||||
func (tx *Transaction) EffectiveTipCmp(other *Transaction, baseFee *big.Int) int {
|
||||
if baseFee == nil {
|
||||
return tx.TipCmp(other)
|
||||
}
|
||||
return tx.EffectiveTipValue(baseFee).Cmp(other.EffectiveTipValue(baseFee))
|
||||
}
|
||||
|
||||
// Hash returns the transaction hash.
|
||||
|
@ -188,7 +188,7 @@ type transactionsByGasPrice []*types.Transaction
|
||||
|
||||
func (t transactionsByGasPrice) Len() int { return len(t) }
|
||||
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
|
||||
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].FeeCapCmp(t[j]) < 0 }
|
||||
|
||||
// getBlockPrices calculates the lowest transaction gas price in a given block
|
||||
// and sends it to the result channel. If the block is empty or all transactions
|
||||
@ -210,7 +210,7 @@ func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, bloc
|
||||
|
||||
var prices []*big.Int
|
||||
for _, tx := range txs {
|
||||
if ignoreUnder != nil && tx.GasPriceIntCmp(ignoreUnder) == -1 {
|
||||
if ignoreUnder != nil && tx.GasPrice().Cmp(ignoreUnder) == -1 {
|
||||
continue
|
||||
}
|
||||
sender, err := types.Sender(signer, tx)
|
||||
|
Loading…
Reference in New Issue
Block a user