diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 9505160f72..553a53d25d 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -40,7 +40,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
- "github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool/legacypool"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth"
@@ -201,20 +201,20 @@ var (
Name: "txpool-journal",
Aliases: []string{"txpool.journal"},
Usage: "Disk journal for local transaction to survive node restarts",
- Value: txpool.DefaultConfig.Journal,
+ Value: ethconfig.Defaults.TxPool.Journal,
Category: flags.TxPoolCategory,
}
TxPoolRejournalFlag = &cli.DurationFlag{
Name: "txpool-rejournal",
Aliases: []string{"txpool.rejournal"},
Usage: "Time interval to regenerate the local transaction journal",
- Value: txpool.DefaultConfig.Rejournal,
+ Value: ethconfig.Defaults.TxPool.Rejournal,
Category: flags.TxPoolCategory,
}
TxPoolPriceLimitFlag = &cli.Uint64Flag{
Name: "txpool-pricelimit",
Aliases: []string{"txpool.pricelimit"},
- Usage: "Minimum gas price limit to enforce for acceptance into the pool",
+ Usage: "Minimum gas price tip to enforce for acceptance into the pool",
Value: ethconfig.Defaults.TxPool.PriceLimit,
Category: flags.TxPoolCategory,
}
@@ -1398,7 +1398,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
}
}
-func setTxPool(ctx *cli.Context, cfg *txpool.Config) {
+func setTxPool(ctx *cli.Context, cfg *legacypool.Config) {
if ctx.IsSet(TxPoolNoLocalsFlag.Name) {
cfg.NoLocals = ctx.Bool(TxPoolNoLocalsFlag.Name)
}
diff --git a/contracts/utils.go b/contracts/utils.go
index 0cd34c6c87..e73266ec2e 100644
--- a/contracts/utils.go
+++ b/contracts/utils.go
@@ -93,7 +93,7 @@ func CreateTransactionSign(chainConfig *params.ChainConfig, pool *txpool.TxPool,
return err
}
// Add tx signed to local tx pool.
- err = pool.AddLocal(txSigned)
+ err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0]
if err != nil {
log.Error("Fail to add tx sign to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce)
return err
@@ -121,7 +121,7 @@ func CreateTransactionSign(chainConfig *params.ChainConfig, pool *txpool.TxPool,
return err
}
// Add tx signed to local tx pool.
- err = pool.AddLocal(txSigned)
+ err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0]
if err != nil {
log.Error("Fail to add tx secret to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce)
return err
@@ -150,7 +150,7 @@ func CreateTransactionSign(chainConfig *params.ChainConfig, pool *txpool.TxPool,
return err
}
// Add tx to pool.
- err = pool.AddLocal(txSigned)
+ err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0]
if err != nil {
log.Error("Fail to add tx opening to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce)
return err
diff --git a/core/txpool/errors.go b/core/txpool/errors.go
new file mode 100644
index 0000000000..b8f0e896d2
--- /dev/null
+++ b/core/txpool/errors.go
@@ -0,0 +1,65 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package txpool
+
+import "errors"
+
+var (
+ // ErrAlreadyKnown is returned if the transactions is already contained
+ // within the pool.
+ ErrAlreadyKnown = errors.New("already known")
+
+ // ErrInvalidSender is returned if the transaction contains an invalid signature.
+ ErrInvalidSender = errors.New("invalid sender")
+
+ // ErrUnderpriced is returned if a transaction's gas price is below the minimum
+ // configured for the transaction pool.
+ ErrUnderpriced = errors.New("transaction underpriced")
+
+ // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
+ // another remote transaction.
+ ErrTxPoolOverflow = errors.New("txpool is full")
+
+ // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
+ // with a different one without the required price bump.
+ ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
+
+ // ErrGasLimit is returned if a transaction's requested gas limit exceeds the
+ // maximum allowance of the current block.
+ ErrGasLimit = errors.New("exceeds block gas limit")
+
+ // ErrNegativeValue is a sanity error to ensure no one is able to specify a
+ // transaction with a negative value.
+ ErrNegativeValue = errors.New("negative value")
+
+ // ErrOversizedData is returned if the input data of a transaction is greater
+ // 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")
+
+ // ErrFutureReplacePending is returned if a future transaction replaces a pending
+ // transaction. Future transactions should only be able to replace other future transactions.
+ ErrFutureReplacePending = errors.New("future transaction tries to replace pending")
+
+ ErrZeroGasPrice = errors.New("zero gas price")
+
+ ErrUnderMinGasPrice = errors.New("under min gas price")
+
+ ErrDuplicateSpecialTransaction = errors.New("duplicate a special transaction")
+
+ ErrMinDeploySMC = errors.New("smart contract creation cost is under allowance")
+)
diff --git a/core/txpool/journal.go b/core/txpool/legacypool/journal.go
similarity index 99%
rename from core/txpool/journal.go
rename to core/txpool/legacypool/journal.go
index bf35ecaf12..fae18c8bf7 100644
--- a/core/txpool/journal.go
+++ b/core/txpool/legacypool/journal.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"errors"
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
new file mode 100644
index 0000000000..53d7368f84
--- /dev/null
+++ b/core/txpool/legacypool/legacypool.go
@@ -0,0 +1,1973 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package legacypool implements the normal EVM execution transaction pool.
+package legacypool
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "math/big"
+ "sort"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/common/prque"
+ "github.com/XinFinOrg/XDPoSChain/consensus"
+ "github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
+ "github.com/XinFinOrg/XDPoSChain/core"
+ "github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/event"
+ "github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/metrics"
+ "github.com/XinFinOrg/XDPoSChain/params"
+)
+
+const (
+ // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
+ chainHeadChanSize = 10
+
+ // txSlotSize is used to calculate how many data slots a single transaction
+ // takes up based on its size. The slots are used as DoS protection, ensuring
+ // that validating a new transaction remains a constant operation (in reality
+ // O(maxslots), where max slots are 4 currently).
+ txSlotSize = 32 * 1024
+
+ // txMaxSize is the maximum size a single transaction can have. This field has
+ // non-trivial consequences: larger transactions are significantly harder and
+ // more expensive to propagate; larger transactions also take more resources
+ // to validate whether they fit into the pool or not.
+ txMaxSize = 2 * txSlotSize // 64KB, don't bump without EIP-2464 support
+)
+
+var (
+ // ErrAlreadyKnown is returned if the transactions is already contained
+ // within the pool.
+ ErrAlreadyKnown = errors.New("already known")
+
+ // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
+ // another remote transaction.
+ ErrTxPoolOverflow = errors.New("txpool is full")
+)
+
+var (
+ evictionInterval = time.Minute // Time interval to check for evictable transactions
+ statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
+)
+
+var (
+ // Metrics for the pending pool
+ pendingDiscardMeter = metrics.NewRegisteredMeter("txpool/pending/discard", nil)
+ pendingReplaceMeter = metrics.NewRegisteredMeter("txpool/pending/replace", nil)
+ pendingRateLimitMeter = metrics.NewRegisteredMeter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting
+ pendingNofundsMeter = metrics.NewRegisteredMeter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds
+
+ // Metrics for the queued pool
+ queuedDiscardMeter = metrics.NewRegisteredMeter("txpool/queued/discard", nil)
+ queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil)
+ queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
+ queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds
+ queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime
+
+ // General tx metrics
+ knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil)
+ validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil)
+ invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil)
+ underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil)
+ overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil)
+
+ // throttleTxMeter counts how many transactions are rejected due to too-many-changes between
+ // txpool reorgs.
+ throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil)
+ // reorgDurationTimer measures how long time a txpool reorg takes.
+ reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil)
+ // dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected
+ // that this number is pretty low, since txpool reorgs happen very frequently.
+ dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015))
+
+ pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil)
+ queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
+ localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
+ slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
+
+ reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
+)
+
+// BlockChain defines the minimal set of methods needed to back a tx pool with
+// a chain. Exists to allow mocking the live chain out of tests.
+type BlockChain interface {
+ // Config retrieves the blockchain's chain configuration.
+ Config() *params.ChainConfig
+
+ // CurrentBlock returns the current head of the chain.
+ CurrentBlock() *types.Header
+
+ // GetBlock retrieves a specific block, used during pool resets.
+ GetBlock(hash common.Hash, number uint64) *types.Block
+
+ // StateAt returns a state database for a given root hash (generally the head).
+ StateAt(root common.Hash) (*state.StateDB, error)
+
+ // Engine retrieves the chain's consensus engine.
+ Engine() consensus.Engine
+
+ // GetHeader returns the hash corresponding to their hash.
+ GetHeader(common.Hash, uint64) *types.Header
+
+ // CurrentHeader retrieves the current header from the local chain.
+ CurrentHeader() *types.Header
+}
+
+// Config are the configuration parameters of the transaction pool.
+type Config struct {
+ Locals []common.Address // Addresses that should be treated by default as local
+ NoLocals bool // Whether local transaction handling should be disabled
+ Journal string // Journal of local transactions to survive node restarts
+ Rejournal time.Duration // Time interval to regenerate the local transaction journal
+
+ PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
+ PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)
+
+ AccountSlots uint64 // Number of executable transaction slots guaranteed per account
+ GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
+ AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
+ GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
+
+ Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
+}
+
+// DefaultConfig contains the default configurations for the transaction pool.
+var DefaultConfig = Config{
+ Journal: "transactions.rlp",
+ Rejournal: time.Hour,
+
+ PriceLimit: 0,
+ PriceBump: 10,
+
+ AccountSlots: 16,
+ GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio
+ AccountQueue: 64,
+ GlobalQueue: 1024,
+
+ Lifetime: 3 * time.Hour,
+}
+
+var defaultMaxTip = big.NewInt(1000 * params.GWei)
+
+// sanitize checks the provided user configurations and changes anything that's
+// unreasonable or unworkable.
+func (config *Config) sanitize() Config {
+ conf := *config
+ if conf.Rejournal < time.Second {
+ log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second)
+ conf.Rejournal = time.Second
+ }
+ if conf.PriceBump < 1 {
+ log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
+ conf.PriceBump = DefaultConfig.PriceBump
+ }
+ if conf.AccountSlots < 1 {
+ log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots)
+ conf.AccountSlots = DefaultConfig.AccountSlots
+ }
+ if conf.GlobalSlots < 1 {
+ log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots)
+ conf.GlobalSlots = DefaultConfig.GlobalSlots
+ }
+ if conf.AccountQueue < 1 {
+ log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue)
+ conf.AccountQueue = DefaultConfig.AccountQueue
+ }
+ if conf.GlobalQueue < 1 {
+ log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue)
+ conf.GlobalQueue = DefaultConfig.GlobalQueue
+ }
+ if conf.Lifetime < 1 {
+ log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime)
+ conf.Lifetime = DefaultConfig.Lifetime
+ }
+ return conf
+}
+
+// LegacyPool contains all currently known transactions. Transactions
+// enter the pool when they are received from the network or submitted
+// locally. They exit the pool when they are included in the blockchain.
+//
+// The pool separates processable transactions (which can be applied to the
+// current state) and future transactions. Transactions move between those
+// two states over time as they are received and processed.
+type LegacyPool struct {
+ config Config
+ chainconfig *params.ChainConfig
+ chain BlockChain
+ gasTip atomic.Pointer[big.Int]
+ txFeed event.Feed
+ scope event.SubscriptionScope
+ signer types.Signer
+ mu sync.RWMutex
+
+ currentHead atomic.Pointer[types.Header] // Current head of the blockchain
+ currentState *state.StateDB // Current state in the blockchain head
+ pendingNonces *noncer // Pending state tracking virtual nonces
+
+ locals *accountSet // Set of local transaction to exempt from eviction rules
+ journal *journal // Journal of local transaction to back up to disk
+
+ pending map[common.Address]*list // All currently processable transactions
+ queue map[common.Address]*list // Queued but non-processable transactions
+ beats map[common.Address]time.Time // Last heartbeat from each known account
+ all *lookup // All transactions to allow lookups
+ priced *pricedList // All transactions sorted by price
+
+ reqResetCh chan *txpoolResetRequest
+ reqPromoteCh chan *accountSet
+ queueTxEventCh chan *types.Transaction
+ reorgDoneCh chan chan struct{}
+ reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop
+ wg sync.WaitGroup // tracks loop, scheduleReorgLoop
+ initDoneCh chan struct{} // is closed once the pool is initialized (for tests)
+
+ changesSinceReorg int // A counter for how many drops we've performed in-between reorg.
+
+ isSigner func(address common.Address) bool
+ trc21FeeCapacity map[common.Address]*big.Int
+}
+
+type txpoolResetRequest struct {
+ oldHead, newHead *types.Header
+}
+
+// New creates a new transaction pool to gather, sort and filter inbound
+// transactions from the network.
+func New(config Config, chain BlockChain) *LegacyPool {
+ // Sanitize the input to ensure no vulnerable gas prices are set
+ config = (&config).sanitize()
+
+ // Create the transaction pool with its initial settings
+ pool := &LegacyPool{
+ config: config,
+ chainconfig: chain.Config(),
+ chain: chain,
+ signer: types.LatestSigner(chain.Config()),
+ pending: make(map[common.Address]*list),
+ queue: make(map[common.Address]*list),
+ beats: make(map[common.Address]time.Time),
+ all: newLookup(),
+ reqResetCh: make(chan *txpoolResetRequest),
+ reqPromoteCh: make(chan *accountSet),
+ queueTxEventCh: make(chan *types.Transaction),
+ reorgDoneCh: make(chan chan struct{}),
+ reorgShutdownCh: make(chan struct{}),
+ initDoneCh: make(chan struct{}),
+ trc21FeeCapacity: map[common.Address]*big.Int{},
+ }
+ pool.locals = newAccountSet(pool.signer)
+ for _, addr := range config.Locals {
+ log.Info("Setting new local account", "address", addr)
+ pool.locals.add(addr)
+ }
+ pool.priced = newPricedList(pool.all)
+
+ if !config.NoLocals && config.Journal != "" {
+ pool.journal = newTxJournal(config.Journal)
+ }
+ return pool
+}
+
+// Filter returns whether the given transaction can be consumed by the legacy
+// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
+func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
+ switch tx.Type() {
+ case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
+ return true
+ default:
+ return false
+ }
+}
+
+// Init sets the gas price needed to keep a transaction in the pool and the chain
+// head to allow balance / nonce checks. The transaction journal will be loaded
+// from disk and filtered based on the provided starting settings. The internal
+// goroutines will be spun up and the pool deemed operational afterwards.
+func (pool *LegacyPool) Init(gasTip *big.Int, head *types.Header) error {
+ // Set the basic pool parameters
+ pool.gasTip.Store(gasTip)
+ pool.reset(nil, head)
+
+ // Start the reorg loop early, so it can handle requests generated during
+ // journal loading.
+ pool.wg.Go(pool.scheduleReorgLoop)
+
+ // If local transactions and journaling is enabled, load from disk
+ if pool.journal != nil {
+ if err := pool.journal.load(pool.addLocals); err != nil {
+ log.Warn("Failed to load transaction journal", "err", err)
+ }
+ if err := pool.journal.rotate(pool.local()); err != nil {
+ log.Warn("Failed to rotate transaction journal", "err", err)
+ }
+ }
+ pool.wg.Go(pool.loop)
+ return nil
+}
+
+// loop is the transaction pool's main event loop, waiting for and reacting to
+// outside blockchain events as well as for various reporting and transaction
+// eviction events.
+func (pool *LegacyPool) loop() {
+ var (
+ prevPending, prevQueued, prevStales int
+
+ // Start the stats reporting and transaction eviction tickers
+ report = time.NewTicker(statsReportInterval)
+ evict = time.NewTicker(evictionInterval)
+ journal = time.NewTicker(pool.config.Rejournal)
+ )
+ defer report.Stop()
+ defer evict.Stop()
+ defer journal.Stop()
+
+ // Notify tests that the init phase is done
+ close(pool.initDoneCh)
+ for {
+ select {
+ // Handle pool shutdown
+ case <-pool.reorgShutdownCh:
+ return
+
+ // Handle stats reporting ticks
+ case <-report.C:
+ pool.mu.RLock()
+ pending, queued := pool.stats()
+ pool.mu.RUnlock()
+ stales := int(pool.priced.stales.Load())
+
+ if pending != prevPending || queued != prevQueued || stales != prevStales {
+ log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales)
+ prevPending, prevQueued, prevStales = pending, queued, stales
+ }
+
+ // Handle inactive account transaction eviction
+ case <-evict.C:
+ pool.mu.Lock()
+ for addr := range pool.queue {
+ // Skip local transactions from the eviction mechanism
+ if pool.locals.contains(addr) {
+ continue
+ }
+ // Any non-locals old enough should be removed
+ if time.Since(pool.beats[addr]) > pool.config.Lifetime {
+ list := pool.queue[addr].Flatten()
+ for _, tx := range list {
+ pool.removeTx(tx.Hash(), true)
+ }
+ queuedEvictionMeter.Mark(int64(len(list)))
+ }
+ }
+ pool.mu.Unlock()
+
+ // Handle local transaction journal rotation
+ case <-journal.C:
+ if pool.journal != nil {
+ pool.mu.Lock()
+ if err := pool.journal.rotate(pool.local()); err != nil {
+ log.Warn("Failed to rotate local tx journal", "err", err)
+ }
+ pool.mu.Unlock()
+ }
+ }
+ }
+}
+
+// Close terminates the transaction pool.
+func (pool *LegacyPool) Close() error {
+ // Unsubscribe all subscriptions registered from txpool
+ pool.scope.Close()
+
+ // Terminate the pool reorger and return
+ close(pool.reorgShutdownCh)
+ pool.wg.Wait()
+
+ if pool.journal != nil {
+ pool.journal.close()
+ }
+ log.Info("Transaction pool stopped")
+ return nil
+}
+
+// Reset implements txpool.SubPool, allowing the legacy pool's internal state to be
+// kept in sync with the main transaction pool's internal state.
+func (pool *LegacyPool) Reset(oldHead, newHead *types.Header) {
+ wait := pool.requestReset(oldHead, newHead)
+ <-wait
+}
+
+// SubscribeTransactions registers a subscription of NewTxsEvent and
+// starts sending event to the given channel.
+func (pool *LegacyPool) SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription {
+ return pool.scope.Track(pool.txFeed.Subscribe(ch))
+}
+
+// SetGasTip updates the minimum gas tip required by the transaction pool for a
+// new transaction, and drops all transactions below this threshold. Negative
+// gas prices and prices exceeding 1000 GWei are considered invalid and will be
+// rejected without updating the threshold.
+func (pool *LegacyPool) SetGasTip(tip *big.Int) error {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ if tip == nil {
+ log.Warn("Reject nil gas tip")
+ return errors.New("reject nil gas tip")
+ }
+ if tip.Sign() < 0 {
+ log.Warn("Reject invalid gas tip", "tip", tip)
+ return fmt.Errorf("reject negative gas tip: %v", tip)
+ }
+ if tip.Cmp(defaultMaxTip) > 0 {
+ log.Warn("Reject invalid gas tip", "tip", tip, "max", defaultMaxTip)
+ return fmt.Errorf("reject too high gas tip: %v, maximum: %v", tip, defaultMaxTip)
+ }
+
+ old := pool.gasTip.Load()
+ pool.gasTip.Store(new(big.Int).Set(tip))
+
+ // If the min miner fee increased, remove transactions below the new threshold
+ if tip.Cmp(old) > 0 {
+ // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead
+ drop := pool.all.RemotesBelowTip(tip)
+ for _, tx := range drop {
+ pool.removeTx(tx.Hash(), false)
+ }
+ pool.priced.Removed(len(drop))
+ }
+ log.Info("Transaction pool tip threshold updated", "tip", tip)
+ return nil
+}
+
+// Nonce returns the next nonce of an account, with all transactions executable
+// by the pool already applied on top.
+func (pool *LegacyPool) Nonce(addr common.Address) uint64 {
+ pool.mu.RLock()
+ defer pool.mu.RUnlock()
+
+ return pool.pendingNonces.get(addr)
+}
+
+// Stats retrieves the current pool stats, namely the number of pending and the
+// number of queued (non-executable) transactions.
+func (pool *LegacyPool) Stats() (int, int) {
+ pool.mu.RLock()
+ defer pool.mu.RUnlock()
+
+ return pool.stats()
+}
+
+// stats retrieves the current pool stats, namely the number of pending and the
+// number of queued (non-executable) transactions.
+func (pool *LegacyPool) stats() (int, int) {
+ pending := 0
+ for _, list := range pool.pending {
+ pending += list.Len()
+ }
+ queued := 0
+ for _, list := range pool.queue {
+ queued += list.Len()
+ }
+ return pending, queued
+}
+
+// Content retrieves the data content of the transaction pool, returning all the
+// pending as well as queued transactions, grouped by account and sorted by nonce.
+func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
+ for addr, list := range pool.pending {
+ pending[addr] = list.Flatten()
+ }
+ queued := make(map[common.Address][]*types.Transaction, len(pool.queue))
+ for addr, list := range pool.queue {
+ queued[addr] = list.Flatten()
+ }
+ return pending, queued
+}
+
+// ContentFrom retrieves the data content of the transaction pool, returning the
+// pending as well as queued transactions of this address, grouped by nonce.
+func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
+ pool.mu.RLock()
+ defer pool.mu.RUnlock()
+
+ var pending []*types.Transaction
+ if list, ok := pool.pending[addr]; ok {
+ pending = list.Flatten()
+ }
+ var queued []*types.Transaction
+ if list, ok := pool.queue[addr]; ok {
+ queued = list.Flatten()
+ }
+ return pending, queued
+}
+
+// Pending retrieves all currently processable transactions, grouped by origin
+// account and sorted by nonce. The returned transaction set is a copy and can be
+// freely modified by calling code.
+//
+// The enforceTips parameter can be used to do an extra filtering on the pending
+// transactions and only return those whose **effective** tip is large enough in
+// the next pending execution environment.
+func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
+ for addr, list := range pool.pending {
+ txs := list.Flatten()
+
+ // If the miner requests tip enforcement, cap the lists now
+ if enforceTips && !pool.locals.contains(addr) {
+ for i, tx := range txs {
+ if !tx.IsSpecialTransaction() && tx.EffectiveGasTipIntCmp(pool.gasTip.Load(), pool.priced.urgent.baseFee) < 0 {
+ txs = txs[:i]
+ break
+ }
+ }
+ }
+ if len(txs) > 0 {
+ pending[addr] = txs
+ }
+ }
+ return pending
+}
+
+// Locals retrieves the accounts currently considered local by the pool.
+func (pool *LegacyPool) Locals() []common.Address {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ return pool.locals.flatten()
+}
+
+// local retrieves all currently known local transactions, grouped by origin
+// account and sorted by nonce. The returned transaction set is a copy and can be
+// freely modified by calling code.
+func (pool *LegacyPool) local() map[common.Address]types.Transactions {
+ txs := make(map[common.Address]types.Transactions)
+ for addr := range pool.locals.accounts {
+ if pending := pool.pending[addr]; pending != nil {
+ txs[addr] = append(txs[addr], pending.Flatten()...)
+ }
+ if queued := pool.queue[addr]; queued != nil {
+ txs[addr] = append(txs[addr], queued.Flatten()...)
+ }
+ }
+ return txs
+}
+
+// validateTxBasics checks whether a transaction is valid according to the consensus
+// rules, but does not check state-dependent validation such as sufficient balance.
+// This check is meant as an early check which only needs to be performed once,
+// and does not require the pool mutex to be held.
+func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) error {
+ opts := &txpool.ValidationOptions{
+ Config: pool.chainconfig,
+ Accept: 0 |
+ 1< 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, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
+ underpricedTxMeter.Mark(1)
+ return false, txpool.ErrUnderpriced
+ }
+ // We're about to replace a transaction. The reorg does a more thorough
+ // analysis of what to remove and how, but it runs async. We don't want to
+ // do too many replacements between reorg-runs, so we cap the number of
+ // replacements to 25% of the slots
+ if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) {
+ throttleTxMeter.Mark(1)
+ return false, ErrTxPoolOverflow
+ }
+
+ // New transaction is better than our worse ones, make room for it.
+ // If it's a local transaction, forcibly discard all available transactions.
+ // Otherwise if we can't make enough room for new one, abort the operation.
+ drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal)
+
+ // Special case, we still can't make the room for the new remote one.
+ if !isLocal && !success {
+ log.Trace("Discarding overflown transaction", "hash", hash)
+ overflowedTxMeter.Mark(1)
+ return false, ErrTxPoolOverflow
+ }
+
+ // If the new transaction is a future transaction it should never churn pending transactions
+ if !isLocal && pool.isGapped(from, tx) {
+ var replacesPending bool
+ for _, dropTx := range drop {
+ dropSender, _ := types.Sender(pool.signer, dropTx)
+ if list := pool.pending[dropSender]; list != nil && list.Contains(dropTx.Nonce()) {
+ replacesPending = true
+ break
+ }
+ }
+ // Add all transactions back to the priced queue
+ if replacesPending {
+ for _, dropTx := range drop {
+ pool.priced.Put(dropTx, false)
+ }
+ log.Trace("Discarding future transaction replacing pending tx", "hash", hash)
+ return false, txpool.ErrFutureReplacePending
+ }
+ }
+
+ // Kick out the underpriced remote transactions.
+ for _, tx := range drop {
+ log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
+ underpricedTxMeter.Mark(1)
+ dropped := pool.removeTx(tx.Hash(), false)
+ pool.changesSinceReorg += dropped
+ }
+ }
+
+ // Try to replace an existing transaction in the pending pool
+ if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) {
+ // Nonce already pending, check if required price bump is met
+ inserted, old := list.Add(tx, pool.config.PriceBump)
+ if !inserted {
+ pendingDiscardMeter.Mark(1)
+ return false, txpool.ErrReplaceUnderpriced
+ }
+ // New transaction is better, replace old one
+ if old != nil {
+ pool.all.Remove(old.Hash())
+ pool.priced.Removed(1)
+ pendingReplaceMeter.Mark(1)
+ }
+ pool.all.Add(tx, isLocal)
+ pool.priced.Put(tx, isLocal)
+ pool.journalTx(from, tx)
+ pool.queueTxEvent(tx)
+ log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
+
+ // Successful promotion, bump the heartbeat
+ pool.beats[from] = time.Now()
+ return old != nil, nil
+ }
+ // New transaction isn't replacing a pending one, push into queue
+ replaced, err = pool.enqueueTx(hash, tx, isLocal, true)
+ if err != nil {
+ return false, err
+ }
+ // Mark local addresses and journal local transactions
+ if local && !pool.locals.contains(from) {
+ log.Info("Setting new local account", "address", from)
+ pool.locals.add(from)
+ pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time.
+ }
+ if isLocal {
+ localGauge.Inc(1)
+ }
+ pool.journalTx(from, tx)
+
+ log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
+ return replaced, nil
+}
+
+// isGapped reports whether the given transaction is immediately executable.
+func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) bool {
+ // Short circuit if transaction falls within the scope of the pending list
+ // or matches the next pending nonce which can be promoted as an executable
+ // transaction afterwards. Note, the tx staleness is already checked in
+ // 'validateTx' function previously.
+ next := pool.pendingNonces.get(from)
+ if tx.Nonce() <= next {
+ return false
+ }
+ // The transaction has a nonce gap with pending list, it's only considered
+ // as executable if transactions in queue can fill up the nonce gap.
+ queue, ok := pool.queue[from]
+ if !ok {
+ return true
+ }
+ for nonce := next; nonce < tx.Nonce(); nonce++ {
+ if !queue.Contains(nonce) {
+ return true // txs in queue can't fill up the nonce gap
+ }
+ }
+ return false
+}
+
+// enqueueTx inserts a new transaction into the non-executable transaction queue.
+//
+// Note, this method assumes the pool lock is held!
+func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) {
+ // Try to insert the transaction into the future queue
+ from, _ := types.Sender(pool.signer, tx) // already validated
+ if pool.queue[from] == nil {
+ pool.queue[from] = newList(false)
+ }
+ inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
+ if !inserted {
+ // An older transaction was better, discard this
+ queuedDiscardMeter.Mark(1)
+ return false, txpool.ErrReplaceUnderpriced
+ }
+ // Discard any previous transaction and mark this
+ if old != nil {
+ pool.all.Remove(old.Hash())
+ pool.priced.Removed(1)
+ queuedReplaceMeter.Mark(1)
+ } else {
+ // Nothing was replaced, bump the queued counter
+ queuedGauge.Inc(1)
+ }
+ // If the transaction isn't in lookup set but it's expected to be there,
+ // show the error log.
+ if pool.all.Get(hash) == nil && !addAll {
+ log.Error("Missing transaction in lookup set, please report the issue", "hash", hash)
+ }
+ if addAll {
+ pool.all.Add(tx, local)
+ pool.priced.Put(tx, local)
+ }
+ // If we never record the heartbeat, do it right now.
+ if _, exist := pool.beats[from]; !exist {
+ pool.beats[from] = time.Now()
+ }
+ return old != nil, nil
+}
+
+// journalTx adds the specified transaction to the local disk journal if it is
+// deemed to have been sent from a local account.
+func (pool *LegacyPool) journalTx(from common.Address, tx *types.Transaction) {
+ // Only journal if it's enabled and the transaction is local
+ if pool.journal == nil || !pool.locals.contains(from) {
+ return
+ }
+ if err := pool.journal.insert(tx); err != nil {
+ log.Warn("Failed to journal local transaction", "err", err)
+ }
+}
+
+// promoteTx adds a transaction to the pending (processable) list of transactions
+// and returns whether it was inserted or an older was better.
+//
+// Note, this method assumes the pool lock is held!
+func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
+ // Try to insert the transaction into the pending queue
+ if pool.pending[addr] == nil {
+ pool.pending[addr] = newList(true)
+ }
+ list := pool.pending[addr]
+
+ inserted, old := list.Add(tx, pool.config.PriceBump)
+ if !inserted {
+ // An older transaction was better, discard this
+ pool.all.Remove(hash)
+ pool.priced.Removed(1)
+
+ pendingDiscardMeter.Mark(1)
+ return false
+ }
+ // Otherwise discard any previous transaction and mark this
+ if old != nil {
+ pool.all.Remove(old.Hash())
+ pool.priced.Removed(1)
+ pendingReplaceMeter.Mark(1)
+ } else {
+ // Nothing was replaced, bump the pending counter
+ pendingGauge.Inc(1)
+ }
+ // Set the potentially new pending nonce and notify any subsystems of the new tx
+ pool.pendingNonces.set(addr, tx.Nonce()+1)
+
+ // Successful promotion, bump the heartbeat
+ pool.beats[addr] = time.Now()
+ return true
+}
+
+func (pool *LegacyPool) promoteSpecialTx(addr common.Address, tx *types.Transaction, isLocal bool) (bool, error) {
+ // Try to insert the transaction into the pending queue
+ if pool.pending[addr] == nil {
+ pool.pending[addr] = newList(true)
+ }
+ list := pool.pending[addr]
+
+ old := list.txs.Get(tx.Nonce())
+ if old.IsSpecialTransaction() {
+ return false, txpool.ErrDuplicateSpecialTransaction
+ }
+ // Otherwise discard any previous transaction and mark this
+ if old != nil {
+ pool.all.Remove(old.Hash())
+ pool.priced.Removed(1)
+ pendingReplaceMeter.Mark(1)
+ } else {
+ // Nothing was replaced, bump the pending counter
+ pendingGauge.Inc(1)
+ }
+ list.txs.Put(tx)
+ if cost := tx.Cost(); list.costcap.Cmp(cost) < 0 {
+ list.costcap = cost
+ }
+ if gas := tx.Gas(); list.gascap < gas {
+ list.gascap = gas
+ }
+ // Failsafe to work around direct pending inserts (tests)
+ if pool.all.Get(tx.Hash()) == nil {
+ pool.all.Add(tx, isLocal)
+ }
+ // Set the potentially new pending nonce and notify any subsystems of the new tx
+ pool.beats[addr] = time.Now()
+ pool.pendingNonces.set(addr, tx.Nonce()+1)
+ pool.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}})
+ return true, nil
+}
+
+// Add enqueues a batch of transactions into the pool if they are valid. Depending
+// on the local flag, full pricing contraints will or will not be applied.
+//
+// If sync is set, the method will block until all internal maintenance related
+// to the add is finished. Only use this during tests for determinism!
+func (pool *LegacyPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error {
+ unwrapped := make([]*types.Transaction, len(txs))
+ for i, tx := range txs {
+ unwrapped[i] = tx.Tx
+ }
+ return pool.addTxs(unwrapped, local, sync)
+}
+
+// AddLocals enqueues a batch of transactions into the pool if they are valid, marking the
+// senders as a local ones, ensuring they go around the local pricing constraints.
+//
+// This method is used to add transactions from the RPC API and performs synchronous pool
+// reorganization and event propagation.
+func (pool *LegacyPool) addLocals(txs []*types.Transaction) []error {
+ return pool.addTxs(txs, !pool.config.NoLocals, true)
+}
+
+// AddLocal enqueues a single local transaction into the pool if it is valid. This is
+// a convenience wrapper around AddLocals.
+func (pool *LegacyPool) addLocal(tx *types.Transaction) error {
+ errs := pool.addLocals([]*types.Transaction{tx})
+ return errs[0]
+}
+
+// AddRemotes enqueues a batch of transactions into the pool if they are valid. If the
+// senders are not among the locally tracked ones, full pricing constraints will apply.
+//
+// This method is used to add transactions from the p2p network and does not wait for pool
+// reorganization and internal event propagation.
+func (pool *LegacyPool) AddRemotes(txs []*types.Transaction) []error {
+ return pool.addTxs(txs, false, false)
+}
+
+// addRemote enqueues a single transaction into the pool if it is valid. This is a convenience
+// wrapper around AddRemotes.
+func (pool *LegacyPool) addRemote(tx *types.Transaction) error {
+ errs := pool.AddRemotes([]*types.Transaction{tx})
+ return errs[0]
+}
+
+// AddRemotesSync is like AddRemotes, but waits for pool reorganization. Tests use this method.
+func (pool *LegacyPool) addRemotesSync(txs []*types.Transaction) []error {
+ return pool.addTxs(txs, false, true)
+}
+
+// This is like AddRemotes with a single transaction, but waits for pool reorganization. Tests use this method.
+func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
+ return pool.addTxs([]*types.Transaction{tx}, false, true)[0]
+}
+
+// addTxs attempts to queue a batch of transactions if they are valid.
+func (pool *LegacyPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
+ // Filter out known ones without obtaining the pool lock or recovering signatures
+ var (
+ errs = make([]error, len(txs))
+ news = make([]*types.Transaction, 0, len(txs))
+ )
+ for i, tx := range txs {
+ // If the transaction is known, pre-set the error slot
+ if pool.all.Get(tx.Hash()) != nil {
+ errs[i] = ErrAlreadyKnown
+ knownTxMeter.Mark(1)
+ continue
+ }
+ // Exclude transactions with basic errors, e.g invalid signatures and
+ // insufficient intrinsic gas as soon as possible and cache senders
+ // in transactions before obtaining lock
+ if err := pool.validateTxBasics(tx, local); err != nil {
+ errs[i] = err
+ invalidTxMeter.Mark(1)
+ continue
+ }
+ // Accumulate all unknown transactions for deeper processing
+ news = append(news, tx)
+ }
+ if len(news) == 0 {
+ return errs
+ }
+
+ // Process all the new transaction and merge any errors into the original slice
+ pool.mu.Lock()
+ newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
+ pool.mu.Unlock()
+
+ var nilSlot = 0
+ for _, err := range newErrs {
+ for errs[nilSlot] != nil {
+ nilSlot++
+ }
+ errs[nilSlot] = err
+ nilSlot++
+ }
+ // Reorg the pool internals if needed and return
+ done := pool.requestPromoteExecutables(dirtyAddrs)
+ if sync {
+ <-done
+ }
+ return errs
+}
+
+// addTxsLocked attempts to queue a batch of transactions if they are valid.
+// The transaction pool lock must be held.
+func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) {
+ dirty := newAccountSet(pool.signer)
+ errs := make([]error, len(txs))
+ for i, tx := range txs {
+ replaced, err := pool.add(tx, local)
+ errs[i] = err
+ if err == nil && !replaced {
+ dirty.addTx(tx)
+ }
+ }
+ validTxMeter.Mark(int64(len(dirty.accounts)))
+ return errs, dirty
+}
+
+// Status returns the status (unknown/pending/queued) of a batch of transactions
+// identified by their hashes.
+func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
+ tx := pool.get(hash)
+ if tx == nil {
+ return txpool.TxStatusUnknown
+ }
+ from, _ := types.Sender(pool.signer, tx) // already validated
+
+ pool.mu.RLock()
+ defer pool.mu.RUnlock()
+
+ if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
+ return txpool.TxStatusPending
+ } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
+ return txpool.TxStatusQueued
+ }
+ return txpool.TxStatusUnknown
+}
+
+// Get returns a transaction if it is contained in the pool and nil otherwise.
+func (pool *LegacyPool) Get(hash common.Hash) *txpool.Transaction {
+ tx := pool.get(hash)
+ if tx == nil {
+ return nil
+ }
+ return &txpool.Transaction{Tx: tx}
+}
+
+// get returns a transaction if it is contained in the pool and nil otherwise.
+func (pool *LegacyPool) get(hash common.Hash) *types.Transaction {
+ return pool.all.Get(hash)
+}
+
+// Has returns an indicator whether txpool has a transaction cached with the
+// given hash.
+func (pool *LegacyPool) Has(hash common.Hash) bool {
+ return pool.all.Get(hash) != nil
+}
+
+// removeTx removes a single transaction from the queue, moving all subsequent
+// transactions back to the future queue.
+// Returns the number of transactions removed from the pending queue.
+func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool) int {
+ // Fetch the transaction we wish to delete
+ tx := pool.all.Get(hash)
+ if tx == nil {
+ return 0
+ }
+ addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
+
+ // Remove it from the list of known transactions
+ pool.all.Remove(hash)
+ if outofbound {
+ pool.priced.Removed(1)
+ }
+ if pool.locals.contains(addr) {
+ localGauge.Dec(1)
+ }
+ // Remove the transaction from the pending lists and reset the account nonce
+ if pending := pool.pending[addr]; pending != nil {
+ if removed, invalids := pending.Remove(tx); removed {
+ // If no more pending transactions are left, remove the list
+ if pending.Empty() {
+ delete(pool.pending, addr)
+ }
+ // Postpone any invalidated transactions
+ for _, tx := range invalids {
+ // Internal shuffle shouldn't touch the lookup set.
+ pool.enqueueTx(tx.Hash(), tx, false, false)
+ }
+ // Update the account nonce if needed
+ pool.pendingNonces.setIfLower(addr, tx.Nonce())
+ // Reduce the pending counter
+ pendingGauge.Dec(int64(1 + len(invalids)))
+ return 1 + len(invalids)
+ }
+ }
+ // Transaction is in the future queue
+ if future := pool.queue[addr]; future != nil {
+ if removed, _ := future.Remove(tx); removed {
+ // Reduce the queued counter
+ queuedGauge.Dec(1)
+ }
+ if future.Empty() {
+ delete(pool.queue, addr)
+ delete(pool.beats, addr)
+ }
+ }
+ return 0
+}
+
+// requestReset requests a pool reset to the new head block.
+// The returned channel is closed when the reset has occurred.
+func (pool *LegacyPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} {
+ select {
+ case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}:
+ return <-pool.reorgDoneCh
+ case <-pool.reorgShutdownCh:
+ return pool.reorgShutdownCh
+ }
+}
+
+// requestPromoteExecutables requests transaction promotion checks for the given addresses.
+// The returned channel is closed when the promotion checks have occurred.
+func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} {
+ select {
+ case pool.reqPromoteCh <- set:
+ return <-pool.reorgDoneCh
+ case <-pool.reorgShutdownCh:
+ return pool.reorgShutdownCh
+ }
+}
+
+// queueTxEvent enqueues a transaction event to be sent in the next reorg run.
+func (pool *LegacyPool) queueTxEvent(tx *types.Transaction) {
+ select {
+ case pool.queueTxEventCh <- tx:
+ case <-pool.reorgShutdownCh:
+ }
+}
+
+// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not
+// call those methods directly, but request them being run using requestReset and
+// requestPromoteExecutables instead.
+func (pool *LegacyPool) scheduleReorgLoop() {
+ var (
+ curDone chan struct{} // non-nil while runReorg is active
+ nextDone = make(chan struct{})
+ launchNextRun bool
+ reset *txpoolResetRequest
+ dirtyAccounts *accountSet
+ queuedEvents = make(map[common.Address]*sortedMap)
+ )
+ for {
+ // Launch next background reorg if needed
+ if curDone == nil && launchNextRun {
+ // Run the background reorg and announcements
+ go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents)
+
+ // Prepare everything for the next round of reorg
+ curDone, nextDone = nextDone, make(chan struct{})
+ launchNextRun = false
+
+ reset, dirtyAccounts = nil, nil
+ queuedEvents = make(map[common.Address]*sortedMap)
+ }
+
+ select {
+ case req := <-pool.reqResetCh:
+ // Reset request: update head if request is already pending.
+ if reset == nil {
+ reset = req
+ } else {
+ reset.newHead = req.newHead
+ }
+ launchNextRun = true
+ pool.reorgDoneCh <- nextDone
+
+ case req := <-pool.reqPromoteCh:
+ // Promote request: update address set if request is already pending.
+ if dirtyAccounts == nil {
+ dirtyAccounts = req
+ } else {
+ dirtyAccounts.merge(req)
+ }
+ launchNextRun = true
+ pool.reorgDoneCh <- nextDone
+
+ case tx := <-pool.queueTxEventCh:
+ // Queue up the event, but don't schedule a reorg. It's up to the caller to
+ // request one later if they want the events sent.
+ addr, _ := types.Sender(pool.signer, tx)
+ if _, ok := queuedEvents[addr]; !ok {
+ queuedEvents[addr] = newSortedMap()
+ }
+ queuedEvents[addr].Put(tx)
+
+ case <-curDone:
+ curDone = nil
+
+ case <-pool.reorgShutdownCh:
+ // Wait for current run to finish.
+ if curDone != nil {
+ <-curDone
+ }
+ close(nextDone)
+ return
+ }
+ }
+}
+
+// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
+func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) {
+ defer func(t0 time.Time) {
+ reorgDurationTimer.Update(time.Since(t0))
+ }(time.Now())
+ defer close(done)
+
+ var promoteAddrs []common.Address
+ if dirtyAccounts != nil && reset == nil {
+ // Only dirty accounts need to be promoted, unless we're resetting.
+ // For resets, all addresses in the tx queue will be promoted and
+ // the flatten operation can be avoided.
+ promoteAddrs = dirtyAccounts.flatten()
+ }
+ pool.mu.Lock()
+ if reset != nil {
+ // Reset from the old head to the new, rescheduling any reorged transactions
+ pool.reset(reset.oldHead, reset.newHead)
+
+ // Nonces were reset, discard any events that became stale
+ for addr := range events {
+ events[addr].Forward(pool.pendingNonces.get(addr))
+ if events[addr].Len() == 0 {
+ delete(events, addr)
+ }
+ }
+ // Reset needs promote for all addresses
+ promoteAddrs = make([]common.Address, 0, len(pool.queue))
+ for addr := range pool.queue {
+ promoteAddrs = append(promoteAddrs, addr)
+ }
+ }
+ // Check for pending transactions for every account that sent new ones
+ promoted := pool.promoteExecutables(promoteAddrs)
+
+ // If a new block appeared, validate the pool of pending transactions. This will
+ // remove any transaction that has been included in the block or was invalidated
+ // because of another transaction (e.g. higher gas price).
+ if reset != nil {
+ pool.demoteUnexecutables()
+ if reset.newHead != nil && pool.chainconfig.IsEIP1559(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) {
+ pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead)
+ pool.priced.SetBaseFee(pendingBaseFee)
+ }
+ // Update all accounts to the latest known pending nonce
+ nonces := make(map[common.Address]uint64, len(pool.pending))
+ for addr, list := range pool.pending {
+ highestPending := list.LastElement()
+ nonces[addr] = highestPending.Nonce() + 1
+ }
+ pool.pendingNonces.setAll(nonces)
+ }
+ // Ensure pool.queue and pool.pending sizes stay within the configured limits.
+ pool.truncatePending()
+ pool.truncateQueue()
+
+ dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg))
+ pool.changesSinceReorg = 0 // Reset change counter
+ pool.mu.Unlock()
+
+ // Notify subsystems for newly added transactions
+ for _, tx := range promoted {
+ addr, _ := types.Sender(pool.signer, tx)
+ if _, ok := events[addr]; !ok {
+ events[addr] = newSortedMap()
+ }
+ events[addr].Put(tx)
+ }
+ if len(events) > 0 {
+ var txs []*types.Transaction
+ for _, set := range events {
+ txs = append(txs, set.Flatten()...)
+ }
+ pool.txFeed.Send(core.NewTxsEvent{Txs: txs})
+ }
+}
+
+// reset retrieves the current state of the blockchain and ensures the content
+// of the transaction pool is valid with regard to the chain state.
+func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
+ // If we're reorging an old state, reinject all dropped transactions
+ var reinject types.Transactions
+
+ if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
+ // If the reorg is too deep, avoid doing it (will happen during fast sync)
+ oldNum := oldHead.Number.Uint64()
+ newNum := newHead.Number.Uint64()
+
+ if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
+ log.Debug("Skipping deep transaction reorg", "depth", depth)
+ } else {
+ // Reorg seems shallow enough to pull in all transactions into memory
+ var discarded, included types.Transactions
+ var (
+ rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
+ add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
+ )
+ if rem == nil {
+ // This can happen if a setHead is performed, where we simply discard the old
+ // head from the chain.
+ // If that is the case, we don't have the lost transactions anymore, and
+ // there's nothing to add
+ if newNum >= oldNum {
+ // If we reorged to a same or higher number, then it's not a case of setHead
+ log.Warn("Transaction pool reset with missing oldhead",
+ "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
+ return
+ }
+ // If the reorg ended up on a lower number, it's indicative of setHead being the cause
+ log.Debug("Skipping transaction reset caused by setHead",
+ "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
+ // We still need to update the current state s.th. the lost transactions can be readded by the user
+ } else {
+ for rem.NumberU64() > add.NumberU64() {
+ discarded = append(discarded, rem.Transactions()...)
+ if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
+ log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
+ return
+ }
+ }
+ for add.NumberU64() > rem.NumberU64() {
+ included = append(included, add.Transactions()...)
+ if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
+ log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
+ return
+ }
+ }
+ for rem.Hash() != add.Hash() {
+ discarded = append(discarded, rem.Transactions()...)
+ if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
+ log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
+ return
+ }
+ included = append(included, add.Transactions()...)
+ if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
+ log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
+ return
+ }
+ }
+ reinject = types.TxDifference(discarded, included)
+ }
+ }
+ }
+ // Initialize the internal state to the current head
+ if newHead == nil {
+ newHead = pool.chain.CurrentBlock() // Special case during testing
+ }
+ statedb, err := pool.chain.StateAt(newHead.Root)
+ if err != nil {
+ log.Error("Failed to reset txpool state", "err", err)
+ return
+ }
+ pool.currentHead.Store(newHead)
+ pool.currentState = statedb
+ pool.trc21FeeCapacity = statedb.GetTRC21FeeCapacityFromStateWithCache(newHead.Root)
+ pool.pendingNonces = newNoncer(statedb)
+
+ // Inject any transactions discarded due to reorgs
+ log.Debug("Reinjecting stale transactions", "count", len(reinject))
+ core.SenderCacher.Recover(pool.signer, reinject)
+ pool.addTxsLocked(reinject, false)
+}
+
+// promoteExecutables moves transactions that have become processable from the
+// future queue to the set of pending transactions. During this process, all
+// invalidated transactions (low nonce, low balance) are deleted.
+func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
+ log.Debug("start promoteExecutables")
+ defer func(start time.Time) {
+ log.Debug("end promoteExecutables", "time", common.PrettyDuration(time.Since(start)))
+ }(time.Now())
+
+ // Track the promoted transactions to broadcast them at once
+ var promoted []*types.Transaction
+
+ // Iterate over all accounts and promote any executable transactions
+ gasLimit := pool.currentHead.Load().GasLimit
+ for _, addr := range accounts {
+ list := pool.queue[addr]
+ if list == nil {
+ continue // Just in case someone calls with a non existing account
+ }
+ // Drop all transactions that are deemed too old (low nonce)
+ forwards := list.Forward(pool.currentState.GetNonce(addr))
+ for _, tx := range forwards {
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+ }
+ log.Trace("Removed old queued transactions", "count", len(forwards))
+ // Drop all transactions that are too costly (low balance or out of gas)
+ var number *big.Int = nil
+ if pool.chain.CurrentHeader() != nil {
+ number = pool.chain.CurrentHeader().Number
+ }
+ drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
+ for _, tx := range drops {
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+ }
+ log.Trace("Removed unpayable queued transactions", "count", len(drops))
+ queuedNofundsMeter.Mark(int64(len(drops)))
+
+ // Gather all executable transactions and promote them
+ readies := list.Ready(pool.pendingNonces.get(addr))
+ for _, tx := range readies {
+ hash := tx.Hash()
+ if pool.promoteTx(addr, hash, tx) {
+ promoted = append(promoted, tx)
+ }
+ }
+ log.Trace("Promoted queued transactions", "count", len(promoted))
+ queuedGauge.Dec(int64(len(readies)))
+
+ // Drop all transactions over the allowed limit
+ var caps types.Transactions
+ if !pool.locals.contains(addr) {
+ caps = list.Cap(int(pool.config.AccountQueue))
+ for _, tx := range caps {
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+ log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
+ }
+ queuedRateLimitMeter.Mark(int64(len(caps)))
+ }
+ // Mark all the items dropped as removed
+ pool.priced.Removed(len(forwards) + len(drops) + len(caps))
+ queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
+ if pool.locals.contains(addr) {
+ localGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
+ }
+ // Delete the entire queue entry if it became empty.
+ if list.Empty() {
+ delete(pool.queue, addr)
+ delete(pool.beats, addr)
+ }
+ }
+ return promoted
+}
+
+// truncatePending removes transactions from the pending queue if the pool is above the
+// pending limit. The algorithm tries to reduce transaction counts by an approximately
+// equal number for all for accounts with many pending transactions.
+func (pool *LegacyPool) truncatePending() {
+ pending := uint64(0)
+ for _, list := range pool.pending {
+ pending += uint64(list.Len())
+ }
+ if pending <= pool.config.GlobalSlots {
+ return
+ }
+
+ pendingBeforeCap := pending
+ // Assemble a spam order to penalize large transactors first
+ spammers := prque.New[int64, common.Address](nil)
+ for addr, list := range pool.pending {
+ // Only evict transactions from high rollers
+ if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
+ spammers.Push(addr, int64(list.Len()))
+ }
+ }
+ // Gradually drop transactions from offenders
+ offenders := []common.Address{}
+ for pending > pool.config.GlobalSlots && !spammers.Empty() {
+ // Retrieve the next offender if not local address
+ offender, _ := spammers.Pop()
+ offenders = append(offenders, offender)
+
+ // Equalize balances until all the same or below threshold
+ if len(offenders) > 1 {
+ // Calculate the equalization threshold for all current offenders
+ threshold := pool.pending[offender].Len()
+
+ // Iteratively reduce all offenders until below limit or threshold reached
+ for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
+ for i := 0; i < len(offenders)-1; i++ {
+ list := pool.pending[offenders[i]]
+
+ caps := list.Cap(list.Len() - 1)
+ for _, tx := range caps {
+ // Drop the transaction from the global pools too
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+
+ // Update the account nonce to the dropped transaction
+ pool.pendingNonces.setIfLower(offenders[i], tx.Nonce())
+ log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
+ }
+ pool.priced.Removed(len(caps))
+ pendingGauge.Dec(int64(len(caps)))
+ if pool.locals.contains(offenders[i]) {
+ localGauge.Dec(int64(len(caps)))
+ }
+ pending--
+ }
+ }
+ }
+ }
+
+ // If still above threshold, reduce to limit or min allowance
+ if pending > pool.config.GlobalSlots && len(offenders) > 0 {
+ for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
+ for _, addr := range offenders {
+ list := pool.pending[addr]
+
+ caps := list.Cap(list.Len() - 1)
+ for _, tx := range caps {
+ // Drop the transaction from the global pools too
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+
+ // Update the account nonce to the dropped transaction
+ pool.pendingNonces.setIfLower(addr, tx.Nonce())
+ log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
+ }
+ pool.priced.Removed(len(caps))
+ pendingGauge.Dec(int64(len(caps)))
+ if pool.locals.contains(addr) {
+ localGauge.Dec(int64(len(caps)))
+ }
+ pending--
+ }
+ }
+ }
+ pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending))
+}
+
+// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
+func (pool *LegacyPool) truncateQueue() {
+ queued := uint64(0)
+ for _, list := range pool.queue {
+ queued += uint64(list.Len())
+ }
+ if queued <= pool.config.GlobalQueue {
+ return
+ }
+
+ // Sort all accounts with queued transactions by heartbeat
+ addresses := make(addressesByHeartbeat, 0, len(pool.queue))
+ for addr := range pool.queue {
+ if !pool.locals.contains(addr) { // don't drop locals
+ addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
+ }
+ }
+ sort.Sort(sort.Reverse(addresses))
+
+ // Drop transactions until the total is below the limit or only locals remain
+ for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
+ addr := addresses[len(addresses)-1]
+ list := pool.queue[addr.address]
+
+ addresses = addresses[:len(addresses)-1]
+
+ // Drop all transactions if they are less than the overflow
+ if size := uint64(list.Len()); size <= drop {
+ for _, tx := range list.Flatten() {
+ pool.removeTx(tx.Hash(), true)
+ }
+ drop -= size
+ queuedRateLimitMeter.Mark(int64(size))
+ continue
+ }
+ // Otherwise drop only last few transactions
+ txs := list.Flatten()
+ for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
+ pool.removeTx(txs[i].Hash(), true)
+ drop--
+ queuedRateLimitMeter.Mark(1)
+ }
+ }
+}
+
+// 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 *LegacyPool) demoteUnexecutables() {
+ // Iterate over all accounts and demote any non-executable transactions
+ gasLimit := pool.currentHead.Load().GasLimit
+ for addr, list := range pool.pending {
+ nonce := pool.currentState.GetNonce(addr)
+
+ // Drop all transactions that are deemed too old (low nonce)
+ olds := list.Forward(nonce)
+ for _, tx := range olds {
+ hash := tx.Hash()
+ pool.all.Remove(hash)
+ log.Trace("Removed old pending transaction", "hash", hash)
+ }
+ // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
+ var number *big.Int = nil
+ if pool.chain.CurrentHeader() != nil {
+ number = pool.chain.CurrentHeader().Number
+ }
+ drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
+ for _, tx := range drops {
+ hash := tx.Hash()
+ 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 {
+ hash := tx.Hash()
+ log.Trace("Demoting pending transaction", "hash", hash)
+
+ // Internal shuffle shouldn't touch the lookup set.
+ pool.enqueueTx(hash, tx, false, false)
+ }
+ pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
+ if pool.locals.contains(addr) {
+ localGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
+ }
+ // If there's a gap in front, alert (should never happen) and postpone all transactions
+ if list.Len() > 0 && list.txs.Get(nonce) == nil {
+ gapped := list.Cap(0)
+ for _, tx := range gapped {
+ hash := tx.Hash()
+ log.Warn("Demoting invalidated transaction", "hash", hash)
+
+ // Internal shuffle shouldn't touch the lookup set.
+ pool.enqueueTx(hash, tx, false, false)
+ }
+ pendingGauge.Dec(int64(len(gapped)))
+ }
+ // Delete the entire pending entry if it became empty.
+ if list.Empty() {
+ delete(pool.pending, addr)
+ }
+ }
+}
+
+// SetSigner sets the function to identify signer addresses.
+func (pool *LegacyPool) SetSigner(f func(address common.Address) bool) {
+ pool.isSigner = f
+}
+
+// IsSigner checks if the given address is a signer.
+func (pool *LegacyPool) IsSigner(addr common.Address) bool {
+ return pool.isSigner != nil && pool.isSigner(addr)
+}
+
+// addressByHeartbeat is an account address tagged with its last activity timestamp.
+type addressByHeartbeat struct {
+ address common.Address
+ heartbeat time.Time
+}
+
+type addressesByHeartbeat []addressByHeartbeat
+
+func (a addressesByHeartbeat) Len() int { return len(a) }
+func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
+func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+// accountSet is simply a set of addresses to check for existence, and a signer
+// capable of deriving addresses from transactions.
+type accountSet struct {
+ accounts map[common.Address]struct{}
+ signer types.Signer
+ cache *[]common.Address
+}
+
+// newAccountSet creates a new address set with an associated signer for sender
+// derivations.
+func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet {
+ as := &accountSet{
+ accounts: make(map[common.Address]struct{}, len(addrs)),
+ signer: signer,
+ }
+ for _, addr := range addrs {
+ as.add(addr)
+ }
+ return as
+}
+
+// contains checks if a given address is contained within the set.
+func (as *accountSet) contains(addr common.Address) bool {
+ _, exist := as.accounts[addr]
+ return exist
+}
+
+func (as *accountSet) empty() bool {
+ return len(as.accounts) == 0
+}
+
+// containsTx checks if the sender of a given tx is within the set. If the sender
+// cannot be derived, this method returns false.
+func (as *accountSet) containsTx(tx *types.Transaction) bool {
+ if addr, err := types.Sender(as.signer, tx); err == nil {
+ return as.contains(addr)
+ }
+ return false
+}
+
+// add inserts a new address into the set to track.
+func (as *accountSet) add(addr common.Address) {
+ as.accounts[addr] = struct{}{}
+ as.cache = nil
+}
+
+// addTx adds the sender of tx into the set.
+func (as *accountSet) addTx(tx *types.Transaction) {
+ if addr, err := types.Sender(as.signer, tx); err == nil {
+ as.add(addr)
+ }
+}
+
+// flatten returns the list of addresses within this set, also caching it for later
+// reuse. The returned slice should not be changed!
+func (as *accountSet) flatten() []common.Address {
+ if as.cache == nil {
+ accounts := make([]common.Address, 0, len(as.accounts))
+ for account := range as.accounts {
+ accounts = append(accounts, account)
+ }
+ as.cache = &accounts
+ }
+ return *as.cache
+}
+
+// merge adds all addresses from the 'other' set into 'as'.
+func (as *accountSet) merge(other *accountSet) {
+ for addr := range other.accounts {
+ as.accounts[addr] = struct{}{}
+ }
+ as.cache = nil
+}
+
+// lookup is used internally by TxPool to track transactions while allowing
+// lookup without mutex contention.
+//
+// Note, although this type is properly protected against concurrent access, it
+// is **not** a type that should ever be mutated or even exposed outside of the
+// transaction pool, since its internal state is tightly coupled with the pools
+// internal mechanisms. The sole purpose of the type is to permit out-of-bound
+// peeking into the pool in TxPool.Get without having to acquire the widely scoped
+// TxPool.mu mutex.
+//
+// This lookup set combines the notion of "local transactions", which is useful
+// to build upper-level structure.
+type lookup struct {
+ slots int
+ lock sync.RWMutex
+ locals map[common.Hash]*types.Transaction
+ remotes map[common.Hash]*types.Transaction
+}
+
+// newLookup returns a new lookup structure.
+func newLookup() *lookup {
+ return &lookup{
+ locals: make(map[common.Hash]*types.Transaction),
+ remotes: make(map[common.Hash]*types.Transaction),
+ }
+}
+
+// Range calls f on each key and value present in the map. The callback passed
+// should return the indicator whether the iteration needs to be continued.
+// Callers need to specify which set (or both) to be iterated.
+func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ if local {
+ for key, value := range t.locals {
+ if !f(key, value, true) {
+ return
+ }
+ }
+ }
+ if remote {
+ for key, value := range t.remotes {
+ if !f(key, value, false) {
+ return
+ }
+ }
+ }
+}
+
+// Get returns a transaction if it exists in the lookup, or nil if not found.
+func (t *lookup) Get(hash common.Hash) *types.Transaction {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ if tx := t.locals[hash]; tx != nil {
+ return tx
+ }
+ return t.remotes[hash]
+}
+
+// GetLocal returns a transaction if it exists in the lookup, or nil if not found.
+func (t *lookup) GetLocal(hash common.Hash) *types.Transaction {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return t.locals[hash]
+}
+
+// GetRemote returns a transaction if it exists in the lookup, or nil if not found.
+func (t *lookup) GetRemote(hash common.Hash) *types.Transaction {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return t.remotes[hash]
+}
+
+// Count returns the current number of transactions in the lookup.
+func (t *lookup) Count() int {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return len(t.locals) + len(t.remotes)
+}
+
+// LocalCount returns the current number of local transactions in the lookup.
+func (t *lookup) LocalCount() int {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return len(t.locals)
+}
+
+// RemoteCount returns the current number of remote transactions in the lookup.
+func (t *lookup) RemoteCount() int {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return len(t.remotes)
+}
+
+// Slots returns the current number of slots used in the lookup.
+func (t *lookup) Slots() int {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ return t.slots
+}
+
+// Add adds a transaction to the lookup.
+func (t *lookup) Add(tx *types.Transaction, local bool) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ t.slots += numSlots(tx)
+ slotsGauge.Update(int64(t.slots))
+
+ if local {
+ t.locals[tx.Hash()] = tx
+ } else {
+ t.remotes[tx.Hash()] = tx
+ }
+}
+
+// Remove removes a transaction from the lookup.
+func (t *lookup) Remove(hash common.Hash) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ tx, ok := t.locals[hash]
+ if !ok {
+ tx, ok = t.remotes[hash]
+ }
+ if !ok {
+ log.Error("No transaction found to be deleted", "hash", hash)
+ return
+ }
+ t.slots -= numSlots(tx)
+ slotsGauge.Update(int64(t.slots))
+
+ delete(t.locals, hash)
+ delete(t.remotes, hash)
+}
+
+// RemoteToLocals migrates the transactions belongs to the given locals to locals
+// set. The assumption is held the locals set is thread-safe to be used.
+func (t *lookup) RemoteToLocals(locals *accountSet) int {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ var migrated int
+ for hash, tx := range t.remotes {
+ if locals.containsTx(tx) {
+ t.locals[hash] = tx
+ delete(t.remotes, hash)
+ migrated += 1
+ }
+ }
+ return migrated
+}
+
+// RemotesBelowTip finds all remote transactions below the given tip threshold.
+func (t *lookup) 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.GasTipCapIntCmp(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)
+}
diff --git a/core/txpool/txpool2_test.go b/core/txpool/legacypool/legacypool2_test.go
similarity index 85%
rename from core/txpool/txpool2_test.go
rename to core/txpool/legacypool/legacypool2_test.go
index 035bec9397..14c01eac6b 100644
--- a/core/txpool/txpool2_test.go
+++ b/core/txpool/legacypool/legacypool2_test.go
@@ -13,7 +13,7 @@
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"crypto/ecdsa"
@@ -34,7 +34,7 @@ func pricedValuedTransaction(nonce uint64, value int64, gaslimit uint64, gaspric
return tx
}
-func count(t *testing.T, pool *TxPool) (pending int, queued int) {
+func count(t *testing.T, pool *LegacyPool) (pending int, queued int) {
t.Helper()
pending, queued = pool.stats()
if err := validatePoolInternals(pool); err != nil {
@@ -43,7 +43,7 @@ func count(t *testing.T, pool *TxPool) (pending int, queued int) {
return pending, queued
}
-func fillPool(t testing.TB, pool *TxPool) {
+func fillPool(t testing.TB, pool *LegacyPool) {
t.Helper()
// Create a number of test accounts, fund them and make transactions
executableTxs := types.Transactions{}
@@ -57,8 +57,8 @@ func fillPool(t testing.TB, pool *TxPool) {
}
}
// Import the batch and verify that limits have been enforced
- pool.AddRemotesSync(executableTxs)
- pool.AddRemotesSync(nonExecutableTxs)
+ pool.addRemotesSync(executableTxs)
+ pool.addRemotesSync(nonExecutableTxs)
pending, queued := pool.Stats()
slots := pool.all.Slots()
// sanity-check that the test prerequisites are ok (pending full)
@@ -80,12 +80,13 @@ func TestTransactionFutureAttack(t *testing.T) {
// Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.GlobalQueue = 100
config.GlobalSlots = 100
- pool := New(config, eip1559Config, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
fillPool(t, pool)
pending, _ := pool.Stats()
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
@@ -97,7 +98,7 @@ func TestTransactionFutureAttack(t *testing.T) {
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key))
}
for i := 0; i < 5; i++ {
- pool.AddRemotesSync(futureTxs)
+ pool.addRemotesSync(futureTxs)
newPending, newQueued := count(t, pool)
t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots())
}
@@ -116,9 +117,10 @@ func TestTransactionFuture1559(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, eip1559Config, blockchain)
- defer pool.Stop()
+ blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts, fund them and make transactions
fillPool(t, pool)
@@ -132,7 +134,7 @@ func TestTransactionFuture1559(t *testing.T) {
for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ {
futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key))
}
- pool.AddRemotesSync(futureTxs)
+ pool.addRemotesSync(futureTxs)
}
newPending, _ := pool.Stats()
// Pending should not have been touched
@@ -148,9 +150,10 @@ func TestTransactionZAttack(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, eip1559Config, blockchain)
- defer pool.Stop()
+ blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts, fund them and make transactions
fillPool(t, pool)
@@ -182,7 +185,7 @@ func TestTransactionZAttack(t *testing.T) {
key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key))
- pool.AddRemotesSync(futureTxs)
+ pool.addRemotesSync(futureTxs)
}
overDraftTxs := types.Transactions{}
@@ -193,11 +196,11 @@ func TestTransactionZAttack(t *testing.T) {
overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key))
}
}
- pool.AddRemotesSync(overDraftTxs)
- pool.AddRemotesSync(overDraftTxs)
- pool.AddRemotesSync(overDraftTxs)
- pool.AddRemotesSync(overDraftTxs)
- pool.AddRemotesSync(overDraftTxs)
+ pool.addRemotesSync(overDraftTxs)
+ pool.addRemotesSync(overDraftTxs)
+ pool.addRemotesSync(overDraftTxs)
+ pool.addRemotesSync(overDraftTxs)
+ pool.addRemotesSync(overDraftTxs)
newPending, newQueued := count(t, pool)
newIvPending := countInvalidPending()
@@ -218,12 +221,13 @@ func TestTransactionZAttack(t *testing.T) {
func BenchmarkFutureAttack(b *testing.B) {
// Create the pool to test the limit enforcement with
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.GlobalQueue = 100
config.GlobalSlots = 100
- pool := New(config, eip1559Config, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
fillPool(b, pool)
key, _ := crypto.GenerateKey()
@@ -235,6 +239,6 @@ func BenchmarkFutureAttack(b *testing.B) {
}
b.ResetTimer()
for i := 0; i < 5; i++ {
- pool.AddRemotesSync(futureTxs)
+ pool.addRemotesSync(futureTxs)
}
}
diff --git a/core/txpool/txpool_test.go b/core/txpool/legacypool/legacypool_test.go
similarity index 87%
rename from core/txpool/txpool_test.go
rename to core/txpool/legacypool/legacypool_test.go
index 7be57393a1..4a618fbcf5 100644
--- a/core/txpool/txpool_test.go
+++ b/core/txpool/legacypool/legacypool_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"crypto/ecdsa"
@@ -35,6 +35,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
@@ -62,13 +63,14 @@ func init() {
}
type testBlockChain struct {
+ config *params.ChainConfig
gasLimit atomic.Uint64
statedb *state.StateDB
chainHeadFeed *event.Feed
}
-func newTestBlockChain(gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain {
- bc := testBlockChain{statedb: statedb, chainHeadFeed: new(event.Feed)}
+func newTestBlockChain(config *params.ChainConfig, gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain {
+ bc := testBlockChain{config: config, statedb: statedb, chainHeadFeed: new(event.Feed)}
bc.gasLimit.Store(gasLimit)
return &bc
}
@@ -86,7 +88,7 @@ func (bc *testBlockChain) CurrentHeader() *types.Header {
}
func (bc *testBlockChain) Config() *params.ChainConfig {
- return nil
+ return bc.config
}
func (bc *testBlockChain) CurrentBlock() *types.Header {
@@ -141,25 +143,27 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
return tx
}
-func setupPool() (*TxPool, *ecdsa.PrivateKey) {
+func setupPool() (*LegacyPool, *ecdsa.PrivateKey) {
return setupPoolWithConfig(params.TestChainConfig)
}
-func setupPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) {
+func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) {
diskdb := rawdb.NewMemoryDatabase()
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(diskdb))
- blockchain := newTestBlockChain(10000000, statedb, new(event.Feed))
+ blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed))
key, _ := crypto.GenerateKey()
- pool := New(testTxPoolConfig, config, blockchain)
-
+ pool := New(testTxPoolConfig, blockchain)
+ if err := pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock()); err != nil {
+ panic(err)
+ }
// wait for the pool to initialize
<-pool.initDoneCh
return pool, key
}
// validatePoolInternals checks various consistency invariants within the pool.
-func validatePoolInternals(pool *TxPool) error {
+func validatePoolInternals(pool *LegacyPool) error {
pool.mu.RLock()
defer pool.mu.RUnlock()
@@ -239,8 +243,7 @@ func (c *testChain) State() (*state.StateDB, error) {
// a state change between those fetches.
stdb := c.statedb
if *c.trigger {
- db := rawdb.NewMemoryDatabase()
- c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(db))
+ c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
// simulate that the new head block included tx0 and tx1
c.statedb.SetNonce(c.address, 2)
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
@@ -265,20 +268,21 @@ func TestStateChangeDuringReset(t *testing.T) {
// setup pool with 2 transaction in it
statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
- blockchain := &testChain{newTestBlockChain(1000000000, statedb, new(event.Feed)), address, &trigger}
+ blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger}
tx0 := pricedTransaction(0, 100000, big.NewInt(250000000), key)
tx1 := pricedTransaction(1, 100000, big.NewInt(250000000), key)
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
nonce := pool.Nonce(address)
if nonce != 0 {
t.Fatalf("Invalid nonce, want 0, got %d", nonce)
}
- pool.AddRemotesSync([]*types.Transaction{tx0, tx1})
+ pool.addRemotesSync([]*types.Transaction{tx0, tx1})
nonce = pool.Nonce(address)
if nonce != 2 {
@@ -295,13 +299,13 @@ func TestStateChangeDuringReset(t *testing.T) {
}
}
-func testAddBalance(pool *TxPool, addr common.Address, amount *big.Int) {
+func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) {
pool.mu.Lock()
pool.currentState.AddBalance(addr, amount, tracing.BalanceChangeUnspecified)
pool.mu.Unlock()
}
-func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) {
+func testSetNonce(pool *LegacyPool, addr common.Address, nonce uint64) {
pool.mu.Lock()
pool.currentState.SetNonce(addr, nonce)
pool.mu.Unlock()
@@ -311,27 +315,27 @@ func TestInvalidTransactions(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
tx := transaction(0, 100, key)
from, _ := deriveSender(tx)
// Intrinsic gas too low
testAddBalance(pool, from, big.NewInt(1))
- if err, want := pool.AddRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) {
+ if err, want := pool.addRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) {
t.Errorf("want %v have %v", want, err)
}
// Insufficient funds
tx = transaction(0, 100000, key)
- if err, want := pool.AddRemote(tx), core.ErrInsufficientFunds; !errors.Is(err, want) {
+ if err, want := pool.addRemote(tx), core.ErrInsufficientFunds; !errors.Is(err, want) {
t.Errorf("want %v have %v", want, err)
}
testSetNonce(pool, from, 1)
testAddBalance(pool, from, big.NewInt(0xffffffffffffff))
tx = transaction(0, 100000, key)
- if err, want := pool.AddRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) {
+ if err, want := pool.addRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) {
t.Errorf("want %v have %v", want, err)
}
@@ -340,11 +344,11 @@ func TestInvalidTransactions(t *testing.T) {
// which is higher than MinGasPrice but lower than pool's gasTip (1 Gwei)
pool.gasTip.Store(big.NewInt(1000000000)) // Set pool gasTip to 1 Gwei (1000000000)
tx = pricedTransaction(1, 100000, big.NewInt(300000000), key)
- if err, want := pool.AddRemote(tx), ErrUnderpriced; !errors.Is(err, want) {
+ if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
t.Errorf("want %v have %v", want, err)
}
// Local transactions should be accepted even if underpriced
- if err := pool.AddLocal(tx); err != nil {
+ if err := pool.addLocal(tx); err != nil {
t.Error("expected", nil, "got", err)
}
}
@@ -353,7 +357,7 @@ func TestQueue(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
tx := transaction(0, 100, key)
from, _ := deriveSender(tx)
@@ -385,7 +389,7 @@ func TestQueue2(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
tx1 := transaction(0, 100, key)
tx2 := transaction(10, 100, key)
@@ -411,13 +415,13 @@ func TestNegativeValue(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
from, _ := deriveSender(tx)
testAddBalance(pool, from, big.NewInt(1))
- if err := pool.AddRemote(tx); err != ErrNegativeValue {
- t.Error("expected", ErrNegativeValue, "got", err)
+ if err := pool.addRemote(tx); err != txpool.ErrNegativeValue {
+ t.Error("expected", txpool.ErrNegativeValue, "got", err)
}
}
@@ -427,7 +431,7 @@ func TestValidateTransactionEIP2681(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
addr := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, addr, big.NewInt(2e18))
@@ -493,11 +497,11 @@ func TestTipAboveFeeCap(t *testing.T) {
t.Parallel()
pool, key := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
- if err := pool.AddRemote(tx); err != core.ErrTipAboveFeeCap {
+ if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap {
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
}
}
@@ -506,18 +510,18 @@ func TestVeryHighValues(t *testing.T) {
t.Parallel()
pool, key := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
veryBigNumber := big.NewInt(1)
veryBigNumber.Lsh(veryBigNumber, 300)
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
- if err := pool.AddRemote(tx); err != core.ErrTipVeryHigh {
+ if err := pool.addRemote(tx); err != core.ErrTipVeryHigh {
t.Error("expected", core.ErrTipVeryHigh, "got", err)
}
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
- if err := pool.AddRemote(tx2); err != core.ErrFeeCapVeryHigh {
+ if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh {
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
}
}
@@ -526,14 +530,14 @@ func TestChainFork(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.AddBalance(addr, big.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
- pool.chain = newTestBlockChain(1000000, statedb, new(event.Feed))
+ pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
<-pool.requestReset(nil, nil)
}
resetState()
@@ -555,14 +559,14 @@ func TestDoubleNonce(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.AddBalance(addr, big.NewInt(600000000000000), tracing.BalanceChangeUnspecified)
- pool.chain = newTestBlockChain(1000000, statedb, new(event.Feed))
+ pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
<-pool.requestReset(nil, nil)
}
resetState()
@@ -608,7 +612,7 @@ func TestMissingNonce(t *testing.T) {
t.Parallel()
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
addr := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, addr, big.NewInt(100000000000000))
@@ -632,7 +636,7 @@ func TestNonceRecovery(t *testing.T) {
const n = 10
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
addr := crypto.PubkeyToAddress(key.PublicKey)
testSetNonce(pool, addr, n)
@@ -640,7 +644,7 @@ func TestNonceRecovery(t *testing.T) {
<-pool.requestReset(nil, nil)
tx := pricedTransaction(n, 100000, big.NewInt(300000000), key)
- if err := pool.AddRemote(tx); err != nil {
+ if err := pool.addRemote(tx); err != nil {
t.Error(err)
}
// simulate some weird re-order of transactions and missing nonce(s)
@@ -658,7 +662,7 @@ func TestDropping(t *testing.T) {
// Create a test account and fund it
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(1000))
@@ -761,12 +765,12 @@ func TestPostponing(t *testing.T) {
t.Parallel()
// Create the pool to test the postponing with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create two test accounts to produce different gap profiles with
keys := make([]*ecdsa.PrivateKey, 2)
@@ -790,7 +794,7 @@ func TestPostponing(t *testing.T) {
txs = append(txs, tx)
}
}
- for i, err := range pool.AddRemotesSync(txs) {
+ for i, err := range pool.addRemotesSync(txs) {
if err != nil {
t.Fatalf("tx %d: failed to add transactions: %v", i, err)
}
@@ -875,7 +879,7 @@ func TestGapFilling(t *testing.T) {
// Create a test account and fund it
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(90000000000000))
@@ -886,7 +890,7 @@ func TestGapFilling(t *testing.T) {
defer sub.Unsubscribe()
// Create a pending and a queued transaction with a nonce-gap in between
- pool.AddRemotesSync([]*types.Transaction{
+ pool.addRemotesSync([]*types.Transaction{
pricedTransaction(0, 100000, big.NewInt(300000000), key),
pricedTransaction(2, 100000, big.NewInt(300000000), key),
})
@@ -929,7 +933,7 @@ func TestQueueAccountLimiting(t *testing.T) {
// Create a test account and fund it
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(300000000000000))
@@ -974,17 +978,17 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
t.Parallel()
// Create the pool to test the limit enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.NoLocals = nolocals
config.AccountQueue = 1
config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible)
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts and fund them (last one will be the local)
keys := make([]*ecdsa.PrivateKey, 5)
@@ -1006,7 +1010,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
nonces[addr]++
}
// Import the batch and verify that limits have been enforced
- pool.AddRemotesSync(txs)
+ pool.addRemotesSync(txs)
queued := 0
for addr, list := range pool.queue {
@@ -1023,7 +1027,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
for i := uint64(0); i < 3*config.GlobalQueue; i++ {
txs = append(txs, pricedTransaction(i+1, 100000, big.NewInt(300000000), local))
}
- pool.AddLocals(txs)
+ pool.addLocals(txs)
// If locals are disabled, the previous eviction algorithm should apply here too
if nolocals {
@@ -1066,16 +1070,16 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
evictionInterval = time.Millisecond * 100
// Create the pool to test the non-expiration enforcement
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.Lifetime = time.Second
config.NoLocals = nolocals
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create two test accounts to ensure remotes expire but locals do not
local, _ := crypto.GenerateKey()
@@ -1085,10 +1089,10 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(100000000000000))
// Add the two transactions and ensure they both are queued up
- if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add local transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(300000000), remote)); err != nil {
+ if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(300000000), remote)); err != nil {
t.Fatalf("failed to add remote transaction: %v", err)
}
pending, queued := pool.Stats()
@@ -1155,7 +1159,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
}
// Queue gapped transactions
- if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(4, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add remote transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(300000000), remote)); err != nil {
@@ -1164,7 +1168,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
time.Sleep(5 * evictionInterval) // A half lifetime pass
// Queue executable transactions, the life cycle should be restarted.
- if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add remote transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(300000000), remote)); err != nil {
@@ -1212,7 +1216,7 @@ func TestPendingLimiting(t *testing.T) {
// Create a test account and fund it
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(400000000000000))
@@ -1253,15 +1257,15 @@ func TestPendingGlobalLimiting(t *testing.T) {
t.Parallel()
// Create the pool to test the limit enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.GlobalSlots = config.AccountSlots * 10
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts and fund them
keys := make([]*ecdsa.PrivateKey, 5)
@@ -1281,7 +1285,7 @@ func TestPendingGlobalLimiting(t *testing.T) {
}
}
// Import the batch and verify that limits have been enforced
- pool.AddRemotesSync(txs)
+ pool.addRemotesSync(txs)
pending := 0
for _, list := range pool.pending {
@@ -1303,7 +1307,7 @@ func TestAllowedTxSize(t *testing.T) {
// Create a test account and fund it
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(10000000000000000))
@@ -1355,17 +1359,17 @@ func TestCapClearsFromAll(t *testing.T) {
t.Parallel()
// Create the pool to test the limit enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.AccountSlots = 2
config.AccountQueue = 2
config.GlobalSlots = 8
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts and fund them
key, _ := crypto.GenerateKey()
@@ -1390,15 +1394,16 @@ func TestPendingMinimumAllowance(t *testing.T) {
t.Parallel()
// Create the pool to test the limit enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.AccountSlots = 5
config.GlobalSlots = 1
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts and fund them
keys := make([]*ecdsa.PrivateKey, 5)
@@ -1418,7 +1423,7 @@ func TestPendingMinimumAllowance(t *testing.T) {
}
}
// Import the batch and verify that limits have been enforced
- pool.AddRemotesSync(txs)
+ pool.addRemotesSync(txs)
for addr, list := range pool.pending {
if list.Len() != int(config.AccountSlots) {
@@ -1439,12 +1444,12 @@ func TestRepricing(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
@@ -1475,8 +1480,8 @@ func TestRepricing(t *testing.T) {
ltx := pricedTransaction(0, 100000, big.NewInt(250000000), keys[3])
// Import the batch and that both pending and queued transactions match up
- pool.AddRemotesSync(txs)
- pool.AddLocal(ltx)
+ pool.addRemotesSync(txs)
+ pool.addLocal(ltx)
pending, queued := pool.Stats()
if pending != 7 {
@@ -1508,14 +1513,14 @@ func TestRepricing(t *testing.T) {
t.Fatalf("pool internal state corrupted: %v", err)
}
// Check that we can't add the old transactions back
- if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(250000000), keys[0])); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(250000000), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(250000000), keys[2])); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(250000000), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
if err := validateEvents(events, 0); err != nil {
t.Fatalf("post-reprice event firing failed: %v", err)
@@ -1525,7 +1530,7 @@ func TestRepricing(t *testing.T) {
}
// However we can add local underpriced transactions
tx := pricedTransaction(1, 100000, big.NewInt(250000000), keys[3])
- if err := pool.AddLocal(tx); err != nil {
+ if err := pool.addLocal(tx); err != nil {
t.Fatalf("failed to add underpriced local transaction: %v", err)
}
if pending, _ = pool.Stats(); pending != 3 {
@@ -1538,13 +1543,13 @@ func TestRepricing(t *testing.T) {
t.Fatalf("pool internal state corrupted: %v", err)
}
// And we can fill gaps with properly priced transactions
- if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(500000000), keys[0])); err != nil {
+ if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(500000000), keys[0])); err != nil {
t.Fatalf("failed to add pending transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(500000000), keys[1])); err != nil {
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(500000000), keys[1])); err != nil {
t.Fatalf("failed to add pending transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(500000000), keys[2])); err != nil {
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(500000000), keys[2])); err != nil {
t.Fatalf("failed to add queued transaction: %v", err)
}
if err := validateEvents(events, 5); err != nil {
@@ -1565,7 +1570,7 @@ func TestRepricingDynamicFee(t *testing.T) {
// Create the pool to test the pricing enforcement with
pool, _ := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
@@ -1596,8 +1601,8 @@ func TestRepricingDynamicFee(t *testing.T) {
ltx := dynamicFeeTx(0, 100000, big.NewInt(350000000), big.NewInt(300000000), keys[3])
// Import the batch and that both pending and queued transactions match up
- pool.AddRemotesSync(txs)
- pool.AddLocal(ltx)
+ pool.addRemotesSync(txs)
+ pool.addLocal(ltx)
pending, queued := pool.Stats()
if pending != 7 {
@@ -1630,16 +1635,16 @@ func TestRepricingDynamicFee(t *testing.T) {
}
// Check that we can't add the old transactions back
tx := pricedTransaction(1, 100000, big.NewInt(300000000), keys[0])
- if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
tx = dynamicFeeTx(0, 100000, big.NewInt(350000000), big.NewInt(300000000), keys[1])
- if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
tx = dynamicFeeTx(2, 100000, big.NewInt(300000000), big.NewInt(300000000), keys[2])
- if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
if err := validateEvents(events, 0); err != nil {
t.Fatalf("post-reprice event firing failed: %v", err)
@@ -1649,7 +1654,7 @@ func TestRepricingDynamicFee(t *testing.T) {
}
// However we can add local underpriced transactions
tx = dynamicFeeTx(1, 100000, big.NewInt(300000000), big.NewInt(300000000), keys[3])
- if err := pool.AddLocal(tx); err != nil {
+ if err := pool.addLocal(tx); err != nil {
t.Fatalf("failed to add underpriced local transaction: %v", err)
}
if pending, _ = pool.Stats(); pending != 3 {
@@ -1663,15 +1668,15 @@ func TestRepricingDynamicFee(t *testing.T) {
}
// And we can fill gaps with properly priced transactions
tx = pricedTransaction(1, 100000, big.NewInt(350000000), keys[0])
- if err := pool.AddRemote(tx); err != nil {
+ if err := pool.addRemote(tx); err != nil {
t.Fatalf("failed to add pending transaction: %v", err)
}
tx = dynamicFeeTx(0, 100000, big.NewInt(400000000), big.NewInt(350000000), keys[1])
- if err := pool.AddRemote(tx); err != nil {
+ if err := pool.addRemote(tx); err != nil {
t.Fatalf("failed to add pending transaction: %v", err)
}
tx = dynamicFeeTx(2, 100000, big.NewInt(350000000), big.NewInt(350000000), keys[2])
- if err := pool.AddRemote(tx); err != nil {
+ if err := pool.addRemote(tx); err != nil {
t.Fatalf("failed to add queued transaction: %v", err)
}
if err := validateEvents(events, 5); err != nil {
@@ -1688,12 +1693,12 @@ func TestRepricingKeepsLocals(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, eip1559Config, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a number of test accounts and fund them
keys := make([]*ecdsa.PrivateKey, 3)
@@ -1706,23 +1711,23 @@ func TestRepricingKeepsLocals(t *testing.T) {
for i := uint64(0); i < 5; i++ {
// Add pending transaction.
pendingTx := pricedTransaction(i, 100000, big.NewInt(int64((i+1)*250000000)), keys[2])
- if err := pool.AddLocal(pendingTx); err != nil {
+ if err := pool.addLocal(pendingTx); err != nil {
t.Fatal(err)
}
// Add queued transaction.
queuedTx := pricedTransaction(i+6, 100000, big.NewInt(int64((i+1)*250000000)), keys[2])
- if err := pool.AddLocal(queuedTx); err != nil {
+ if err := pool.addLocal(queuedTx); err != nil {
t.Fatal(err)
}
// Add pending dynamic fee transaction.
pendingTx = dynamicFeeTx(i, 100000, big.NewInt(int64((i+1)*250000000)), big.NewInt(int64((i+1)*250000000)), keys[1])
- if err := pool.AddLocal(pendingTx); err != nil {
+ if err := pool.addLocal(pendingTx); err != nil {
t.Fatal(err)
}
// Add queued dynamic fee transaction.
queuedTx = dynamicFeeTx(i+6, 100000, big.NewInt(int64((i+1)*250000000)), big.NewInt(int64((i+1)*250000000)), keys[1])
- if err := pool.AddLocal(queuedTx); err != nil {
+ if err := pool.addLocal(queuedTx); err != nil {
t.Fatal(err)
}
}
@@ -1759,20 +1764,20 @@ func TestRepricingKeepsLocals(t *testing.T) {
// pending transactions are moved into the queue.
//
// Note, local transactions are never allowed to be dropped.
-func TestPoolUnderpricing(t *testing.T) {
+func TestUnderpricing(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.GlobalSlots = 2
config.GlobalQueue = 2
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
@@ -1800,7 +1805,7 @@ func TestPoolUnderpricing(t *testing.T) {
// Import the batch and that both pending and queued transactions match up
pool.AddRemotes(txs)
- pool.AddLocal(ltx)
+ pool.addLocal(ltx)
pending, queued := pool.Stats()
if pending != 3 {
@@ -1816,26 +1821,26 @@ func TestPoolUnderpricing(t *testing.T) {
t.Fatalf("pool internal state corrupted: %v", err)
}
// Ensure that adding an underpriced transaction on block limit fails
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, ErrUnderpriced) {
- t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
// Replace a future transaction with a future transaction
- if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(500000000), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1
+ if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(500000000), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1
t.Fatalf("failed to add well priced transaction: %v", err)
}
// Ensure that adding high priced transactions drops cheap ones, but not own
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(750000000), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que -
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(750000000), keys[1])); 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)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1000000000), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1000000000), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2
t.Fatalf("failed to add well priced transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(1250000000), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3
+ if err := pool.addRemote(pricedTransaction(3, 100000, big.NewInt(1250000000), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3
t.Fatalf("failed to add well priced transaction: %v", err)
}
// Ensure that replacing a pending transaction with a future transaction fails
- if err := pool.AddRemote(pricedTransaction(5, 100000, big.NewInt(1500000000), keys[1])); err != ErrFutureReplacePending {
- t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
+ if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(1500000000), keys[1])); err != txpool.ErrFutureReplacePending {
+ t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending)
}
pending, queued = pool.Stats()
if pending != 2 {
@@ -1852,11 +1857,11 @@ func TestPoolUnderpricing(t *testing.T) {
}
// Ensure that adding local transactions can push out even higher priced ones
ltx = pricedTransaction(1, 100000, big.NewInt(250000000), keys[2])
- if err := pool.AddLocal(ltx); err != nil {
+ if err := pool.addLocal(ltx); err != nil {
t.Fatalf("failed to append underpriced local transaction: %v", err)
}
ltx = pricedTransaction(0, 100000, big.NewInt(250000000), keys[3])
- if err := pool.AddLocal(ltx); err != nil {
+ if err := pool.addLocal(ltx); err != nil {
t.Fatalf("failed to add new underpriced local transaction: %v", err)
}
pending, queued = pool.Stats()
@@ -1877,21 +1882,21 @@ func TestPoolUnderpricing(t *testing.T) {
// Tests that more expensive transactions push out cheap ones from the pool, but
// without producing instability by creating gaps that start jumping transactions
// back and forth between queued/pending.
-func TestPoolStableUnderpricing(t *testing.T) {
+func TestStableUnderpricing(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.GlobalSlots = common.LimitThresholdNonceInQueue
config.GlobalQueue = 0
config.AccountSlots = config.GlobalSlots - 1
- pool := New(config, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
@@ -1909,7 +1914,7 @@ func TestPoolStableUnderpricing(t *testing.T) {
for i := uint64(0); i < config.GlobalSlots; i++ {
txs = append(txs, pricedTransaction(i, 100000, big.NewInt(300000000), keys[0]))
}
- pool.AddRemotesSync(txs)
+ pool.addRemotesSync(txs)
pending, queued := pool.Stats()
if pending != int(config.GlobalSlots) {
@@ -1952,7 +1957,7 @@ func TestUnderpricingDynamicFee(t *testing.T) {
t.Parallel()
pool, _ := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
pool.config.GlobalSlots = 2
pool.config.GlobalQueue = 2
@@ -1980,7 +1985,7 @@ func TestUnderpricingDynamicFee(t *testing.T) {
// 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
+ pool.addLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1
pending, queued := pool.Stats()
if pending != 3 {
@@ -1998,22 +2003,22 @@ func TestUnderpricingDynamicFee(t *testing.T) {
// Ensure that adding an underpriced transaction fails
tx := dynamicFeeTx(0, 100000, big.NewInt(260000000), big.NewInt(250000000), keys[1])
- if err := pool.AddRemote(tx); !errors.Is(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)
+ if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1
+ t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
}
// Ensure that adding high priced transactions drops cheap ones, but not own
tx = pricedTransaction(0, 100000, big.NewInt(260000000), keys[1])
- if err := pool.AddRemote(tx); err != nil { // +K1:0, -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que -
+ 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(1, 100000, big.NewInt(270000000), keys[1])
- if err := pool.AddRemote(tx); err != nil { // +K1:2, -K0:1 => Pend K0:0 K1:0, K2:0; Que K1:2
+ 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(2, 100000, big.NewInt(280000000), big.NewInt(250000000), keys[1])
- if err := pool.AddRemote(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3
+ 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()
@@ -2031,11 +2036,11 @@ func TestUnderpricingDynamicFee(t *testing.T) {
}
// Ensure that adding local transactions can push out even higher priced ones
ltx = dynamicFeeTx(1, 100000, big.NewInt(250000000), big.NewInt(250000000), keys[2])
- if err := pool.AddLocal(ltx); err != nil {
+ if err := pool.addLocal(ltx); err != nil {
t.Fatalf("failed to append underpriced local transaction: %v", err)
}
ltx = dynamicFeeTx(0, 100000, big.NewInt(250000000), big.NewInt(250000000), keys[3])
- if err := pool.AddLocal(ltx); err != nil {
+ if err := pool.addLocal(ltx); err != nil {
t.Fatalf("failed to add new underpriced local transaction: %v", err)
}
pending, queued = pool.Stats()
@@ -2059,7 +2064,7 @@ func TestDualHeapEviction(t *testing.T) {
t.Parallel()
pool, _ := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
pool.config.GlobalSlots = 10
pool.config.GlobalQueue = 10
@@ -2088,7 +2093,7 @@ func TestDualHeapEviction(t *testing.T) {
tx = dynamicFeeTx(0, 100000, big.NewInt(int64(250000000+baseFee+200+i)), big.NewInt(250000000), key)
highCap = tx
}
- pool.AddRemotesSync([]*types.Transaction{tx})
+ pool.addRemotesSync([]*types.Transaction{tx})
}
pending, queued := pool.Stats()
if pending+queued != 20 {
@@ -2116,10 +2121,11 @@ func TestDeduplication(t *testing.T) {
// Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create a test account to add transactions with
key, _ := crypto.GenerateKey()
@@ -2134,7 +2140,7 @@ func TestDeduplication(t *testing.T) {
for i := 0; i < len(txs); i += 2 {
firsts = append(firsts, txs[i])
}
- errs := pool.AddRemotesSync(firsts)
+ errs := pool.addRemotesSync(firsts)
if len(errs) != len(firsts) {
t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts))
}
@@ -2151,7 +2157,7 @@ func TestDeduplication(t *testing.T) {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1)
}
// Try to add all of them now and ensure previous ones error out as knowns
- errs = pool.AddRemotesSync(txs)
+ errs = pool.addRemotesSync(txs)
if len(errs) != len(txs) {
t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs))
}
@@ -2181,12 +2187,12 @@ func TestReplacement(t *testing.T) {
t.Parallel()
// Create the pool to test the pricing enforcement with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
@@ -2204,10 +2210,10 @@ func TestReplacement(t *testing.T) {
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(250000000), key)); err != nil {
t.Fatalf("failed to add original cheap pending transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(250000000), key)); err != ErrReplaceUnderpriced {
- t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(250000000), key)); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(300000000), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(300000000), key)); err != nil {
t.Fatalf("failed to replace original cheap pending transaction: %v", err)
}
if err := validateEvents(events, 2); err != nil {
@@ -2217,10 +2223,10 @@ func TestReplacement(t *testing.T) {
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
t.Fatalf("failed to add original proper pending transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
- t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
t.Fatalf("failed to replace original proper pending transaction: %v", err)
}
if err := validateEvents(events, 2); err != nil {
@@ -2228,23 +2234,23 @@ func TestReplacement(t *testing.T) {
}
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(250000000), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(250000000), key)); err != nil {
t.Fatalf("failed to add original cheap queued transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(250000000), key)); err != ErrReplaceUnderpriced {
- t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(250000000), key)); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(300000000), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(300000000), key)); err != nil {
t.Fatalf("failed to replace original cheap queued transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
t.Fatalf("failed to add original proper queued transaction: %v", err)
}
- if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
- t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
}
- if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
+ if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
t.Fatalf("failed to replace original proper queued transaction: %v", err)
}
@@ -2263,7 +2269,7 @@ func TestReplacementDynamicFee(t *testing.T) {
// Create the pool to test the pricing enforcement with
pool, key := setupPoolWithConfig(eip1559Config)
- defer pool.Stop()
+ defer pool.Close()
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(500000000000000))
// Keep track of transaction events to ensure all executables get announced
@@ -2305,12 +2311,12 @@ func TestReplacementDynamicFee(t *testing.T) {
}
// 2. Don't bump tip or feecap => discard
tx = dynamicFeeTx(nonce, 100001, big.NewInt(300000000), big.NewInt(250000000), key)
- if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
- t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
}
// 3. Bump both more than min => accept
tx = dynamicFeeTx(nonce, 100000, big.NewInt(350000000), big.NewInt(300000000), key)
- if err := pool.AddRemote(tx); err != nil {
+ 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)
@@ -2328,27 +2334,27 @@ func TestReplacementDynamicFee(t *testing.T) {
}
// 6. Bump tip max allowed so it's still underpriced => discard
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), 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)
+ if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
}
// 7. Bump fee cap max allowed so it's still underpriced => discard
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
- if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
- t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
}
// 8. Bump tip min for acceptance => accept
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), 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)
+ if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
}
// 9. Bump fee cap min for acceptance => accept
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
- if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced {
- t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced)
+ if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
+ t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.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 {
+ 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)
@@ -2368,11 +2374,11 @@ func TestReplacementDynamicFee(t *testing.T) {
// Tests that local transactions are journaled to disk, but remote transactions
// get discarded between restarts.
-func TestJournaling(t *testing.T) { testTransactionJournaling(t, false) }
+func TestJournaling(t *testing.T) { testJournaling(t, false) }
-func TestJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) }
+func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true) }
-func testTransactionJournaling(t *testing.T, nolocals bool) {
+func testJournaling(t *testing.T, nolocals bool) {
t.Parallel()
// Create a temporary file for the journal
@@ -2388,16 +2394,16 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
os.Remove(journal)
// Create the original pool to inject transaction into the journal
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig
config.NoLocals = nolocals
config.Journal = journal
config.Rejournal = time.Second
- pool := New(config, params.TestChainConfig, blockchain)
+ pool := New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
// Create two test accounts to ensure remotes expire but locals do not
local, _ := crypto.GenerateKey()
@@ -2407,13 +2413,13 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(100000000000000))
// Add three local and a remote transactions and ensure they are queued up
- if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(0, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add local transaction: %v", err)
}
- if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add local transaction: %v", err)
}
- if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(300000000), local)); err != nil {
+ if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(300000000), local)); err != nil {
t.Fatalf("failed to add local transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(300000000), remote)); err != nil {
@@ -2430,11 +2436,12 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
t.Fatalf("pool internal state corrupted: %v", err)
}
// Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive
- pool.Stop()
+ pool.Close()
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
- blockchain = newTestBlockChain(1000000, statedb, new(event.Feed))
+ blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool = New(config, params.TestChainConfig, blockchain)
+ pool = New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
pending, queued = pool.Stats()
if queued != 0 {
@@ -2456,11 +2463,12 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2)
<-pool.requestReset(nil, nil)
time.Sleep(2 * config.Rejournal)
- pool.Stop()
+ pool.Close()
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
- blockchain = newTestBlockChain(1000000, statedb, new(event.Feed))
- pool = New(config, params.TestChainConfig, blockchain)
+ blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
+ pool = New(config, blockchain)
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
pending, queued = pool.Stats()
if pending != 0 {
@@ -2478,7 +2486,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
if err := validatePoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}
- pool.Stop()
+ pool.Close()
}
// TestTransactionStatusCheck tests that the pool can correctly retrieve the
@@ -2487,12 +2495,12 @@ func TestStatusCheck(t *testing.T) {
t.Parallel()
// Create the pool to test the status retrievals with
- db := rawdb.NewMemoryDatabase()
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
- blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
- pool := New(testTxPoolConfig, params.TestChainConfig, blockchain)
- defer pool.Stop()
+ pool := New(testTxPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
+ defer pool.Close()
// Create the test accounts to check various transaction statuses with
keys := make([]*ecdsa.PrivateKey, 3)
@@ -2509,7 +2517,7 @@ func TestStatusCheck(t *testing.T) {
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(300000000), keys[2])) // Queued only
// Import the transaction and ensure they are correctly added
- pool.AddRemotesSync(txs)
+ pool.addRemotesSync(txs)
pending, queued := pool.Stats()
if pending != 2 {
@@ -2527,13 +2535,11 @@ func TestStatusCheck(t *testing.T) {
hashes[i] = tx.Hash()
}
hashes = append(hashes, common.Hash{})
+ expect := []txpool.TxStatus{txpool.TxStatusPending, txpool.TxStatusPending, txpool.TxStatusQueued, txpool.TxStatusQueued, txpool.TxStatusUnknown}
- statuses := pool.Status(hashes)
- expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown}
-
- for i := 0; i < len(statuses); i++ {
- if statuses[i] != expect[i] {
- t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i])
+ for i := 0; i < len(hashes); i++ {
+ if status := pool.Status(hashes[i]); status != expect[i] {
+ t.Errorf("transaction %d: status mismatch: have %v, want %v", i, status, expect[i])
}
}
}
@@ -2565,7 +2571,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1
func benchmarkPendingDemotion(b *testing.B, size int) {
// Add a batch of transactions to a pool one by one
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(1000000))
@@ -2590,7 +2596,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1
func benchmarkFuturePromotion(b *testing.B, size int) {
// Add a batch of transactions to a pool one by one
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(1000000))
@@ -2618,7 +2624,7 @@ func BenchmarkBatchLocalInsert10000(b *testing.B) { benchmarkBatchInsert(b, 1000
func benchmarkBatchInsert(b *testing.B, size int, local bool) {
// Generate a batch of transactions to enqueue into the pool
pool, key := setupPool()
- defer pool.Stop()
+ defer pool.Close()
account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(1000000000000000000))
@@ -2634,7 +2640,7 @@ func benchmarkBatchInsert(b *testing.B, size int, local bool) {
b.ResetTimer()
for _, batch := range batches {
if local {
- pool.AddLocals(batch)
+ pool.addLocals(batch)
} else {
pool.AddRemotes(batch)
}
@@ -2664,7 +2670,7 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
pool, _ := setupPool()
testAddBalance(pool, account, big.NewInt(100000000))
for _, local := range locals {
- pool.AddLocal(local)
+ pool.addLocal(local)
}
b.StartTimer()
// Assign a high enough balance for testing
@@ -2672,7 +2678,7 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
for i := 0; i < len(remotes); i++ {
pool.AddRemotes([]*types.Transaction{remotes[i]})
}
- pool.Stop()
+ pool.Close()
}
}
@@ -2680,7 +2686,7 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
func BenchmarkMultiAccountBatchInsert(b *testing.B) {
// Generate a batch of transactions to enqueue into the pool
pool, _ := setupPool()
- defer pool.Stop()
+ defer pool.Close()
b.ReportAllocs()
batches := make(types.Transactions, b.N)
for i := 0; i < b.N; i++ {
@@ -2693,7 +2699,7 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) {
// Benchmark importing the transactions into the queue
b.ResetTimer()
for _, tx := range batches {
- pool.AddRemotesSync([]*types.Transaction{tx})
+ pool.addRemotesSync([]*types.Transaction{tx})
}
}
@@ -2779,7 +2785,7 @@ func TestSetGasPrice(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Create a fresh pool instance for each test case to ensure isolation
pool, _ := setupPool()
- defer pool.Stop()
+ defer pool.Close()
oldPrice := pool.gasTip.Load()
haveErr := pool.SetGasTip(tc.tip)
diff --git a/core/txpool/lending_pool.go b/core/txpool/legacypool/lending_pool.go
similarity index 99%
rename from core/txpool/lending_pool.go
rename to core/txpool/legacypool/lending_pool.go
index 2f4bab9a8a..1e6bc168f7 100644
--- a/core/txpool/lending_pool.go
+++ b/core/txpool/legacypool/lending_pool.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"errors"
@@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
@@ -627,13 +628,13 @@ func (pool *LendingPool) validateTx(tx *types.LendingTransaction, local bool) er
}
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
- return ErrOversizedData
+ return txpool.ErrOversizedData
}
// Make sure the transaction is signed properly
from, err := types.LendingSender(pool.signer, tx)
if err != nil {
- return ErrInvalidSender
+ return txpool.ErrInvalidSender
}
err = pool.validateLending(tx)
if err != nil {
@@ -869,18 +870,18 @@ func (pool *LendingPool) addTxsLocked(txs []*types.LendingTransaction, local boo
// Status returns the status (unknown/pending/queued) of a batch of transactions
// identified by their hashes.
-func (pool *LendingPool) Status(hashes []common.Hash) []TxStatus {
+func (pool *LendingPool) Status(hashes []common.Hash) []txpool.TxStatus {
pool.mu.RLock()
defer pool.mu.RUnlock()
- status := make([]TxStatus, len(hashes))
+ status := make([]txpool.TxStatus, len(hashes))
for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil {
from, _ := types.LendingSender(pool.signer, tx) // already validated
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
- status[i] = TxStatusPending
+ status[i] = txpool.TxStatusPending
} else {
- status[i] = TxStatusQueued
+ status[i] = txpool.TxStatusQueued
}
}
}
diff --git a/core/txpool/lending_pool_test.go b/core/txpool/legacypool/lending_pool_test.go
similarity index 99%
rename from core/txpool/lending_pool_test.go
rename to core/txpool/legacypool/lending_pool_test.go
index c49e679f70..ae7985e8b2 100644
--- a/core/txpool/lending_pool_test.go
+++ b/core/txpool/legacypool/lending_pool_test.go
@@ -1,4 +1,4 @@
-package txpool
+package legacypool
import (
"math/big"
diff --git a/core/txpool/lending_tx_journal.go b/core/txpool/legacypool/lending_tx_journal.go
similarity index 99%
rename from core/txpool/lending_tx_journal.go
rename to core/txpool/legacypool/lending_tx_journal.go
index f484ff9e12..639543f3c9 100644
--- a/core/txpool/lending_tx_journal.go
+++ b/core/txpool/legacypool/lending_tx_journal.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"io"
diff --git a/core/txpool/lending_tx_list.go b/core/txpool/legacypool/lending_tx_list.go
similarity index 99%
rename from core/txpool/lending_tx_list.go
rename to core/txpool/legacypool/lending_tx_list.go
index e38f212011..f0fad3d7cb 100644
--- a/core/txpool/lending_tx_list.go
+++ b/core/txpool/legacypool/lending_tx_list.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"container/heap"
diff --git a/core/txpool/list.go b/core/txpool/legacypool/list.go
similarity index 99%
rename from core/txpool/list.go
rename to core/txpool/legacypool/list.go
index 04185a6c41..0629606b41 100644
--- a/core/txpool/list.go
+++ b/core/txpool/legacypool/list.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"container/heap"
diff --git a/core/txpool/list_test.go b/core/txpool/legacypool/list_test.go
similarity index 99%
rename from core/txpool/list_test.go
rename to core/txpool/legacypool/list_test.go
index 39e809b010..79f067011e 100644
--- a/core/txpool/list_test.go
+++ b/core/txpool/legacypool/list_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"math/big"
diff --git a/core/txpool/noncer.go b/core/txpool/legacypool/noncer.go
similarity index 99%
rename from core/txpool/noncer.go
rename to core/txpool/legacypool/noncer.go
index c9854a4238..86dd4feee2 100644
--- a/core/txpool/noncer.go
+++ b/core/txpool/legacypool/noncer.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"sync"
diff --git a/core/txpool/order_pool.go b/core/txpool/legacypool/order_pool.go
similarity index 99%
rename from core/txpool/order_pool.go
rename to core/txpool/legacypool/order_pool.go
index 123d36f8bd..0437709453 100644
--- a/core/txpool/order_pool.go
+++ b/core/txpool/legacypool/order_pool.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"errors"
@@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
@@ -529,13 +530,13 @@ func (pool *OrderPool) validateTx(tx *types.OrderTransaction, local bool) error
}
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
- return ErrOversizedData
+ return txpool.ErrOversizedData
}
// Make sure the transaction is signed properly
from, err := types.OrderSender(pool.signer, tx)
if err != nil {
- return ErrInvalidSender
+ return txpool.ErrInvalidSender
}
err = pool.validateOrder(tx)
if err != nil {
@@ -778,18 +779,18 @@ func (pool *OrderPool) addTxsLocked(txs []*types.OrderTransaction, local bool) [
// Status returns the status (unknown/pending/queued) of a batch of transactions
// identified by their hashes.
-func (pool *OrderPool) Status(hashes []common.Hash) []TxStatus {
+func (pool *OrderPool) Status(hashes []common.Hash) []txpool.TxStatus {
pool.mu.RLock()
defer pool.mu.RUnlock()
- status := make([]TxStatus, len(hashes))
+ status := make([]txpool.TxStatus, len(hashes))
for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil {
from, _ := types.OrderSender(pool.signer, tx) // already validated
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
- status[i] = TxStatusPending
+ status[i] = txpool.TxStatusPending
} else {
- status[i] = TxStatusQueued
+ status[i] = txpool.TxStatusQueued
}
}
}
diff --git a/core/txpool/order_tx_journal.go b/core/txpool/legacypool/order_tx_journal.go
similarity index 99%
rename from core/txpool/order_tx_journal.go
rename to core/txpool/legacypool/order_tx_journal.go
index 5c6e0fa09d..70040608be 100644
--- a/core/txpool/order_tx_journal.go
+++ b/core/txpool/legacypool/order_tx_journal.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"io"
diff --git a/core/txpool/order_tx_list.go b/core/txpool/legacypool/order_tx_list.go
similarity index 99%
rename from core/txpool/order_tx_list.go
rename to core/txpool/legacypool/order_tx_list.go
index 4efdef04f8..152dacea24 100644
--- a/core/txpool/order_tx_list.go
+++ b/core/txpool/legacypool/order_tx_list.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package txpool
+package legacypool
import (
"container/heap"
diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go
new file mode 100644
index 0000000000..bcb71cb320
--- /dev/null
+++ b/core/txpool/subpool.go
@@ -0,0 +1,112 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package txpool
+
+import (
+ "math/big"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/core"
+ "github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/event"
+)
+
+// Transaction is a helper struct to group together a canonical transaction with
+// satellite data items that are needed by the pool but are not part of the chain.
+type Transaction struct {
+ Tx *types.Transaction // Canonical transaction
+}
+
+// SubPool represents a specialized transaction pool that lives on its own (e.g.
+// blob pool). Since independent of how many specialized pools we have, they do
+// need to be updated in lockstep and assemble into one coherent view for block
+// production, this interface defines the common methods that allow the primary
+// transaction pool to manage the subpools.
+type SubPool interface {
+ // Filter is a selector used to decide whether a transaction should be added
+ // to this particular subpool.
+ Filter(tx *types.Transaction) bool
+
+ // Init sets the base parameters of the subpool, allowing it to load any saved
+ // transactions from disk and also permitting internal maintenance routines to
+ // start up.
+ //
+ // These should not be passed as a constructor argument - nor should the pools
+ // start by themselves - in order to keep multiple subpools in lockstep with
+ // one another.
+ Init(gasTip *big.Int, head *types.Header) error
+
+ // Close terminates any background processing threads and releases any held
+ // resources.
+ Close() error
+
+ // Reset retrieves the current state of the blockchain and ensures the content
+ // of the transaction pool is valid with regard to the chain state.
+ Reset(oldHead, newHead *types.Header)
+
+ // SetGasTip updates the minimum price required by the subpool for a new
+ // transaction, and drops all transactions below this threshold.
+ SetGasTip(tip *big.Int) error
+
+ // Has returns an indicator whether subpool has a transaction cached with the
+ // given hash.
+ Has(hash common.Hash) bool
+
+ // Get returns a transaction if it is contained in the pool, or nil otherwise.
+ Get(hash common.Hash) *Transaction
+
+ // Add enqueues a batch of transactions into the pool if they are valid. Due
+ // to the large transaction churn, add may postpone fully integrating the tx
+ // to a later point to batch multiple ones together.
+ Add(txs []*Transaction, local bool, sync bool) []error
+
+ // Pending retrieves all currently processable transactions, grouped by origin
+ // account and sorted by nonce.
+ Pending(enforceTips bool) map[common.Address][]*types.Transaction
+
+ // SubscribeTransactions subscribes to new transaction events.
+ SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription
+
+ // Nonce returns the next nonce of an account, with all transactions executable
+ // by the pool already applied on top.
+ Nonce(addr common.Address) uint64
+
+ // Stats retrieves the current pool stats, namely the number of pending and the
+ // number of queued (non-executable) transactions.
+ Stats() (int, int)
+
+ // Content retrieves the data content of the transaction pool, returning all the
+ // pending as well as queued transactions, grouped by account and sorted by nonce.
+ Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction)
+
+ // ContentFrom retrieves the data content of the transaction pool, returning the
+ // pending as well as queued transactions of this address, grouped by nonce.
+ ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction)
+
+ // Locals retrieves the accounts currently considered local by the pool.
+ Locals() []common.Address
+
+ // Status returns the known status (unknown/pending/queued) of a transaction
+ // identified by their hashes.
+ Status(hash common.Hash) TxStatus
+
+ // SetSigner sets the function to identify signer accounts.
+ SetSigner(f func(address common.Address) bool)
+
+ // IsSigner checks if the given address is a signer.
+ IsSigner(addr common.Address) bool
+}
diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go
index e6765b57fb..109b626b00 100644
--- a/core/txpool/txpool.go
+++ b/core/txpool/txpool.go
@@ -1,4 +1,4 @@
-// Copyright 2014 The go-ethereum Authors
+// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -18,131 +18,13 @@ package txpool
import (
"errors"
- "fmt"
- "math"
+ "maps"
"math/big"
- "sort"
- "sync"
- "sync/atomic"
- "time"
"github.com/XinFinOrg/XDPoSChain/common"
- "github.com/XinFinOrg/XDPoSChain/common/prque"
- "github.com/XinFinOrg/XDPoSChain/consensus"
- "github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core"
- "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
- "github.com/XinFinOrg/XDPoSChain/log"
- "github.com/XinFinOrg/XDPoSChain/metrics"
- "github.com/XinFinOrg/XDPoSChain/params"
-)
-
-const (
- // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
- chainHeadChanSize = 10
-
- // txSlotSize is used to calculate how many data slots a single transaction
- // takes up based on its size. The slots are used as DoS protection, ensuring
- // that validating a new transaction remains a constant operation (in reality
- // O(maxslots), where max slots are 4 currently).
- txSlotSize = 32 * 1024
-
- // txMaxSize is the maximum size a single transaction can have. This field has
- // non-trivial consequences: larger transactions are significantly harder and
- // more expensive to propagate; larger transactions also take more resources
- // to validate whether they fit into the pool or not.
- txMaxSize = 2 * txSlotSize // 64KB, don't bump without EIP-2464 support
-)
-
-var (
- // ErrAlreadyKnown is returned if the transactions is already contained
- // within the pool.
- ErrAlreadyKnown = errors.New("already known")
-
- // ErrInvalidSender is returned if the transaction contains an invalid signature.
- ErrInvalidSender = errors.New("invalid sender")
-
- // ErrUnderpriced is returned if a transaction's gas price is below the minimum
- // configured for the transaction pool.
- ErrUnderpriced = errors.New("transaction underpriced")
-
- // ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet
- // another remote transaction.
- ErrTxPoolOverflow = errors.New("txpool is full")
-
- // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
- // with a different one without the required price bump.
- ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
-
- // ErrGasLimit is returned if a transaction's requested gas limit exceeds the
- // maximum allowance of the current block.
- ErrGasLimit = errors.New("exceeds block gas limit")
-
- // ErrNegativeValue is a sanity error to ensure no one is able to specify a
- // transaction with a negative value.
- ErrNegativeValue = errors.New("negative value")
-
- // ErrOversizedData is returned if the input data of a transaction is greater
- // 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")
-
- // ErrFutureReplacePending is returned if a future transaction replaces a pending
- // transaction. Future transactions should only be able to replace other future transactions.
- ErrFutureReplacePending = errors.New("future transaction tries to replace pending")
-
- ErrZeroGasPrice = errors.New("zero gas price")
-
- ErrUnderMinGasPrice = errors.New("under min gas price")
-
- ErrDuplicateSpecialTransaction = errors.New("duplicate a special transaction")
-
- ErrMinDeploySMC = errors.New("smart contract creation cost is under allowance")
-)
-
-var (
- evictionInterval = time.Minute // Time interval to check for evictable transactions
- statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
-)
-
-var (
- // Metrics for the pending pool
- pendingDiscardMeter = metrics.NewRegisteredMeter("txpool/pending/discard", nil)
- pendingReplaceMeter = metrics.NewRegisteredMeter("txpool/pending/replace", nil)
- pendingRateLimitMeter = metrics.NewRegisteredMeter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting
- pendingNofundsMeter = metrics.NewRegisteredMeter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds
-
- // Metrics for the queued pool
- queuedDiscardMeter = metrics.NewRegisteredMeter("txpool/queued/discard", nil)
- queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil)
- queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
- queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds
- queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime
-
- // General tx metrics
- knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil)
- validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil)
- invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil)
- underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil)
- overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil)
-
- // throttleTxMeter counts how many transactions are rejected due to too-many-changes between
- // txpool reorgs.
- throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil)
- // reorgDurationTimer measures how long time a txpool reorg takes.
- reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil)
- // dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected
- // that this number is pretty low, since txpool reorgs happen very frequently.
- dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015))
-
- pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil)
- 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.
@@ -155,1830 +37,327 @@ const (
TxStatusIncluded
)
-// blockChain provides the state of blockchain and current gas limit to do
-// some pre checks in tx pool and event subscribers.
-type blockChain interface {
+// BlockChain defines the minimal set of methods needed to back a tx pool with
+// a chain. Exists to allow mocking the live chain out of tests.
+type BlockChain interface {
+ // CurrentBlock returns the current head of the chain.
CurrentBlock() *types.Header
- GetBlock(hash common.Hash, number uint64) *types.Block
- StateAt(root common.Hash) (*state.StateDB, error)
+
+ // SubscribeChainHeadEvent subscribes to new blocks being added to the chain.
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
-
- // Engine retrieves the chain's consensus engine.
- Engine() consensus.Engine
-
- // GetHeader returns the hash corresponding to their hash.
- GetHeader(common.Hash, uint64) *types.Header
-
- // CurrentHeader retrieves the current header from the local chain.
- CurrentHeader() *types.Header
-
- // Config retrieves the blockchain's chain configuration.
- Config() *params.ChainConfig
}
-// Config are the configuration parameters of the transaction pool.
-type Config struct {
- Locals []common.Address // Addresses that should be treated by default as local
- NoLocals bool // Whether local transaction handling should be disabled
- Journal string // Journal of local transactions to survive node restarts
- Rejournal time.Duration // Time interval to regenerate the local transaction journal
-
- PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
- PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)
-
- AccountSlots uint64 // Number of executable transaction slots guaranteed per account
- GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
- AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
- GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
-
- Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
-}
-
-// DefaultConfig contains the default configurations for the transaction pool.
-var DefaultConfig = Config{
- Journal: "transactions.rlp",
- Rejournal: time.Hour,
-
- PriceLimit: 0,
- PriceBump: 10,
-
- AccountSlots: 16,
- GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio
- AccountQueue: 64,
- GlobalQueue: 1024,
-
- Lifetime: 3 * time.Hour,
-}
-
-var defaultMaxTip = big.NewInt(1000 * params.GWei)
-
-// sanitize checks the provided user configurations and changes anything that's
-// unreasonable or unworkable.
-func (config *Config) sanitize() Config {
- conf := *config
- if conf.Rejournal < time.Second {
- log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second)
- conf.Rejournal = time.Second
- }
- if conf.PriceBump < 1 {
- log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
- conf.PriceBump = DefaultConfig.PriceBump
- }
- if conf.AccountSlots < 1 {
- log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots)
- conf.AccountSlots = DefaultConfig.AccountSlots
- }
- if conf.GlobalSlots < 1 {
- log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots)
- conf.GlobalSlots = DefaultConfig.GlobalSlots
- }
- if conf.AccountQueue < 1 {
- log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue)
- conf.AccountQueue = DefaultConfig.AccountQueue
- }
- if conf.GlobalQueue < 1 {
- log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue)
- conf.GlobalQueue = DefaultConfig.GlobalQueue
- }
- if conf.Lifetime < 1 {
- log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime)
- conf.Lifetime = DefaultConfig.Lifetime
- }
- return conf
-}
-
-// TxPool contains all currently known transactions. Transactions
-// enter the pool when they are received from the network or submitted
-// locally. They exit the pool when they are included in the blockchain.
-//
-// The pool separates processable transactions (which can be applied to the
-// current state) and future transactions. Transactions move between those
-// two states over time as they are received and processed.
+// TxPool is an aggregator for various transaction specific pools, collectively
+// tracking all the transactions deemed interesting by the node. Transactions
+// enter the pool when they are received from the network or submitted locally.
+// They exit the pool when they are included in the blockchain or evicted due to
+// resource constraints.
type TxPool struct {
- config Config
- chainconfig *params.ChainConfig
- chain blockChain
- gasTip atomic.Pointer[big.Int]
- txFeed event.Feed
- scope event.SubscriptionScope
- signer types.Signer
- mu sync.RWMutex
-
- currentHead atomic.Pointer[types.Header] // Current head of the blockchain
- currentState *state.StateDB // Current state in the blockchain head
- pendingNonces *noncer // Pending state tracking virtual nonces
-
- locals *accountSet // Set of local transaction to exempt from eviction rules
- journal *journal // Journal of local transaction to back up to disk
-
- pending map[common.Address]*list // All currently processable transactions
- queue map[common.Address]*list // Queued but non-processable transactions
- beats map[common.Address]time.Time // Last heartbeat from each known account
- all *lookup // All transactions to allow lookups
- priced *pricedList // All transactions sorted by price
-
- chainHeadCh chan core.ChainHeadEvent
- chainHeadSub event.Subscription
- reqResetCh chan *txpoolResetRequest
- reqPromoteCh chan *accountSet
- queueTxEventCh chan *types.Transaction
- reorgDoneCh chan chan struct{}
- reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop
- wg sync.WaitGroup // tracks loop, scheduleReorgLoop
- initDoneCh chan struct{} // is closed once the pool is initialized (for tests)
-
- changesSinceReorg int // A counter for how many drops we've performed in-between reorg.
-
- isSigner func(address common.Address) bool
- trc21FeeCapacity map[common.Address]*big.Int
-}
-
-type txpoolResetRequest struct {
- oldHead, newHead *types.Header
+ subpools []SubPool // List of subpools for specialized transaction handling
+ subs event.SubscriptionScope // Subscription scope to unsubscribe all on shutdown
+ quit chan chan error // Quit channel to tear down the head updater
}
// New creates a new transaction pool to gather, sort and filter inbound
// transactions from the network.
-func New(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
- // Sanitize the input to ensure no vulnerable gas prices are set
- config = (&config).sanitize()
+func New(gasTip *big.Int, chain BlockChain, subpools []SubPool) (*TxPool, error) {
+ // Retrieve the current head so that all subpools and this main coordinator
+ // pool will have the same starting state, even if the chain moves forward
+ // during initialization.
+ head := chain.CurrentBlock()
- // Create the transaction pool with its initial settings
pool := &TxPool{
- config: config,
- chainconfig: chainconfig,
- chain: chain,
- signer: types.LatestSigner(chainconfig),
- pending: make(map[common.Address]*list),
- queue: make(map[common.Address]*list),
- beats: make(map[common.Address]time.Time),
- all: newLookup(),
- chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
- reqResetCh: make(chan *txpoolResetRequest),
- reqPromoteCh: make(chan *accountSet),
- queueTxEventCh: make(chan *types.Transaction),
- reorgDoneCh: make(chan chan struct{}),
- reorgShutdownCh: make(chan struct{}),
- initDoneCh: make(chan struct{}),
- trc21FeeCapacity: map[common.Address]*big.Int{},
+ subpools: subpools,
+ quit: make(chan chan error),
}
- pool.gasTip.Store(new(big.Int).SetUint64(config.PriceLimit))
- pool.locals = newAccountSet(pool.signer)
- for _, addr := range config.Locals {
- log.Info("Setting new local account", "address", addr)
- pool.locals.add(addr)
- }
- pool.priced = newPricedList(pool.all)
- pool.reset(nil, chain.CurrentBlock())
-
- // Start the reorg loop early so it can handle requests generated during journal loading.
- pool.wg.Go(pool.scheduleReorgLoop)
-
- // If local transactions and journaling is enabled, load from disk
- if !config.NoLocals && config.Journal != "" {
- pool.journal = newTxJournal(config.Journal)
-
- if err := pool.journal.load(pool.AddLocals); err != nil {
- log.Warn("Failed to load transaction journal", "err", err)
- }
- if err := pool.journal.rotate(pool.local()); err != nil {
- log.Warn("Failed to rotate transaction journal", "err", err)
+ for i, subpool := range subpools {
+ if err := subpool.Init(gasTip, head); err != nil {
+ for j := i - 1; j >= 0; j-- {
+ subpools[j].Close()
+ }
+ return nil, err
}
}
+ go pool.loop(head, chain)
+ return pool, nil
+}
- // Subscribe events from blockchain and start the main event loop.
- pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
- pool.wg.Go(pool.loop)
+// Close terminates the transaction pool and all its subpools.
+func (p *TxPool) Close() error {
+ var errs []error
- return pool
+ // Terminate the reset loop and wait for it to finish
+ errc := make(chan error)
+ p.quit <- errc
+ errs = append(errs, <-errc)
+
+ // Terminate each subpool
+ for _, subpool := range p.subpools {
+ errs = append(errs, subpool.Close())
+ }
+ return errors.Join(errs...)
}
// loop is the transaction pool's main event loop, waiting for and reacting to
// outside blockchain events as well as for various reporting and transaction
// eviction events.
-func (pool *TxPool) loop() {
+func (p *TxPool) loop(head *types.Header, chain BlockChain) {
+ // Subscribe to chain head events to trigger subpool resets
var (
- prevPending, prevQueued, prevStales int
- // Start the stats reporting and transaction eviction tickers
- report = time.NewTicker(statsReportInterval)
- evict = time.NewTicker(evictionInterval)
- journal = time.NewTicker(pool.config.Rejournal)
- // Track the previous head headers for transaction reorgs
- head = pool.chain.CurrentBlock()
+ newHeadCh = make(chan core.ChainHeadEvent)
+ newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh)
)
- defer report.Stop()
- defer evict.Stop()
- defer journal.Stop()
+ defer newHeadSub.Unsubscribe()
- // Notify tests that the init phase is done
- close(pool.initDoneCh)
- for {
- select {
- // Handle ChainHeadEvent
- case ev := <-pool.chainHeadCh:
- if ev.Block != nil {
- pool.requestReset(head, ev.Block.Header())
- head = ev.Block.Header()
- }
-
- // System shutdown.
- case <-pool.chainHeadSub.Err():
- close(pool.reorgShutdownCh)
- return
-
- // Handle stats reporting ticks
- case <-report.C:
- pool.mu.RLock()
- pending, queued := pool.stats()
- pool.mu.RUnlock()
- stales := int(pool.priced.stales.Load())
-
- if pending != prevPending || queued != prevQueued || stales != prevStales {
- log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales)
- prevPending, prevQueued, prevStales = pending, queued, stales
- }
-
- // Handle inactive account transaction eviction
- case <-evict.C:
- pool.mu.Lock()
- for addr := range pool.queue {
- // Skip local transactions from the eviction mechanism
- if pool.locals.contains(addr) {
- continue
- }
- // Any non-locals old enough should be removed
- if time.Since(pool.beats[addr]) > pool.config.Lifetime {
- list := pool.queue[addr].Flatten()
- for _, tx := range list {
- pool.removeTx(tx.Hash(), true)
+ // Track the previous and current head to feed to an idle reset
+ var (
+ oldHead = head
+ newHead = oldHead
+ )
+ // Consume chain head events and start resets when none is running
+ var (
+ resetBusy = make(chan struct{}, 1) // Allow 1 reset to run concurrently
+ resetDone = make(chan *types.Header)
+ )
+ var errc chan error
+ for errc == nil {
+ // Something interesting might have happened, run a reset if there is
+ // one needed but none is running. The resetter will run on its own
+ // goroutine to allow chain head events to be consumed contiguously.
+ if newHead != oldHead {
+ // Try to inject a busy marker and start a reset if successful
+ select {
+ case resetBusy <- struct{}{}:
+ // Busy marker injected, start a new subpool reset
+ go func(oldHead, newHead *types.Header) {
+ for _, subpool := range p.subpools {
+ subpool.Reset(oldHead, newHead)
}
- queuedEvictionMeter.Mark(int64(len(list)))
- }
- }
- pool.mu.Unlock()
+ resetDone <- newHead
+ }(oldHead, newHead)
- // Handle local transaction journal rotation
- case <-journal.C:
- if pool.journal != nil {
- pool.mu.Lock()
- if err := pool.journal.rotate(pool.local()); err != nil {
- log.Warn("Failed to rotate local tx journal", "err", err)
- }
- pool.mu.Unlock()
+ default:
+ // Reset already running, wait until it finishes
}
}
+ // Wait for the next chain head event or a previous reset finish
+ select {
+ case event := <-newHeadCh:
+ // Chain moved forward, store the head for later consumption
+ newHead = event.Block.Header()
+
+ case head := <-resetDone:
+ // Previous reset finished, update the old head and allow a new reset
+ oldHead = head
+ <-resetBusy
+
+ case errc = <-p.quit:
+ // Termination requested, break out on the next loop round
+ }
}
-}
-
-// Stop terminates the transaction pool.
-func (pool *TxPool) Stop() {
- // Unsubscribe all subscriptions registered from txpool
- pool.scope.Close()
-
- // Unsubscribe subscriptions registered from blockchain
- pool.chainHeadSub.Unsubscribe()
- pool.wg.Wait()
-
- if pool.journal != nil {
- pool.journal.close()
- }
- log.Info("Transaction pool stopped")
-}
-
-// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
-// starts sending event to the given channel.
-func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return pool.scope.Track(pool.txFeed.Subscribe(ch))
+ // Notify the closer of termination (no error possible for now)
+ errc <- nil
}
// SetGasTip updates the minimum gas tip required by the transaction pool for a
-// new transaction, and drops all transactions below this threshold. Negative
-// gas prices and prices exceeding 1000 GWei are considered invalid and will be
-// rejected without updating the threshold.
-func (pool *TxPool) SetGasTip(tip *big.Int) error {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- if tip == nil {
- log.Warn("Reject nil gas tip")
- return errors.New("reject nil gas tip")
- }
- if tip.Sign() < 0 {
- log.Warn("Reject invalid gas tip", "tip", tip)
- return fmt.Errorf("reject negative gas tip: %v", tip)
- }
- if tip.Cmp(defaultMaxTip) > 0 {
- log.Warn("Reject invalid gas tip", "tip", tip, "max", defaultMaxTip)
- return fmt.Errorf("reject too high gas tip: %v, maximum: %v", tip, defaultMaxTip)
- }
-
- old := pool.gasTip.Load()
- pool.gasTip.Store(new(big.Int).Set(tip))
-
- // If the min miner fee increased, remove transactions below the new threshold
- if tip.Cmp(old) > 0 {
- // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead
- drop := pool.all.RemotesBelowTip(tip)
- for _, tx := range drop {
- pool.removeTx(tx.Hash(), false)
+// new transaction, and drops all transactions below this threshold.
+func (p *TxPool) SetGasTip(tip *big.Int) error {
+ for _, subpool := range p.subpools {
+ if err := subpool.SetGasTip(tip); err != nil {
+ return err
}
- pool.priced.Removed(len(drop))
- }
- log.Info("Transaction pool tip threshold updated", "tip", tip)
- return nil
-}
-
-// Nonce returns the next nonce of an account, with all transactions executable
-// by the pool already applied on top.
-func (pool *TxPool) Nonce(addr common.Address) uint64 {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- return pool.pendingNonces.get(addr)
-}
-
-// Stats retrieves the current pool stats, namely the number of pending and the
-// number of queued (non-executable) transactions.
-func (pool *TxPool) Stats() (int, int) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- return pool.stats()
-}
-
-// stats retrieves the current pool stats, namely the number of pending and the
-// number of queued (non-executable) transactions.
-func (pool *TxPool) stats() (int, int) {
- pending := 0
- for _, list := range pool.pending {
- pending += list.Len()
- }
- queued := 0
- for _, list := range pool.queue {
- queued += list.Len()
- }
- return pending, queued
-}
-
-// Content retrieves the data content of the transaction pool, returning all the
-// pending as well as queued transactions, grouped by account and sorted by nonce.
-func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- pending := make(map[common.Address]types.Transactions, len(pool.pending))
- for addr, list := range pool.pending {
- pending[addr] = list.Flatten()
- }
- queued := make(map[common.Address]types.Transactions, len(pool.queue))
- for addr, list := range pool.queue {
- queued[addr] = list.Flatten()
- }
- return pending, queued
-}
-
-// ContentFrom retrieves the data content of the transaction pool, returning the
-// pending as well as queued transactions of this address, grouped by nonce.
-func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- var pending types.Transactions
- if list, ok := pool.pending[addr]; ok {
- pending = list.Flatten()
- }
- var queued types.Transactions
- if list, ok := pool.queue[addr]; ok {
- queued = list.Flatten()
- }
- return pending, queued
-}
-
-// Pending retrieves all currently processable transactions, grouped by origin
-// account and sorted by nonce. The returned transaction set is a copy and can be
-// freely modified by calling code.
-//
-// The enforceTips parameter can be used to do an extra filtering on the pending
-// transactions and only return those whose **effective** tip is large enough in
-// the next pending execution environment.
-func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transactions {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- pending := make(map[common.Address]types.Transactions, len(pool.pending))
- for addr, list := range pool.pending {
- txs := list.Flatten()
-
- // If the miner requests tip enforcement, cap the lists now
- if enforceTips && !pool.locals.contains(addr) {
- for i, tx := range txs {
- if !tx.IsSpecialTransaction() && tx.EffectiveGasTipIntCmp(pool.gasTip.Load(), pool.priced.urgent.baseFee) < 0 {
- txs = txs[:i]
- break
- }
- }
- }
- if len(txs) > 0 {
- pending[addr] = txs
- }
- }
- return pending
-}
-
-// Locals retrieves the accounts currently considered local by the pool.
-func (pool *TxPool) Locals() []common.Address {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- return pool.locals.flatten()
-}
-
-// local retrieves all currently known local transactions, grouped by origin
-// account and sorted by nonce. The returned transaction set is a copy and can be
-// freely modified by calling code.
-func (pool *TxPool) local() map[common.Address]types.Transactions {
- txs := make(map[common.Address]types.Transactions)
- for addr := range pool.locals.accounts {
- if pending := pool.pending[addr]; pending != nil {
- txs[addr] = append(txs[addr], pending.Flatten()...)
- }
- if queued := pool.queue[addr]; queued != nil {
- txs[addr] = append(txs[addr], queued.Flatten()...)
- }
- }
- return txs
-}
-
-// validateTxBasics checks whether a transaction is valid according to the consensus
-// rules, but does not check state-dependent validation such as sufficient balance.
-// This check is meant as an early check which only needs to be performed once,
-// and does not require the pool mutex to be held.
-func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error {
- opts := &ValidationOptions{
- Config: pool.chainconfig,
- Accept: 0 |
- 1< 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, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
- underpricedTxMeter.Mark(1)
- return false, ErrUnderpriced
- }
- // We're about to replace a transaction. The reorg does a more thorough
- // analysis of what to remove and how, but it runs async. We don't want to
- // do too many replacements between reorg-runs, so we cap the number of
- // replacements to 25% of the slots
- if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) {
- throttleTxMeter.Mark(1)
- return false, ErrTxPoolOverflow
- }
-
- // New transaction is better than our worse ones, make room for it.
- // If it's a local transaction, forcibly discard all available transactions.
- // Otherwise if we can't make enough room for new one, abort the operation.
- drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal)
-
- // Special case, we still can't make the room for the new remote one.
- if !isLocal && !success {
- log.Trace("Discarding overflown transaction", "hash", hash)
- overflowedTxMeter.Mark(1)
- return false, ErrTxPoolOverflow
- }
-
- // If the new transaction is a future transaction it should never churn pending transactions
- if !isLocal && pool.isGapped(from, tx) {
- var replacesPending bool
- for _, dropTx := range drop {
- dropSender, _ := types.Sender(pool.signer, dropTx)
- if list := pool.pending[dropSender]; list != nil && list.Contains(dropTx.Nonce()) {
- replacesPending = true
- break
- }
- }
- // Add all transactions back to the priced queue
- if replacesPending {
- for _, dropTx := range drop {
- pool.priced.Put(dropTx, false)
- }
- log.Trace("Discarding future transaction replacing pending tx", "hash", hash)
- return false, ErrFutureReplacePending
- }
- }
-
- // Kick out the underpriced remote transactions.
- for _, tx := range drop {
- log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
- underpricedTxMeter.Mark(1)
- dropped := pool.removeTx(tx.Hash(), false)
- pool.changesSinceReorg += dropped
- }
- }
-
- // Try to replace an existing transaction in the pending pool
- if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) {
- // Nonce already pending, check if required price bump is met
- inserted, old := list.Add(tx, pool.config.PriceBump)
- if !inserted {
- pendingDiscardMeter.Mark(1)
- return false, ErrReplaceUnderpriced
- }
- // New transaction is better, replace old one
- if old != nil {
- pool.all.Remove(old.Hash())
- pool.priced.Removed(1)
- pendingReplaceMeter.Mark(1)
- }
- pool.all.Add(tx, isLocal)
- pool.priced.Put(tx, isLocal)
- pool.journalTx(from, tx)
- pool.queueTxEvent(tx)
- log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
-
- // Successful promotion, bump the heartbeat
- pool.beats[from] = time.Now()
- return old != nil, nil
- }
- // New transaction isn't replacing a pending one, push into queue
- replaced, err = pool.enqueueTx(hash, tx, isLocal, true)
- if err != nil {
- return false, err
- }
- // Mark local addresses and journal local transactions
- if local && !pool.locals.contains(from) {
- log.Info("Setting new local account", "address", from)
- pool.locals.add(from)
- pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time.
- }
- if isLocal {
- localGauge.Inc(1)
- }
- pool.journalTx(from, tx)
-
- log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
- return replaced, nil
-}
-
-// isGapped reports whether the given transaction is immediately executable.
-func (pool *TxPool) isGapped(from common.Address, tx *types.Transaction) bool {
- // Short circuit if transaction falls within the scope of the pending list
- // or matches the next pending nonce which can be promoted as an executable
- // transaction afterwards. Note, the tx staleness is already checked in
- // 'validateTx' function previously.
- next := pool.pendingNonces.get(from)
- if tx.Nonce() <= next {
- return false
- }
- // The transaction has a nonce gap with pending list, it's only considered
- // as executable if transactions in queue can fill up the nonce gap.
- queue, ok := pool.queue[from]
- if !ok {
- return true
- }
- for nonce := next; nonce < tx.Nonce(); nonce++ {
- if !queue.Contains(nonce) {
- return true // txs in queue can't fill up the nonce gap
+// Has returns an indicator whether the pool has a transaction cached with the
+// given hash.
+func (p *TxPool) Has(hash common.Hash) bool {
+ for _, subpool := range p.subpools {
+ if subpool.Has(hash) {
+ return true
}
}
return false
}
-// enqueueTx inserts a new transaction into the non-executable transaction queue.
-//
-// Note, this method assumes the pool lock is held!
-func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) {
- // Try to insert the transaction into the future queue
- from, _ := types.Sender(pool.signer, tx) // already validated
- if pool.queue[from] == nil {
- pool.queue[from] = newList(false)
+// Get returns a transaction if it is contained in the pool, or nil otherwise.
+func (p *TxPool) Get(hash common.Hash) *Transaction {
+ for _, subpool := range p.subpools {
+ if tx := subpool.Get(hash); tx != nil {
+ return tx
+ }
}
- inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
- if !inserted {
- // An older transaction was better, discard this
- queuedDiscardMeter.Mark(1)
- return false, ErrReplaceUnderpriced
- }
- // Discard any previous transaction and mark this
- if old != nil {
- pool.all.Remove(old.Hash())
- pool.priced.Removed(1)
- queuedReplaceMeter.Mark(1)
- } else {
- // Nothing was replaced, bump the queued counter
- queuedGauge.Inc(1)
- }
- // If the transaction isn't in lookup set but it's expected to be there,
- // show the error log.
- if pool.all.Get(hash) == nil && !addAll {
- log.Error("Missing transaction in lookup set, please report the issue", "hash", hash)
- }
- if addAll {
- pool.all.Add(tx, local)
- pool.priced.Put(tx, local)
- }
- // If we never record the heartbeat, do it right now.
- if _, exist := pool.beats[from]; !exist {
- pool.beats[from] = time.Now()
- }
- return old != nil, nil
+ return nil
}
-// journalTx adds the specified transaction to the local disk journal if it is
-// deemed to have been sent from a local account.
-func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
- // Only journal if it's enabled and the transaction is local
- if pool.journal == nil || !pool.locals.contains(from) {
- return
- }
- if err := pool.journal.insert(tx); err != nil {
- log.Warn("Failed to journal local transaction", "err", err)
- }
-}
+// Add enqueues a batch of transactions into the pool if they are valid. Due
+// to the large transaction churn, add may postpone fully integrating the tx
+// to a later point to batch multiple ones together.
+func (p *TxPool) Add(txs []*Transaction, local bool, sync bool) []error {
+ // Split the input transactions between the subpools. It shouldn't really
+ // happen that we receive merged batches, but better graceful than strange
+ // errors.
+ //
+ // We also need to track how the transactions were split across the subpools,
+ // so we can piece back the returned errors into the original order.
+ txsets := make([][]*Transaction, len(p.subpools))
+ splits := make([]int, len(txs))
-// promoteTx adds a transaction to the pending (processable) list of transactions
-// and returns whether it was inserted or an older was better.
-//
-// Note, this method assumes the pool lock is held!
-func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
- // Try to insert the transaction into the pending queue
- if pool.pending[addr] == nil {
- pool.pending[addr] = newList(true)
- }
- list := pool.pending[addr]
-
- inserted, old := list.Add(tx, pool.config.PriceBump)
- if !inserted {
- // An older transaction was better, discard this
- pool.all.Remove(hash)
- pool.priced.Removed(1)
-
- pendingDiscardMeter.Mark(1)
- return false
- }
- // Otherwise discard any previous transaction and mark this
- if old != nil {
- pool.all.Remove(old.Hash())
- pool.priced.Removed(1)
- pendingReplaceMeter.Mark(1)
- } else {
- // Nothing was replaced, bump the pending counter
- pendingGauge.Inc(1)
- }
- // Set the potentially new pending nonce and notify any subsystems of the new tx
- pool.pendingNonces.set(addr, tx.Nonce()+1)
-
- // Successful promotion, bump the heartbeat
- pool.beats[addr] = time.Now()
- return true
-}
-
-func (pool *TxPool) promoteSpecialTx(addr common.Address, tx *types.Transaction, isLocal bool) (bool, error) {
- // Try to insert the transaction into the pending queue
- if pool.pending[addr] == nil {
- pool.pending[addr] = newList(true)
- }
- list := pool.pending[addr]
-
- old := list.txs.Get(tx.Nonce())
- if old.IsSpecialTransaction() {
- return false, ErrDuplicateSpecialTransaction
- }
- // Otherwise discard any previous transaction and mark this
- if old != nil {
- pool.all.Remove(old.Hash())
- pool.priced.Removed(1)
- pendingReplaceMeter.Mark(1)
- } else {
- // Nothing was replaced, bump the pending counter
- pendingGauge.Inc(1)
- }
- list.txs.Put(tx)
- if cost := tx.Cost(); list.costcap.Cmp(cost) < 0 {
- list.costcap = cost
- }
- if gas := tx.Gas(); list.gascap < gas {
- list.gascap = gas
- }
- // Failsafe to work around direct pending inserts (tests)
- if pool.all.Get(tx.Hash()) == nil {
- pool.all.Add(tx, isLocal)
- }
- // Set the potentially new pending nonce and notify any subsystems of the new tx
- pool.beats[addr] = time.Now()
- pool.pendingNonces.set(addr, tx.Nonce()+1)
- pool.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}})
- return true, nil
-}
-
-// AddLocals enqueues a batch of transactions into the pool if they are valid, marking the
-// senders as a local ones, ensuring they go around the local pricing constraints.
-//
-// This method is used to add transactions from the RPC API and performs synchronous pool
-// reorganization and event propagation.
-func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
- return pool.addTxs(txs, !pool.config.NoLocals, true)
-}
-
-// AddLocal enqueues a single local transaction into the pool if it is valid. This is
-// a convenience wrapper aroundd AddLocals.
-func (pool *TxPool) AddLocal(tx *types.Transaction) error {
- errs := pool.AddLocals([]*types.Transaction{tx})
- return errs[0]
-}
-
-// AddRemotes enqueues a batch of transactions into the pool if they are valid. If the
-// senders are not among the locally tracked ones, full pricing constraints will apply.
-//
-// This method is used to add transactions from the p2p network and does not wait for pool
-// reorganization and internal event propagation.
-func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
- return pool.addTxs(txs, false, false)
-}
-
-// This is like AddRemotes, but waits for pool reorganization. Tests use this method.
-func (pool *TxPool) AddRemotesSync(txs []*types.Transaction) []error {
- return pool.addTxs(txs, false, true)
-}
-
-// This is like AddRemotes with a single transaction, but waits for pool reorganization. Tests use this method.
-func (pool *TxPool) addRemoteSync(tx *types.Transaction) error {
- errs := pool.AddRemotesSync([]*types.Transaction{tx})
- return errs[0]
-}
-
-// AddRemote enqueues a single transaction into the pool if it is valid. This is a convenience
-// wrapper around AddRemotes.
-//
-// Deprecated: use AddRemotes
-func (pool *TxPool) AddRemote(tx *types.Transaction) error {
- errs := pool.AddRemotes([]*types.Transaction{tx})
- return errs[0]
-}
-
-// addTxs attempts to queue a batch of transactions if they are valid.
-func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
- // Filter out known ones without obtaining the pool lock or recovering signatures
- var (
- errs = make([]error, len(txs))
- news = make([]*types.Transaction, 0, len(txs))
- )
for i, tx := range txs {
- // If the transaction is known, pre-set the error slot
- if pool.all.Get(tx.Hash()) != nil {
- errs[i] = ErrAlreadyKnown
- knownTxMeter.Mark(1)
+ // Mark this transaction belonging to no-subpool
+ splits[i] = -1
+
+ // Try to find a subpool that accepts the transaction
+ for j, subpool := range p.subpools {
+ if subpool.Filter(tx.Tx) {
+ txsets[j] = append(txsets[j], tx)
+ splits[i] = j
+ break
+ }
+ }
+ }
+ // Add the transactions split apart to the individual subpools and piece
+ // back the errors into the original sort order.
+ errsets := make([][]error, len(p.subpools))
+ for i := 0; i < len(p.subpools); i++ {
+ errsets[i] = p.subpools[i].Add(txsets[i], local, sync)
+ }
+ errs := make([]error, len(txs))
+ for i, split := range splits {
+ // If the transaction was rejected by all subpools, mark it unsupported
+ if split == -1 {
+ errs[i] = core.ErrTxTypeNotSupported
continue
}
- // Exclude transactions with basic errors, e.g invalid signatures and
- // insufficient intrinsic gas as soon as possible and cache senders
- // in transactions before obtaining lock
- if err := pool.validateTxBasics(tx, local); err != nil {
- errs[i] = err
- invalidTxMeter.Mark(1)
- continue
- }
- // Accumulate all unknown transactions for deeper processing
- news = append(news, tx)
- }
- if len(news) == 0 {
- return errs
- }
-
- // Process all the new transaction and merge any errors into the original slice
- pool.mu.Lock()
- newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
- pool.mu.Unlock()
-
- var nilSlot = 0
- for _, err := range newErrs {
- for errs[nilSlot] != nil {
- nilSlot++
- }
- errs[nilSlot] = err
- nilSlot++
- }
- // Reorg the pool internals if needed and return
- done := pool.requestPromoteExecutables(dirtyAddrs)
- if sync {
- <-done
+ // Find which subpool handled it and pull in the corresponding error
+ errs[i] = errsets[split][0]
+ errsets[split] = errsets[split][1:]
}
return errs
}
-// addTxsLocked attempts to queue a batch of transactions if they are valid.
-// The transaction pool lock must be held.
-func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) {
- dirty := newAccountSet(pool.signer)
- errs := make([]error, len(txs))
- for i, tx := range txs {
- replaced, err := pool.add(tx, local)
- errs[i] = err
- if err == nil && !replaced {
- dirty.addTx(tx)
+// Pending retrieves all currently processable transactions, grouped by origin
+// account and sorted by nonce.
+func (p *TxPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
+ txs := make(map[common.Address][]*types.Transaction)
+ for _, subpool := range p.subpools {
+ maps.Copy(txs, subpool.Pending(enforceTips))
+ }
+ return txs
+}
+
+// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and starts sending
+// events to the given channel.
+func (p *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
+ subs := make([]event.Subscription, len(p.subpools))
+ for i, subpool := range p.subpools {
+ subs[i] = subpool.SubscribeTransactions(ch)
+ }
+ return p.subs.Track(event.JoinSubscriptions(subs...))
+}
+
+// Nonce returns the next nonce of an account, with all transactions executable
+// by the pool already applied on top.
+func (p *TxPool) Nonce(addr common.Address) uint64 {
+ // Since (for now) accounts are unique to subpools, only one pool will have
+ // (at max) a non-state nonce. To avoid stateful lookups, just return the
+ // highest nonce for now.
+ var nonce uint64
+ for _, subpool := range p.subpools {
+ if next := subpool.Nonce(addr); nonce < next {
+ nonce = next
}
}
- validTxMeter.Mark(int64(len(dirty.accounts)))
- return errs, dirty
+ return nonce
}
-// Status returns the status (unknown/pending/queued) of a batch of transactions
-// identified by their hashes.
-func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
- status := make([]TxStatus, len(hashes))
- for i, hash := range hashes {
- tx := pool.Get(hash)
- if tx == nil {
- continue
- }
- from, _ := types.Sender(pool.signer, tx) // already validated
- pool.mu.RLock()
- if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
- status[i] = TxStatusPending
- } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
- status[i] = TxStatusQueued
- }
- // implicit else: the tx may have been included into a block between
- // checking pool.Get and obtaining the lock. In that case, TxStatusUnknown is correct
- pool.mu.RUnlock()
+// Stats retrieves the current pool stats, namely the number of pending and the
+// number of queued (non-executable) transactions.
+func (p *TxPool) Stats() (int, int) {
+ var runnable, blocked int
+ for _, subpool := range p.subpools {
+ run, block := subpool.Stats()
+
+ runnable += run
+ blocked += block
}
- return status
+ return runnable, blocked
}
-// Get returns a transaction if it is contained in the pool and nil otherwise.
-func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
- return pool.all.Get(hash)
-}
-
-// Has returns an indicator whether txpool has a transaction cached with the
-// given hash.
-func (pool *TxPool) Has(hash common.Hash) bool {
- return pool.all.Get(hash) != nil
-}
-
-// removeTx removes a single transaction from the queue, moving all subsequent
-// transactions back to the future queue.
-// Returns the number of transactions removed from the pending queue.
-func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) int {
- // Fetch the transaction we wish to delete
- tx := pool.all.Get(hash)
- if tx == nil {
- return 0
- }
- addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
-
- // Remove it from the list of known transactions
- pool.all.Remove(hash)
- if outofbound {
- pool.priced.Removed(1)
- }
- if pool.locals.contains(addr) {
- localGauge.Dec(1)
- }
- // Remove the transaction from the pending lists and reset the account nonce
- if pending := pool.pending[addr]; pending != nil {
- if removed, invalids := pending.Remove(tx); removed {
- // If no more pending transactions are left, remove the list
- if pending.Empty() {
- delete(pool.pending, addr)
- }
- // Postpone any invalidated transactions
- for _, tx := range invalids {
- // Internal shuffle shouldn't touch the lookup set.
- pool.enqueueTx(tx.Hash(), tx, false, false)
- }
- // Update the account nonce if needed
- pool.pendingNonces.setIfLower(addr, tx.Nonce())
- // Reduce the pending counter
- pendingGauge.Dec(int64(1 + len(invalids)))
- return 1 + len(invalids)
- }
- }
- // Transaction is in the future queue
- if future := pool.queue[addr]; future != nil {
- if removed, _ := future.Remove(tx); removed {
- // Reduce the queued counter
- queuedGauge.Dec(1)
- }
- if future.Empty() {
- delete(pool.queue, addr)
- delete(pool.beats, addr)
- }
- }
- return 0
-}
-
-// requestReset requests a pool reset to the new head block.
-// The returned channel is closed when the reset has occurred.
-func (pool *TxPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} {
- select {
- case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}:
- return <-pool.reorgDoneCh
- case <-pool.reorgShutdownCh:
- return pool.reorgShutdownCh
- }
-}
-
-// requestPromoteExecutables requests transaction promotion checks for the given addresses.
-// The returned channel is closed when the promotion checks have occurred.
-func (pool *TxPool) requestPromoteExecutables(set *accountSet) chan struct{} {
- select {
- case pool.reqPromoteCh <- set:
- return <-pool.reorgDoneCh
- case <-pool.reorgShutdownCh:
- return pool.reorgShutdownCh
- }
-}
-
-// queueTxEvent enqueues a transaction event to be sent in the next reorg run.
-func (pool *TxPool) queueTxEvent(tx *types.Transaction) {
- select {
- case pool.queueTxEventCh <- tx:
- case <-pool.reorgShutdownCh:
- }
-}
-
-// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not
-// call those methods directly, but request them being run using requestReset and
-// requestPromoteExecutables instead.
-func (pool *TxPool) scheduleReorgLoop() {
+// Content retrieves the data content of the transaction pool, returning all the
+// pending as well as queued transactions, grouped by account and sorted by nonce.
+func (p *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
var (
- curDone chan struct{} // non-nil while runReorg is active
- nextDone = make(chan struct{})
- launchNextRun bool
- reset *txpoolResetRequest
- dirtyAccounts *accountSet
- queuedEvents = make(map[common.Address]*sortedMap)
+ runnable = make(map[common.Address][]*types.Transaction)
+ blocked = make(map[common.Address][]*types.Transaction)
)
- for {
- // Launch next background reorg if needed
- if curDone == nil && launchNextRun {
- // Run the background reorg and announcements
- go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents)
+ for _, subpool := range p.subpools {
+ run, block := subpool.Content()
- // Prepare everything for the next round of reorg
- curDone, nextDone = nextDone, make(chan struct{})
- launchNextRun = false
-
- reset, dirtyAccounts = nil, nil
- queuedEvents = make(map[common.Address]*sortedMap)
+ for addr, txs := range run {
+ runnable[addr] = txs
}
-
- select {
- case req := <-pool.reqResetCh:
- // Reset request: update head if request is already pending.
- if reset == nil {
- reset = req
- } else {
- reset.newHead = req.newHead
- }
- launchNextRun = true
- pool.reorgDoneCh <- nextDone
-
- case req := <-pool.reqPromoteCh:
- // Promote request: update address set if request is already pending.
- if dirtyAccounts == nil {
- dirtyAccounts = req
- } else {
- dirtyAccounts.merge(req)
- }
- launchNextRun = true
- pool.reorgDoneCh <- nextDone
-
- case tx := <-pool.queueTxEventCh:
- // Queue up the event, but don't schedule a reorg. It's up to the caller to
- // request one later if they want the events sent.
- addr, _ := types.Sender(pool.signer, tx)
- if _, ok := queuedEvents[addr]; !ok {
- queuedEvents[addr] = newSortedMap()
- }
- queuedEvents[addr].Put(tx)
-
- case <-curDone:
- curDone = nil
-
- case <-pool.reorgShutdownCh:
- // Wait for current run to finish.
- if curDone != nil {
- <-curDone
- }
- close(nextDone)
- return
+ for addr, txs := range block {
+ blocked[addr] = txs
}
}
+ return runnable, blocked
}
-// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
-func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) {
- defer func(t0 time.Time) {
- reorgDurationTimer.Update(time.Since(t0))
- }(time.Now())
- defer close(done)
-
- var promoteAddrs []common.Address
- if dirtyAccounts != nil && reset == nil {
- // Only dirty accounts need to be promoted, unless we're resetting.
- // For resets, all addresses in the tx queue will be promoted and
- // the flatten operation can be avoided.
- promoteAddrs = dirtyAccounts.flatten()
- }
- pool.mu.Lock()
- if reset != nil {
- // Reset from the old head to the new, rescheduling any reorged transactions
- pool.reset(reset.oldHead, reset.newHead)
-
- // Nonces were reset, discard any events that became stale
- for addr := range events {
- events[addr].Forward(pool.pendingNonces.get(addr))
- if events[addr].Len() == 0 {
- delete(events, addr)
- }
- }
- // Reset needs promote for all addresses
- promoteAddrs = make([]common.Address, 0, len(pool.queue))
- for addr := range pool.queue {
- promoteAddrs = append(promoteAddrs, addr)
+// ContentFrom retrieves the data content of the transaction pool, returning the
+// pending as well as queued transactions of this address, grouped by nonce.
+func (p *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
+ for _, subpool := range p.subpools {
+ run, block := subpool.ContentFrom(addr)
+ if len(run) != 0 || len(block) != 0 {
+ return run, block
}
}
- // Check for pending transactions for every account that sent new ones
- promoted := pool.promoteExecutables(promoteAddrs)
-
- // If a new block appeared, validate the pool of pending transactions. This will
- // remove any transaction that has been included in the block or was invalidated
- // because of another transaction (e.g. higher gas price).
- if reset != nil {
- pool.demoteUnexecutables()
- if reset.newHead != nil && pool.chainconfig.IsEIP1559(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) {
- pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead)
- pool.priced.SetBaseFee(pendingBaseFee)
- }
- // Update all accounts to the latest known pending nonce
- nonces := make(map[common.Address]uint64, len(pool.pending))
- for addr, list := range pool.pending {
- highestPending := list.LastElement()
- nonces[addr] = highestPending.Nonce() + 1
- }
- pool.pendingNonces.setAll(nonces)
- }
- // Ensure pool.queue and pool.pending sizes stay within the configured limits.
- pool.truncatePending()
- pool.truncateQueue()
-
- dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg))
- pool.changesSinceReorg = 0 // Reset change counter
- pool.mu.Unlock()
-
- // Notify subsystems for newly added transactions
- for _, tx := range promoted {
- addr, _ := types.Sender(pool.signer, tx)
- if _, ok := events[addr]; !ok {
- events[addr] = newSortedMap()
- }
- events[addr].Put(tx)
- }
- if len(events) > 0 {
- var txs []*types.Transaction
- for _, set := range events {
- txs = append(txs, set.Flatten()...)
- }
- pool.txFeed.Send(core.NewTxsEvent{Txs: txs})
- }
+ return []*types.Transaction{}, []*types.Transaction{}
}
-// reset retrieves the current state of the blockchain and ensures the content
-// of the transaction pool is valid with regard to the chain state.
-func (pool *TxPool) reset(oldHead, newHead *types.Header) {
- // If we're reorging an old state, reinject all dropped transactions
- var reinject types.Transactions
-
- if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
- // If the reorg is too deep, avoid doing it (will happen during fast sync)
- oldNum := oldHead.Number.Uint64()
- newNum := newHead.Number.Uint64()
-
- if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
- log.Debug("Skipping deep transaction reorg", "depth", depth)
- } else {
- // Reorg seems shallow enough to pull in all transactions into memory
- var discarded, included types.Transactions
- var (
- rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
- add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
- )
- if rem == nil {
- // This can happen if a setHead is performed, where we simply discard the old
- // head from the chain.
- // If that is the case, we don't have the lost transactions anymore, and
- // there's nothing to add
- if newNum >= oldNum {
- // If we reorged to a same or higher number, then it's not a case of setHead
- log.Warn("Transaction pool reset with missing oldhead",
- "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
- return
- }
- // If the reorg ended up on a lower number, it's indicative of setHead being the cause
- log.Debug("Skipping transaction reset caused by setHead",
- "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
- // We still need to update the current state s.th. the lost transactions can be readded by the user
- } else {
- for rem.NumberU64() > add.NumberU64() {
- discarded = append(discarded, rem.Transactions()...)
- if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
- log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
- return
- }
- }
- for add.NumberU64() > rem.NumberU64() {
- included = append(included, add.Transactions()...)
- if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
- log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
- return
- }
- }
- for rem.Hash() != add.Hash() {
- discarded = append(discarded, rem.Transactions()...)
- if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
- log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
- return
- }
- included = append(included, add.Transactions()...)
- if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
- log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
- return
- }
- }
- reinject = types.TxDifference(discarded, included)
- }
+// Locals retrieves the accounts currently considered local by the pool.
+func (p *TxPool) Locals() []common.Address {
+ // Retrieve the locals from each subpool and deduplicate them
+ locals := make(map[common.Address]struct{})
+ for _, subpool := range p.subpools {
+ for _, local := range subpool.Locals() {
+ locals[local] = struct{}{}
}
}
- // Initialize the internal state to the current head
- if newHead == nil {
- newHead = pool.chain.CurrentBlock() // Special case during testing
+ // Flatten and return the deduplicated local set
+ flat := make([]common.Address, 0, len(locals))
+ for local := range locals {
+ flat = append(flat, local)
}
- statedb, err := pool.chain.StateAt(newHead.Root)
- if err != nil {
- log.Error("Failed to reset txpool state", "err", err)
- return
- }
- pool.currentHead.Store(newHead)
- pool.currentState = statedb
- pool.trc21FeeCapacity = statedb.GetTRC21FeeCapacityFromStateWithCache(newHead.Root)
- pool.pendingNonces = newNoncer(statedb)
-
- // Inject any transactions discarded due to reorgs
- log.Debug("Reinjecting stale transactions", "count", len(reinject))
- core.SenderCacher.Recover(pool.signer, reinject)
- pool.addTxsLocked(reinject, false)
+ return flat
}
-// promoteExecutables moves transactions that have become processable from the
-// future queue to the set of pending transactions. During this process, all
-// invalidated transactions (low nonce, low balance) are deleted.
-func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
- log.Debug("start promoteExecutables")
- defer func(start time.Time) {
- log.Debug("end promoteExecutables", "time", common.PrettyDuration(time.Since(start)))
- }(time.Now())
-
- // Track the promoted transactions to broadcast them at once
- var promoted []*types.Transaction
-
- // Iterate over all accounts and promote any executable transactions
- gasLimit := pool.currentHead.Load().GasLimit
- for _, addr := range accounts {
- list := pool.queue[addr]
- if list == nil {
- continue // Just in case someone calls with a non existing account
- }
- // Drop all transactions that are deemed too old (low nonce)
- forwards := list.Forward(pool.currentState.GetNonce(addr))
- for _, tx := range forwards {
- hash := tx.Hash()
- pool.all.Remove(hash)
- }
- log.Trace("Removed old queued transactions", "count", len(forwards))
- // Drop all transactions that are too costly (low balance or out of gas)
- var number *big.Int = nil
- if pool.chain.CurrentHeader() != nil {
- number = pool.chain.CurrentHeader().Number
- }
- drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
- for _, tx := range drops {
- hash := tx.Hash()
- pool.all.Remove(hash)
- }
- log.Trace("Removed unpayable queued transactions", "count", len(drops))
- queuedNofundsMeter.Mark(int64(len(drops)))
-
- // Gather all executable transactions and promote them
- readies := list.Ready(pool.pendingNonces.get(addr))
- for _, tx := range readies {
- hash := tx.Hash()
- if pool.promoteTx(addr, hash, tx) {
- promoted = append(promoted, tx)
- }
- }
- log.Trace("Promoted queued transactions", "count", len(promoted))
- queuedGauge.Dec(int64(len(readies)))
-
- // Drop all transactions over the allowed limit
- var caps types.Transactions
- if !pool.locals.contains(addr) {
- caps = list.Cap(int(pool.config.AccountQueue))
- for _, tx := range caps {
- hash := tx.Hash()
- pool.all.Remove(hash)
- log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
- }
- queuedRateLimitMeter.Mark(int64(len(caps)))
- }
- // Mark all the items dropped as removed
- pool.priced.Removed(len(forwards) + len(drops) + len(caps))
- queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
- if pool.locals.contains(addr) {
- localGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
- }
- // Delete the entire queue entry if it became empty.
- if list.Empty() {
- delete(pool.queue, addr)
- delete(pool.beats, addr)
- }
- }
- return promoted
-}
-
-// truncatePending removes transactions from the pending queue if the pool is above the
-// pending limit. The algorithm tries to reduce transaction counts by an approximately
-// equal number for all for accounts with many pending transactions.
-func (pool *TxPool) truncatePending() {
- pending := uint64(0)
- for _, list := range pool.pending {
- pending += uint64(list.Len())
- }
- if pending <= pool.config.GlobalSlots {
- return
- }
-
- pendingBeforeCap := pending
- // Assemble a spam order to penalize large transactors first
- spammers := prque.New[int64, common.Address](nil)
- for addr, list := range pool.pending {
- // Only evict transactions from high rollers
- if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
- spammers.Push(addr, int64(list.Len()))
- }
- }
- // Gradually drop transactions from offenders
- offenders := []common.Address{}
- for pending > pool.config.GlobalSlots && !spammers.Empty() {
- // Retrieve the next offender if not local address
- offender, _ := spammers.Pop()
- offenders = append(offenders, offender)
-
- // Equalize balances until all the same or below threshold
- if len(offenders) > 1 {
- // Calculate the equalization threshold for all current offenders
- threshold := pool.pending[offender].Len()
-
- // Iteratively reduce all offenders until below limit or threshold reached
- for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
- for i := 0; i < len(offenders)-1; i++ {
- list := pool.pending[offenders[i]]
-
- caps := list.Cap(list.Len() - 1)
- for _, tx := range caps {
- // Drop the transaction from the global pools too
- hash := tx.Hash()
- pool.all.Remove(hash)
-
- // Update the account nonce to the dropped transaction
- pool.pendingNonces.setIfLower(offenders[i], tx.Nonce())
- log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
- }
- pool.priced.Removed(len(caps))
- pendingGauge.Dec(int64(len(caps)))
- if pool.locals.contains(offenders[i]) {
- localGauge.Dec(int64(len(caps)))
- }
- pending--
- }
- }
- }
- }
-
- // If still above threshold, reduce to limit or min allowance
- if pending > pool.config.GlobalSlots && len(offenders) > 0 {
- for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
- for _, addr := range offenders {
- list := pool.pending[addr]
-
- caps := list.Cap(list.Len() - 1)
- for _, tx := range caps {
- // Drop the transaction from the global pools too
- hash := tx.Hash()
- pool.all.Remove(hash)
-
- // Update the account nonce to the dropped transaction
- pool.pendingNonces.setIfLower(addr, tx.Nonce())
- log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
- }
- pool.priced.Removed(len(caps))
- pendingGauge.Dec(int64(len(caps)))
- if pool.locals.contains(addr) {
- localGauge.Dec(int64(len(caps)))
- }
- pending--
- }
- }
- }
- pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending))
-}
-
-// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
-func (pool *TxPool) truncateQueue() {
- queued := uint64(0)
- for _, list := range pool.queue {
- queued += uint64(list.Len())
- }
- if queued <= pool.config.GlobalQueue {
- return
- }
-
- // Sort all accounts with queued transactions by heartbeat
- addresses := make(addressesByHeartbeat, 0, len(pool.queue))
- for addr := range pool.queue {
- if !pool.locals.contains(addr) { // don't drop locals
- addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
- }
- }
- sort.Sort(sort.Reverse(addresses))
-
- // Drop transactions until the total is below the limit or only locals remain
- for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
- addr := addresses[len(addresses)-1]
- list := pool.queue[addr.address]
-
- addresses = addresses[:len(addresses)-1]
-
- // Drop all transactions if they are less than the overflow
- if size := uint64(list.Len()); size <= drop {
- for _, tx := range list.Flatten() {
- pool.removeTx(tx.Hash(), true)
- }
- drop -= size
- queuedRateLimitMeter.Mark(int64(size))
- continue
- }
- // Otherwise drop only last few transactions
- txs := list.Flatten()
- for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
- pool.removeTx(txs[i].Hash(), true)
- drop--
- queuedRateLimitMeter.Mark(1)
- }
- }
-}
-
-// 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
- gasLimit := pool.currentHead.Load().GasLimit
- for addr, list := range pool.pending {
- nonce := pool.currentState.GetNonce(addr)
-
- // Drop all transactions that are deemed too old (low nonce)
- olds := list.Forward(nonce)
- for _, tx := range olds {
- hash := tx.Hash()
- pool.all.Remove(hash)
- log.Trace("Removed old pending transaction", "hash", hash)
- }
- // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
- var number *big.Int = nil
- if pool.chain.CurrentHeader() != nil {
- number = pool.chain.CurrentHeader().Number
- }
- drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
- for _, tx := range drops {
- hash := tx.Hash()
- 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 {
- hash := tx.Hash()
- log.Trace("Demoting pending transaction", "hash", hash)
-
- // Internal shuffle shouldn't touch the lookup set.
- pool.enqueueTx(hash, tx, false, false)
- }
- pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
- if pool.locals.contains(addr) {
- localGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
- }
- // If there's a gap in front, alert (should never happen) and postpone all transactions
- if list.Len() > 0 && list.txs.Get(nonce) == nil {
- gapped := list.Cap(0)
- for _, tx := range gapped {
- hash := tx.Hash()
- log.Warn("Demoting invalidated transaction", "hash", hash)
-
- // Internal shuffle shouldn't touch the lookup set.
- pool.enqueueTx(hash, tx, false, false)
- }
- pendingGauge.Dec(int64(len(gapped)))
- }
- // Delete the entire pending entry if it became empty.
- if list.Empty() {
- delete(pool.pending, addr)
+// Status returns the known status (unknown/pending/queued) of a transaction
+// identified by their hashes.
+func (p *TxPool) Status(hash common.Hash) TxStatus {
+ for _, subpool := range p.subpools {
+ if status := subpool.Status(hash); status != TxStatusUnknown {
+ return status
}
}
+ return TxStatusUnknown
}
+// SetSigner sets the function to identify signer accounts.
func (pool *TxPool) SetSigner(f func(address common.Address) bool) {
- pool.isSigner = f
+ for _, subpool := range pool.subpools {
+ subpool.SetSigner(f)
+ }
}
+// IsSigner checks if the given address is a signer.
func (pool *TxPool) IsSigner(addr common.Address) bool {
- return pool.isSigner != nil && pool.isSigner(addr)
-}
-
-func (pool *TxPool) IsNotSigner(addr common.Address) bool {
- return pool.isSigner != nil && !pool.isSigner(addr)
-}
-
-// addressByHeartbeat is an account address tagged with its last activity timestamp.
-type addressByHeartbeat struct {
- address common.Address
- heartbeat time.Time
-}
-
-type addressesByHeartbeat []addressByHeartbeat
-
-func (a addressesByHeartbeat) Len() int { return len(a) }
-func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
-func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-
-// accountSet is simply a set of addresses to check for existence, and a signer
-// capable of deriving addresses from transactions.
-type accountSet struct {
- accounts map[common.Address]struct{}
- signer types.Signer
- cache *[]common.Address
-}
-
-// newAccountSet creates a new address set with an associated signer for sender
-// derivations.
-func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet {
- as := &accountSet{
- accounts: make(map[common.Address]struct{}, len(addrs)),
- signer: signer,
- }
- for _, addr := range addrs {
- as.add(addr)
- }
- return as
-}
-
-// contains checks if a given address is contained within the set.
-func (as *accountSet) contains(addr common.Address) bool {
- _, exist := as.accounts[addr]
- return exist
-}
-
-func (as *accountSet) empty() bool {
- return len(as.accounts) == 0
-}
-
-// containsTx checks if the sender of a given tx is within the set. If the sender
-// cannot be derived, this method returns false.
-func (as *accountSet) containsTx(tx *types.Transaction) bool {
- if addr, err := types.Sender(as.signer, tx); err == nil {
- return as.contains(addr)
+ for _, subpool := range pool.subpools {
+ if subpool.IsSigner(addr) {
+ return true
+ }
}
return false
}
-
-// add inserts a new address into the set to track.
-func (as *accountSet) add(addr common.Address) {
- as.accounts[addr] = struct{}{}
- as.cache = nil
-}
-
-// addTx adds the sender of tx into the set.
-func (as *accountSet) addTx(tx *types.Transaction) {
- if addr, err := types.Sender(as.signer, tx); err == nil {
- as.add(addr)
- }
-}
-
-// flatten returns the list of addresses within this set, also caching it for later
-// reuse. The returned slice should not be changed!
-func (as *accountSet) flatten() []common.Address {
- if as.cache == nil {
- accounts := make([]common.Address, 0, len(as.accounts))
- for account := range as.accounts {
- accounts = append(accounts, account)
- }
- as.cache = &accounts
- }
- return *as.cache
-}
-
-// merge adds all addresses from the 'other' set into 'as'.
-func (as *accountSet) merge(other *accountSet) {
- for addr := range other.accounts {
- as.accounts[addr] = struct{}{}
- }
- as.cache = nil
-}
-
-// lookup is used internally by TxPool to track transactions while allowing
-// lookup without mutex contention.
-//
-// Note, although this type is properly protected against concurrent access, it
-// is **not** a type that should ever be mutated or even exposed outside of the
-// transaction pool, since its internal state is tightly coupled with the pools
-// internal mechanisms. The sole purpose of the type is to permit out-of-bound
-// peeking into the pool in TxPool.Get without having to acquire the widely scoped
-// TxPool.mu mutex.
-//
-// This lookup set combines the notion of "local transactions", which is useful
-// to build upper-level structure.
-type lookup struct {
- slots int
- lock sync.RWMutex
- locals map[common.Hash]*types.Transaction
- remotes map[common.Hash]*types.Transaction
-}
-
-// newLookup returns a new lookup structure.
-func newLookup() *lookup {
- return &lookup{
- locals: make(map[common.Hash]*types.Transaction),
- remotes: make(map[common.Hash]*types.Transaction),
- }
-}
-
-// Range calls f on each key and value present in the map. The callback passed
-// should return the indicator whether the iteration needs to be continued.
-// Callers need to specify which set (or both) to be iterated.
-func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- if local {
- for key, value := range t.locals {
- if !f(key, value, true) {
- return
- }
- }
- }
- if remote {
- for key, value := range t.remotes {
- if !f(key, value, false) {
- return
- }
- }
- }
-}
-
-// Get returns a transaction if it exists in the lookup, or nil if not found.
-func (t *lookup) Get(hash common.Hash) *types.Transaction {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- if tx := t.locals[hash]; tx != nil {
- return tx
- }
- return t.remotes[hash]
-}
-
-// GetLocal returns a transaction if it exists in the lookup, or nil if not found.
-func (t *lookup) GetLocal(hash common.Hash) *types.Transaction {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.locals[hash]
-}
-
-// GetRemote returns a transaction if it exists in the lookup, or nil if not found.
-func (t *lookup) GetRemote(hash common.Hash) *types.Transaction {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.remotes[hash]
-}
-
-// Count returns the current number of transactions in the lookup.
-func (t *lookup) Count() int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return len(t.locals) + len(t.remotes)
-}
-
-// LocalCount returns the current number of local transactions in the lookup.
-func (t *lookup) LocalCount() int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return len(t.locals)
-}
-
-// RemoteCount returns the current number of remote transactions in the lookup.
-func (t *lookup) RemoteCount() int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return len(t.remotes)
-}
-
-// Slots returns the current number of slots used in the lookup.
-func (t *lookup) Slots() int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.slots
-}
-
-// Add adds a transaction to the lookup.
-func (t *lookup) Add(tx *types.Transaction, local bool) {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- t.slots += numSlots(tx)
- slotsGauge.Update(int64(t.slots))
-
- if local {
- t.locals[tx.Hash()] = tx
- } else {
- t.remotes[tx.Hash()] = tx
- }
-}
-
-// Remove removes a transaction from the lookup.
-func (t *lookup) Remove(hash common.Hash) {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- tx, ok := t.locals[hash]
- if !ok {
- tx, ok = t.remotes[hash]
- }
- if !ok {
- log.Error("No transaction found to be deleted", "hash", hash)
- return
- }
- t.slots -= numSlots(tx)
- slotsGauge.Update(int64(t.slots))
-
- delete(t.locals, hash)
- delete(t.remotes, hash)
-}
-
-// RemoteToLocals migrates the transactions belongs to the given locals to locals
-// set. The assumption is held the locals set is thread-safe to be used.
-func (t *lookup) RemoteToLocals(locals *accountSet) int {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- var migrated int
- for hash, tx := range t.remotes {
- if locals.containsTx(tx) {
- t.locals[hash] = tx
- delete(t.remotes, hash)
- migrated += 1
- }
- }
- return migrated
-}
-
-// RemotesBelowTip finds all remote transactions below the given tip threshold.
-func (t *lookup) 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.GasTipCapIntCmp(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)
-}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 957320a896..91d1be4904 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -765,9 +765,9 @@ func (s *TxByPriceAndTime) Pop() interface{} {
// transactions in a profit-maximizing sorted order, while supporting removing
// entire batches of transactions for non-executable accounts.
type TransactionsByPriceAndNonce struct {
- txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
- heads TxByPriceAndTime // Next transaction for each unique account (price heap)
- signer Signer // Signer for the set of transactions
+ txs map[common.Address][]*Transaction // Per account nonce-sorted list of transactions
+ heads TxByPriceAndTime // Next transaction for each unique account (price heap)
+ signer Signer // Signer for the set of transactions
}
// NewTransactionsByPriceAndNonce creates a transaction set that can retrieve
@@ -777,7 +777,7 @@ type TransactionsByPriceAndNonce struct {
// if after providing it to the constructor.
//
// It also classifies special txs and normal txs
-func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions, payersSwap map[common.Address]*big.Int) (*TransactionsByPriceAndNonce, Transactions) {
+func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address][]*Transaction, payersSwap map[common.Address]*big.Int) (*TransactionsByPriceAndNonce, Transactions) {
// Initialize a price and received time based heap with the head transactions
heads := TxByPriceAndTime{}
heads.payersSwap = payersSwap
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 0331352b4f..8ed5bf6f68 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -57,9 +57,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
}
// LatestSigner returns the 'most permissive' Signer available for the given chain
-// configuration. Specifically, this enables support of EIP-155 replay protection and
-// EIP-2930 access list transactions when their respective forks are scheduled to occur at
-// any block number in the chain config.
+// configuration. Specifically, this enables support of all types of transactions
+// when their respective forks are scheduled to occur at any block number (or time)
+// in the chain config.
//
// Use this in transaction-handling code where the current block number is unknown. If you
// have the current block number available, use MakeSigner instead.
diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go
index 7804ba89f4..259d9db239 100644
--- a/core/types/transaction_test.go
+++ b/core/types/transaction_test.go
@@ -272,7 +272,7 @@ func TestTransactionPriceNonceSort(t *testing.T) {
signer := HomesteadSigner{}
// Generate a batch of transactions with overlapping values, but shifted nonces
- groups := map[common.Address]Transactions{}
+ groups := map[common.Address][]*Transaction{}
for start, key := range keys {
addr := crypto.PubkeyToAddress(key.PublicKey)
for i := 0; i < 25; i++ {
@@ -382,7 +382,7 @@ func TestTransactionTimeSort(t *testing.T) {
signer := HomesteadSigner{}
// Generate a batch of transactions with overlapping prices, but different creation times
- groups := map[common.Address]Transactions{}
+ groups := map[common.Address][]*Transaction{}
for start, key := range keys {
addr := crypto.PubkeyToAddress(key.PublicKey)
@@ -733,11 +733,11 @@ func TestNewTransactionsByPriceAndNonce_SpecialSeparation(t *testing.T) {
}
testCases := []struct {
- name string
- normalCount int
- specialCount int
- expectNormal int
- expectSpecial int
+ name string
+ normalCount int
+ specialCount int
+ expectNormal int
+ expectSpecial int
}{
{"no transactions", 0, 0, 0, 0},
{"only 1 normal", 1, 0, 1, 0},
@@ -762,7 +762,7 @@ func TestNewTransactionsByPriceAndNonce_SpecialSeparation(t *testing.T) {
for i := 0; i < tc.specialCount; i++ {
txs = append(txs, genSpecialTx(uint64(tc.normalCount+i), key))
}
- group := map[common.Address]Transactions{}
+ group := map[common.Address][]*Transaction{}
if len(txs) > 0 {
group[addr] = txs
}
diff --git a/eth/api_backend.go b/eth/api_backend.go
index fde60d3035..fecd4024cd 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -305,7 +305,7 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
}
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
- return b.eth.txPool.AddLocal(signedTx)
+ return b.eth.txPool.Add([]*txpool.Transaction{{Tx: signedTx}}, true, false)[0]
}
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
@@ -318,7 +318,10 @@ func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
}
func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
- return b.eth.txPool.Get(hash)
+ if tx := b.eth.txPool.Get(hash); tx != nil {
+ return tx.Tx
+ }
+ return nil
}
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
@@ -330,20 +333,24 @@ func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (
return b.eth.txPool.Nonce(addr), nil
}
-func (b *EthAPIBackend) Stats() (pending int, queued int) {
+func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
return b.eth.txPool.Stats()
}
-func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
- return b.eth.TxPool().Content()
+func (b *EthAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
+ return b.eth.txPool.Content()
}
-func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
- return b.eth.TxPool().ContentFrom(addr)
+func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
+ return b.eth.txPool.ContentFrom(addr)
+}
+
+func (b *EthAPIBackend) TxPool() *txpool.TxPool {
+ return b.eth.txPool
}
func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return b.eth.TxPool().SubscribeNewTxsEvent(ch)
+ return b.eth.txPool.SubscribeNewTxsEvent(ch)
}
func (b *EthAPIBackend) Downloader() *downloader.Downloader {
@@ -644,7 +651,3 @@ func (b *EthAPIBackend) IsStaking() bool {
func (b *EthAPIBackend) BlockChain() *core.BlockChain {
return b.eth.blockchain
}
-
-func (b *EthAPIBackend) TxPool() *txpool.TxPool {
- return b.eth.txPool
-}
diff --git a/eth/backend.go b/eth/backend.go
index add16777d9..70d9ea18f4 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -41,6 +41,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool/legacypool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
@@ -72,9 +73,10 @@ type Ethereum struct {
shutdownChan chan bool // Channel for shutting down the ethereum
// Handlers
- txPool *txpool.TxPool
- orderPool *txpool.OrderPool
- lendingPool *txpool.LendingPool
+ txPool *txpool.TxPool
+
+ orderPool *legacypool.OrderPool
+ lendingPool *legacypool.LendingPool
blockchain *core.BlockChain
protocolManager *ProtocolManager
@@ -265,9 +267,15 @@ func New(stack *node.Node, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendin
if config.TxPool.Journal != "" {
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
}
- eth.txPool = txpool.New(config.TxPool, eth.chainConfig, eth.blockchain)
- eth.orderPool = txpool.NewOrderPool(eth.chainConfig, eth.blockchain)
- eth.lendingPool = txpool.NewLendingPool(eth.chainConfig, eth.blockchain)
+ legacyPool := legacypool.New(config.TxPool, eth.blockchain)
+
+ eth.txPool, err = txpool.New(new(big.Int).SetUint64(config.TxPool.PriceLimit), eth.blockchain, []txpool.SubPool{legacyPool})
+ if err != nil {
+ return nil, err
+ }
+
+ eth.orderPool = legacypool.NewOrderPool(eth.chainConfig, eth.blockchain)
+ eth.lendingPool = legacypool.NewLendingPool(eth.chainConfig, eth.blockchain)
if eth.protocolManager, err = NewProtocolManagerEx(eth.chainConfig, config.SyncMode, networkID, eth.eventMux, eth.txPool, eth.orderPool, eth.lendingPool, eth.engine, eth.blockchain, chainDb); err != nil {
return nil, err
@@ -564,7 +572,7 @@ func (e *Ethereum) Stop() error {
e.blockchain.Stop()
e.protocolManager.Stop()
- e.txPool.Stop()
+ e.txPool.Close()
e.miner.Stop()
e.eventMux.Stop()
@@ -582,7 +590,7 @@ func (e *Ethereum) GetXDCX() *XDCx.XDCX {
return e.XDCX
}
-func (e *Ethereum) OrderPool() *txpool.OrderPool {
+func (e *Ethereum) OrderPool() *legacypool.OrderPool {
return e.orderPool
}
@@ -591,6 +599,6 @@ func (e *Ethereum) GetXDCXLending() *XDCxlending.Lending {
}
// LendingPool geth eth lending pool
-func (e *Ethereum) LendingPool() *txpool.LendingPool {
+func (e *Ethereum) LendingPool() *legacypool.LendingPool {
return e.lendingPool
}
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 2f09cd94ed..0b2236637c 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -24,7 +24,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core"
- "github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool/legacypool"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice"
"github.com/XinFinOrg/XDPoSChain/params"
@@ -52,7 +52,7 @@ var Defaults = Config{
FilterLogCacheSize: 32,
GasPrice: big.NewInt(0.25 * params.Shannon),
- TxPool: txpool.DefaultConfig,
+ TxPool: legacypool.DefaultConfig,
RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO,
@@ -99,7 +99,7 @@ type Config struct {
GasPrice *big.Int
// Transaction pool options
- TxPool txpool.Config
+ TxPool legacypool.Config
// Gas Price Oracle options
GPO gasprice.Config
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index e30406c607..1753cc2722 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -9,7 +9,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core"
- "github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool/legacypool"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice"
)
@@ -39,7 +39,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
MinerThreads int `toml:",omitempty"`
ExtraData hexutil.Bytes `toml:",omitempty"`
GasPrice *big.Int
- TxPool txpool.Config
+ TxPool legacypool.Config
GPO gasprice.Config
EnablePreimageRecording bool
VMTrace string
@@ -103,7 +103,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
MinerThreads *int `toml:",omitempty"`
ExtraData *hexutil.Bytes `toml:",omitempty"`
GasPrice *big.Int
- TxPool *txpool.Config
+ TxPool *legacypool.Config
GPO *gasprice.Config
EnablePreimageRecording *bool
VMTrace *string
diff --git a/eth/handler.go b/eth/handler.go
index a99e4a433e..a2ded85473 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/bft"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
@@ -779,7 +780,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
pm.knownTxs.Add(tx.Hash(), struct{}{})
}
}
- pm.txpool.AddRemotes(txs)
+ warped := make([]*txpool.Transaction, len(txs))
+ for i := range txs {
+ warped[i] = &txpool.Transaction{Tx: txs[i]}
+ }
+ pm.txpool.Add(warped, false, false)
case msg.Code == OrderTxMsg:
// Transactions arrived, make sure we have a valid and fresh chain to handle them
diff --git a/eth/helper_test.go b/eth/helper_test.go
index c883e6f624..c73db31db7 100644
--- a/eth/helper_test.go
+++ b/eth/helper_test.go
@@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
@@ -68,7 +69,9 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
panic(err)
}
- pm, err := NewProtocolManager(gspec.Config, mode, ethconfig.Defaults.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db)
+ txpool := newTestTxPool()
+ txpool.added = newtx
+ pm, err := NewProtocolManager(gspec.Config, mode, ethconfig.Defaults.NetworkId, evmux, txpool, engine, blockchain, db)
if err != nil {
return nil, nil, err
}
@@ -90,32 +93,68 @@ func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks i
// testTxPool is a fake, helper transaction pool for testing purposes
type testTxPool struct {
- txFeed event.Feed
- pool []*types.Transaction // Collection of all transactions
- added chan<- []*types.Transaction // Notification channel for new transactions
+ pool map[common.Hash]*types.Transaction // Hash map of collected transactions
- lock sync.RWMutex // Protects the transaction pool
+ txFeed event.Feed
+ lock sync.RWMutex // Protects the transaction pool
+
+ added chan<- []*types.Transaction // Notification channel for new transactions
}
-// AddRemotes appends a batch of transactions to the pool, and notifies any
-// listeners if the addition channel is non nil
-func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
+// newTestTxPool creates a mock transaction pool.
+func newTestTxPool() *testTxPool {
+ return &testTxPool{
+ pool: make(map[common.Hash]*types.Transaction),
+ }
+}
+
+// Has returns an indicator whether txpool has a transaction
+// cached with the given hash.
+func (p *testTxPool) Has(hash common.Hash) bool {
p.lock.Lock()
defer p.lock.Unlock()
- p.pool = append(p.pool, txs...)
- if p.added != nil {
- p.added <- txs
+ return p.pool[hash] != nil
+}
+
+// Get retrieves the transaction from local txpool with given
+// tx hash.
+func (p *testTxPool) Get(hash common.Hash) *txpool.Transaction {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ if tx := p.pool[hash]; tx != nil {
+ return &txpool.Transaction{Tx: tx}
}
- return make([]error, len(txs))
+ return nil
+}
+
+// Add appends a batch of transactions to the pool, and notifies any
+// listeners if the addition channel is non nil
+func (p *testTxPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error {
+ unwrapped := make([]*types.Transaction, len(txs))
+ for i, tx := range txs {
+ unwrapped[i] = tx.Tx
+ }
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ for _, tx := range unwrapped {
+ p.pool[tx.Hash()] = tx
+ }
+
+ if p.added != nil {
+ p.added <- unwrapped
+ }
+ return make([]error, len(unwrapped))
}
// Pending returns all the transactions known to the pool
-func (p *testTxPool) Pending(enforceTips bool) map[common.Address]types.Transactions {
+func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
p.lock.RLock()
defer p.lock.RUnlock()
- batches := make(map[common.Address]types.Transactions)
+ batches := make(map[common.Address][]*types.Transaction)
for _, tx := range p.pool {
from, _ := types.Sender(types.HomesteadSigner{}, tx)
batches[from] = append(batches[from], tx)
@@ -126,6 +165,8 @@ func (p *testTxPool) Pending(enforceTips bool) map[common.Address]types.Transact
return batches
}
+// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and
+// send events to the given channel.
func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return p.txFeed.Subscribe(ch)
}
diff --git a/eth/protocol.go b/eth/protocol.go
index 6cba0e1eba..f10e58b280 100644
--- a/eth/protocol.go
+++ b/eth/protocol.go
@@ -23,6 +23,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/rlp"
@@ -103,12 +104,12 @@ var errorToString = map[int]string{
}
type txPool interface {
- // AddRemotes should add the given transactions to the pool.
- AddRemotes([]*types.Transaction) []error
+ // Add should add the given transactions to the pool.
+ Add(txs []*txpool.Transaction, local bool, sync bool) []error
// Pending should return pending transactions.
// The slice should be modifiable by the caller.
- Pending(enforceTips bool) map[common.Address]types.Transactions
+ Pending(enforceTips bool) map[common.Address][]*types.Transaction
// SubscribeNewTxsEvent should return an event subscription of
// NewTxsEvent and send events to the given channel.
diff --git a/eth/protocol_test.go b/eth/protocol_test.go
index bb8c09e636..271cde6d15 100644
--- a/eth/protocol_test.go
+++ b/eth/protocol_test.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
@@ -131,11 +132,12 @@ func testSendTransactions(t *testing.T, protocol int) {
// Fill the pool with big transactions.
const txsize = txsyncPackSize / 10
- alltxs := make([]*types.Transaction, 100)
+ alltxs := make([]*txpool.Transaction, 100)
for nonce := range alltxs {
- alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
+ tx := newTestTransaction(testAccount, uint64(nonce), txsize)
+ alltxs[nonce] = &txpool.Transaction{Tx: tx}
}
- pm.txpool.AddRemotes(alltxs)
+ pm.txpool.Add(alltxs, false, false)
// Connect several peers. They should all receive the pending transactions.
var wg sync.WaitGroup
@@ -143,7 +145,7 @@ func testSendTransactions(t *testing.T, protocol int) {
defer p.close()
seen := make(map[common.Hash]bool)
for _, tx := range alltxs {
- seen[tx.Hash()] = false
+ seen[tx.Tx.Hash()] = false
}
for n := 0; n < len(alltxs) && !t.Failed(); {
var txs []*types.Transaction
diff --git a/event/multisub.go b/event/multisub.go
new file mode 100644
index 0000000000..1f0af2a292
--- /dev/null
+++ b/event/multisub.go
@@ -0,0 +1,50 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package event
+
+// JoinSubscriptions joins multiple subscriptions to be able to track them as
+// one entity and collectively cancel them or consume any errors from them.
+func JoinSubscriptions(subs ...Subscription) Subscription {
+ return NewSubscription(func(unsubbed <-chan struct{}) error {
+ // Unsubscribe all subscriptions before returning
+ defer func() {
+ for _, sub := range subs {
+ sub.Unsubscribe()
+ }
+ }()
+ // Wait for an error on any of the subscriptions and propagate up
+ errc := make(chan error, len(subs))
+ for i := range subs {
+ go func(sub Subscription) {
+ select {
+ case err := <-sub.Err():
+ if err != nil {
+ errc <- err
+ }
+ case <-unsubbed:
+ }
+ }(subs[i])
+ }
+
+ select {
+ case err := <-errc:
+ return err
+ case <-unsubbed:
+ return nil
+ }
+ })
+}
diff --git a/event/multisub_test.go b/event/multisub_test.go
new file mode 100644
index 0000000000..0758c88f3f
--- /dev/null
+++ b/event/multisub_test.go
@@ -0,0 +1,175 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package event
+
+import (
+ "testing"
+ "time"
+)
+
+func TestMultisub(t *testing.T) {
+ // Create a double subscription and ensure events propagate through
+ var (
+ feed1 Feed
+ feed2 Feed
+ )
+ sink1 := make(chan int, 1)
+ sink2 := make(chan int, 1)
+
+ sub1 := feed1.Subscribe(sink1)
+ sub2 := feed2.Subscribe(sink2)
+
+ sub := JoinSubscriptions(sub1, sub2)
+
+ feed1.Send(1)
+ select {
+ case n := <-sink1:
+ if n != 1 {
+ t.Errorf("sink 1 delivery mismatch: have %d, want %d", n, 1)
+ }
+ default:
+ t.Error("sink 1 missing delivery")
+ }
+
+ feed2.Send(2)
+ select {
+ case n := <-sink2:
+ if n != 2 {
+ t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2)
+ }
+ default:
+ t.Error("sink 2 missing delivery")
+ }
+ // Unsubscribe and ensure no more events are delivered
+ sub.Unsubscribe()
+ select {
+ case <-sub.Err():
+ case <-time.After(50 * time.Millisecond):
+ t.Error("multisub didn't propagate closure")
+ }
+
+ feed1.Send(11)
+ select {
+ case n := <-sink1:
+ t.Errorf("sink 1 unexpected delivery: %d", n)
+ default:
+ }
+
+ feed2.Send(22)
+ select {
+ case n := <-sink2:
+ t.Errorf("sink 2 unexpected delivery: %d", n)
+ default:
+ }
+}
+
+func TestMultisubPartialUnsubscribe(t *testing.T) {
+ // Create a double subscription but terminate one half, ensuring no error
+ // is propagated yet up to the outer subscription
+ var (
+ feed1 Feed
+ feed2 Feed
+ )
+ sink1 := make(chan int, 1)
+ sink2 := make(chan int, 1)
+
+ sub1 := feed1.Subscribe(sink1)
+ sub2 := feed2.Subscribe(sink2)
+
+ sub := JoinSubscriptions(sub1, sub2)
+
+ sub1.Unsubscribe()
+ select {
+ case <-sub.Err():
+ t.Error("multisub propagated closure")
+ case <-time.After(50 * time.Millisecond):
+ }
+ // Ensure that events cross only the second feed
+ feed1.Send(1)
+ select {
+ case n := <-sink1:
+ t.Errorf("sink 1 unexpected delivery: %d", n)
+ default:
+ }
+
+ feed2.Send(2)
+ select {
+ case n := <-sink2:
+ if n != 2 {
+ t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2)
+ }
+ default:
+ t.Error("sink 2 missing delivery")
+ }
+ // Unsubscribe and ensure no more events are delivered
+ sub.Unsubscribe()
+ select {
+ case <-sub.Err():
+ case <-time.After(50 * time.Millisecond):
+ t.Error("multisub didn't propagate closure")
+ }
+
+ feed1.Send(11)
+ select {
+ case n := <-sink1:
+ t.Errorf("sink 1 unexpected delivery: %d", n)
+ default:
+ }
+
+ feed2.Send(22)
+ select {
+ case n := <-sink2:
+ t.Errorf("sink 2 unexpected delivery: %d", n)
+ default:
+ }
+}
+
+func TestMultisubFullUnsubscribe(t *testing.T) {
+ // Create a double subscription and terminate the multi sub, ensuring an
+ // error is propagated up.
+ var (
+ feed1 Feed
+ feed2 Feed
+ )
+ sink1 := make(chan int, 1)
+ sink2 := make(chan int, 1)
+
+ sub1 := feed1.Subscribe(sink1)
+ sub2 := feed2.Subscribe(sink2)
+
+ sub := JoinSubscriptions(sub1, sub2)
+ sub.Unsubscribe()
+ select {
+ case <-sub.Err():
+ case <-time.After(50 * time.Millisecond):
+ t.Error("multisub didn't propagate closure")
+ }
+ // Ensure no more events are delivered
+ feed1.Send(1)
+ select {
+ case n := <-sink1:
+ t.Errorf("sink 1 unexpected delivery: %d", n)
+ default:
+ }
+
+ feed2.Send(2)
+ select {
+ case n := <-sink2:
+ t.Errorf("sink 2 unexpected delivery: %d", n)
+ default:
+ }
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 9f720c912d..a50b3e88b7 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -86,8 +86,8 @@ type Backend interface {
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int)
- TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
- TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions)
+ TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction)
+ TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
ChainConfig() *params.ChainConfig
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 71ace1fd1c..ac065bc7a6 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -370,10 +370,10 @@ func (b *backendMock) GetPoolNonce(ctx context.Context, addr common.Address) (ui
return 0, nil
}
func (b *backendMock) Stats() (pending int, queued int) { return 0, 0 }
-func (b *backendMock) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
+func (b *backendMock) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
return nil, nil
}
-func (b *backendMock) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
+func (b *backendMock) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
return nil, nil
}
func (b *backendMock) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription { return nil }
diff --git a/internal/flags/categories.go b/internal/flags/categories.go
index 1155164be2..79f24e4726 100644
--- a/internal/flags/categories.go
+++ b/internal/flags/categories.go
@@ -22,7 +22,7 @@ const (
EthCategory = "ETHEREUM"
LightCategory = "LIGHT CLIENT"
DevCategory = "DEVELOPER CHAIN"
- TxPoolCategory = "TRANSACTION POOL"
+ TxPoolCategory = "TRANSACTION POOL (EVM)"
PerfCategory = "PERFORMANCE TUNING"
AccountCategory = "ACCOUNT"
APICategory = "API AND CONSOLE"
diff --git a/miner/miner.go b/miner/miner.go
index 8dfe8e762c..db69006de3 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool/legacypool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/ethdb"
@@ -44,8 +45,8 @@ type Backend interface {
TxPool() *txpool.TxPool
ChainDb() ethdb.Database
GetXDCX() *XDCx.XDCX
- OrderPool() *txpool.OrderPool
- LendingPool() *txpool.LendingPool
+ OrderPool() *legacypool.OrderPool
+ LendingPool() *legacypool.LendingPool
GetXDCXLending() *XDCxlending.Lending
}
diff --git a/miner/worker.go b/miner/worker.go
index d656282506..75494d6a82 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -355,7 +355,7 @@ func (w *worker) update() {
// be automatically eliminated.
if atomic.LoadInt32(&w.mining) == 0 {
w.currentMu.Lock()
- txs := make(map[common.Address]types.Transactions)
+ txs := make(map[common.Address][]*types.Transaction, len(ev.Txs))
for _, tx := range ev.Txs {
acc, _ := types.Sender(w.current.signer, tx)
txs[acc] = append(txs[acc], tx)