diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 82ee2fafa8..31eb3512c3 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -17,7 +17,6 @@ package txpool import ( - "container/heap" "context" "errors" "fmt" @@ -28,12 +27,8 @@ import ( "sync/atomic" "time" - "github.com/holiman/uint256" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/tracing" + "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -42,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) const ( @@ -216,42 +212,34 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) conf.Rejournal = time.Second } - if conf.PriceLimit < 1 { log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit) conf.PriceLimit = DefaultConfig.PriceLimit } - 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 } @@ -268,7 +256,6 @@ type TxPool struct { chain blockChain gasPrice *big.Int gasPriceUint *uint256.Int - gasPriceMu sync.RWMutex txFeed event.Feed scope event.SubscriptionScope signer types.Signer @@ -279,21 +266,18 @@ type TxPool struct { eip1559 atomic.Bool // Fork indicator whether we are using EIP-1559 type transactions. shanghai atomic.Bool // Fork indicator whether we are in the Shanghai stage. - currentState *state.StateDB // Current state in the blockchain head - currentStateMutex sync.Mutex // Mutex to protect currentState - pendingNonces *noncer // Pending state tracking virtual nonces - currentMaxGas atomic.Uint64 // Current gas limit for transaction caps + currentState *state.StateDB // Current state in the blockchain head + pendingNonces *noncer // Pending state tracking virtual nonces + currentMaxGas atomic.Uint64 // Current gas limit for transaction caps 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 - pendingCount int - pendingMu sync.RWMutex - 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 + 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 @@ -306,8 +290,6 @@ type TxPool struct { 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. - - promoteTxCh chan struct{} // should be used only for tests } type txpoolResetRequest struct { @@ -316,7 +298,7 @@ type txpoolResetRequest struct { // NewTxPool creates a new transaction pool to gather, sort and filter inbound // transactions from the network. -func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, options ...func(pool *TxPool)) *TxPool { +func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool { // Sanitize the input to ensure no vulnerable gas prices are set config = (&config).sanitize() @@ -340,22 +322,14 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, gasPrice: new(big.Int).SetUint64(config.PriceLimit), gasPriceUint: uint256.NewInt(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()) - // apply options - for _, fn := range options { - fn(pool) - } - // Start the reorg loop early so it can handle requests generated during journal loading. pool.wg.Add(1) go pool.scheduleReorgLoop() @@ -367,7 +341,6 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, 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) } @@ -376,7 +349,6 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, // Subscribe events from blockchain and start the main event loop. pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) pool.wg.Add(1) - go pool.loop() return pool @@ -397,14 +369,12 @@ func (pool *TxPool) loop() { // Track the previous head headers for transaction reorgs head = pool.chain.CurrentBlock() ) - defer report.Stop() defer evict.Stop() defer journal.Stop() // Notify tests that the init phase is done close(pool.initDoneCh) - for { select { // Handle ChainHeadEvent @@ -421,7 +391,9 @@ func (pool *TxPool) loop() { // 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 { @@ -431,45 +403,22 @@ func (pool *TxPool) loop() { // Handle inactive account transaction eviction case <-evict.C: - now := time.Now() - - var ( - list types.Transactions - tx *types.Transaction - toRemove []common.Hash - ) - - pool.mu.RLock() + 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 now.Sub(pool.beats[addr]) > pool.config.Lifetime { - list = pool.queue[addr].Flatten() - for _, tx = range list { - toRemove = append(toRemove, tx.Hash()) + 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.RUnlock() - - if len(toRemove) > 0 { - pool.mu.Lock() - - var hash common.Hash - - for _, hash = range toRemove { - pool.removeTx(hash, true) - } - - pool.mu.Unlock() - } + pool.mu.Unlock() // Handle local transaction journal rotation case <-journal.C: @@ -496,7 +445,6 @@ func (pool *TxPool) Stop() { if pool.journal != nil { pool.journal.close() } - log.Info("Transaction pool stopped") } @@ -508,24 +456,21 @@ func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subsc // GasPrice returns the current gas price enforced by the transaction pool. func (pool *TxPool) GasPrice() *big.Int { - pool.gasPriceMu.RLock() - defer pool.gasPriceMu.RUnlock() + pool.mu.RLock() + defer pool.mu.RUnlock() return new(big.Int).Set(pool.gasPrice) } func (pool *TxPool) GasPriceUint256() *uint256.Int { - pool.gasPriceMu.RLock() - defer pool.gasPriceMu.RUnlock() - return pool.gasPriceUint.Clone() } // SetGasPrice updates the minimum price required by the transaction pool for a // new transaction, and drops all transactions below this threshold. func (pool *TxPool) SetGasPrice(price *big.Int) { - pool.gasPriceMu.Lock() - defer pool.gasPriceMu.Unlock() + pool.mu.Lock() + defer pool.mu.Unlock() old := pool.gasPrice pool.gasPrice = price @@ -538,15 +483,11 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { // if the min miner fee increased, remove transactions below the new threshold if price.Cmp(old) > 0 { - pool.mu.Lock() - defer pool.mu.Unlock() - // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead drop := pool.all.RemotesBelowTip(price) for _, tx := range drop { pool.removeTx(tx.Hash(), false) } - pool.priced.Removed(len(drop)) } @@ -565,6 +506,9 @@ func (pool *TxPool) Nonce(addr common.Address) uint64 { // 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() } @@ -572,69 +516,47 @@ func (pool *TxPool) Stats() (int, int) { // number of queued (non-executable) transactions. func (pool *TxPool) stats() (int, int) { pending := 0 - - pool.pendingMu.RLock() for _, list := range pool.pending { pending += list.Len() } - pool.pendingMu.RUnlock() - - pool.mu.RLock() - queued := 0 for _, list := range pool.queue { queued += list.Len() } - - pool.mu.RUnlock() - 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.pendingMu.RLock() + 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() } - pool.pendingMu.RUnlock() - queued := make(map[common.Address]types.Transactions, len(pool.queue)) - - pool.mu.RLock() - for addr, list := range pool.queue { queued[addr] = list.Flatten() } - - pool.mu.RUnlock() - 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) { - var pending types.Transactions + pool.mu.RLock() + defer pool.mu.RUnlock() - pool.pendingMu.RLock() + var pending types.Transactions if list, ok := pool.pending[addr]; ok { pending = list.Flatten() } - pool.pendingMu.RUnlock() - - pool.mu.RLock() - var queued types.Transactions if list, ok := pool.queue[addr]; ok { queued = list.Flatten() } - - pool.mu.RUnlock() - return pending, queued } @@ -645,74 +567,41 @@ func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types. // 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. -// -//nolint:gocognit -func (pool *TxPool) Pending(ctx context.Context, enforceTips bool) map[common.Address]types.Transactions { - pending := make(map[common.Address]types.Transactions, 10) - - tracing.Exec(ctx, "TxpoolPending", "txpool.Pending()", func(ctx context.Context, span trace.Span) { - tracing.ElapsedTime(ctx, span, "txpool.Pending.RLock()", func(ctx context.Context, s trace.Span) { - pool.pendingMu.RLock() - }) - - defer pool.pendingMu.RUnlock() - - pendingAccounts := len(pool.pending) - - var pendingTxs int - - tracing.ElapsedTime(ctx, span, "Loop", func(ctx context.Context, s trace.Span) { - gasPriceUint := uint256.NewInt(0) - baseFee := uint256.NewInt(0) - - 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 { - pool.pendingMu.RUnlock() - - pool.gasPriceMu.RLock() - if pool.gasPriceUint != nil { - gasPriceUint.Set(pool.gasPriceUint) - } - - pool.priced.urgent.baseFeeMu.Lock() - if pool.priced.urgent.baseFee != nil { - baseFee.Set(pool.priced.urgent.baseFee) - } - pool.priced.urgent.baseFeeMu.Unlock() +func (pool *TxPool) Pending(context context.Context, enforceTips bool) map[common.Address]types.Transactions { + pool.mu.Lock() + defer pool.mu.Unlock() - pool.gasPriceMu.RUnlock() + pending := make(map[common.Address]types.Transactions) + gasPriceUint := uint256.NewInt(0) - pool.pendingMu.RLock() + for addr, list := range pool.pending { + txs := list.Flatten() - if tx.EffectiveGasTipUintLt(gasPriceUint, baseFee) { - txs = txs[:i] - break - } - } + // If the miner requests tip enforcement, cap the lists now + if enforceTips && !pool.locals.contains(addr) { + for i, tx := range txs { + if pool.gasPrice != nil { + gasPriceUint.Set(pool.gasPriceUint) } - if len(txs) > 0 { - pending[addr] = txs - pendingTxs += len(txs) + if tx.EffectiveGasTipUintLt(gasPriceUint, pool.priced.urgent.baseFee) { + txs = txs[:i] + break } } - - tracing.SetAttributes(span, - attribute.Int("pending-transactions", pendingTxs), - attribute.Int("pending-accounts", pendingAccounts), - ) - }) - }) - + } + 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() } @@ -721,22 +610,14 @@ func (pool *TxPool) Locals() []common.Address { // freely modified by calling code. func (pool *TxPool) local() map[common.Address]types.Transactions { txs := make(map[common.Address]types.Transactions) - - pool.locals.m.RLock() - defer pool.locals.m.RUnlock() - for addr := range pool.locals.accounts { - pool.pendingMu.RLock() if pending := pool.pending[addr]; pending != nil { txs[addr] = append(txs[addr], pending.Flatten()...) } - pool.pendingMu.RUnlock() - if queued := pool.queue[addr]; queued != nil { txs[addr] = append(txs[addr], queued.Flatten()...) } } - return txs } @@ -744,21 +625,15 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // 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. -// nolint:gocognit func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { - pool.currentStateMutex.Lock() - defer pool.currentStateMutex.Unlock() - // Accept only legacy transactions until EIP-2718/2930 activates. if !pool.eip2718.Load() && tx.Type() != types.LegacyTxType { return core.ErrTxTypeNotSupported } - // Reject dynamic fee transactions until EIP-1559 activates. if !pool.eip1559.Load() && tx.Type() == types.DynamicFeeTxType { return core.ErrTxTypeNotSupported } - // Reject transactions over defined size to prevent DOS attacks if tx.Size() > txMaxSize { return ErrOversizedData @@ -772,94 +647,43 @@ func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { if tx.Value().Sign() < 0 { return ErrNegativeValue } - // Ensure the transaction doesn't exceed the current block limit gas. if pool.currentMaxGas.Load() < tx.Gas() { return ErrGasLimit } - // Sanity check for extremely large numbers - gasFeeCap := tx.GasFeeCapRef() - if gasFeeCap.BitLen() > 256 { + if tx.GasFeeCap().BitLen() > 256 { return core.ErrFeeCapVeryHigh } - - // do NOT use uint256 here. results vs *big.Int are different - gasTipCap := tx.GasTipCapRef() - if gasTipCap.BitLen() > 256 { + if tx.GasTipCap().BitLen() > 256 { return core.ErrTipVeryHigh } - // Ensure gasFeeCap is greater than or equal to gasTipCap. - gasTipCapU, _ := uint256.FromBig(gasTipCap) - if tx.GasFeeCapUIntLt(gasTipCapU) { + if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { return core.ErrTipAboveFeeCap } - // Make sure the transaction is signed properly. - from, err := types.Sender(pool.signer, tx) - if err != nil && !pool.config.AllowUnprotectedTxs { + if _, err := types.Sender(pool.signer, tx); err != nil { return ErrInvalidSender } - // Drop non-local transactions under our own minimal accepted gas price or tip - pool.gasPriceMu.RLock() - if !local && tx.GasTipCapUIntLt(pool.gasPriceUint) { - pool.gasPriceMu.RUnlock() - return ErrUnderpriced } - - pool.gasPriceMu.RUnlock() - - // Ensure the transaction adheres to nonce ordering - if pool.currentState.GetNonce(from) > tx.Nonce() { - return core.ErrNonceTooLow - } - - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - balance := pool.currentState.GetBalance(from) - // if balance.Cmp(tx.Cost()) < 0 { - // return core.ErrInsufficientFunds - // } - // Verify that replacing transactions will not result in overdraft - pool.pendingMu.RLock() - defer pool.pendingMu.RUnlock() - - list := pool.pending[from] - if list != nil { // Sender already has pending txs - sum := new(big.Int).Add(tx.Cost(), list.totalcost) - if repl := list.txs.Get(tx.Nonce()); repl != nil { - // Deduct the cost of a transaction replaced by this - sum.Sub(sum, repl.Cost()) - } - - if balance.Cmp(sum) < 0 { - log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) - return ErrOverdraft - } - } // Ensure the transaction has more gas than the basic tx fee. intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul.Load(), pool.shanghai.Load()) if err != nil { return err } - if tx.Gas() < intrGas { return core.ErrIntrinsicGas } - return nil } // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). -func (pool *TxPool) validateTx(tx *types.Transaction, _ bool) error { - pool.currentStateMutex.Lock() - defer pool.currentStateMutex.Unlock() - +func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // Signature has been checked already, this cannot error. from, _ := types.Sender(pool.signer, tx) // Ensure the transaction adheres to nonce ordering @@ -881,13 +705,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, _ bool) error { // Deduct the cost of a transaction replaced by this sum.Sub(sum, repl.Cost()) } - if balance.Cmp(sum) < 0 { log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) return ErrOverdraft } } - return nil } @@ -904,7 +726,6 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if pool.all.Get(hash) != nil { log.Trace("Discarding already known transaction", "hash", hash) knownTxMeter.Mark(1) - return false, ErrAlreadyKnown } // Make the local flag. If it's from local source or it's from the network but @@ -915,7 +736,6 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if err := pool.validateTx(tx, isLocal); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) - return false, err } @@ -926,9 +746,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCapUint(), "gasFeeCap", tx.GasFeeCapUint()) + log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) - return false, ErrUnderpriced } @@ -950,13 +769,12 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e 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 pool.isFuture(from, tx) { + 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()) { @@ -967,45 +785,36 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // Add all transactions back to the priced queue if replacesPending { for _, dropTx := range drop { - heap.Push(&pool.priced.urgent, dropTx) + 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.GasTipCapUint(), "gasFeeCap", tx.GasFeeCapUint()) + 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 - pool.pendingMu.RLock() - 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) - pool.pendingCount++ - pool.pendingMu.RUnlock() - 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) @@ -1014,13 +823,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // Successful promotion, bump the heartbeat pool.beats[from] = time.Now() - return old != nil, nil } - - // it is not an unlocking of unlocked because of the return in previous 'if' - pool.pendingMu.RUnlock() - // New transaction isn't replacing a pending one, push into queue replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { @@ -1032,30 +836,35 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e 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 } -// isFuture reports whether the given transaction is immediately executable. -func (pool *TxPool) isFuture(from common.Address, tx *types.Transaction) bool { - list := pool.pending[from] - if list == nil { - return pool.pendingNonces.get(from) != tx.Nonce() +// isGapped reports whether the given transaction is immediately executable. +func (pool *TxPool) isGapped(from common.Address, tx *types.Transaction) bool { + // Short circuit if transaction matches pending nonce and can be promoted + // to pending list as an executable transaction. + next := pool.pendingNonces.get(from) + if tx.Nonce() == next { + return false } - // Sender has pending transactions. - if old := list.txs.Get(tx.Nonce()); old != nil { - return false // It replaces a pending transaction. + // 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 + } } - // Not replacing, check if parent nonce exists in pending. - return list.txs.Get(tx.Nonce()-1) == nil + return false } // enqueueTx inserts a new transaction into the non-executable transaction queue. @@ -1067,7 +876,6 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo 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 @@ -1088,7 +896,6 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo 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) @@ -1097,7 +904,6 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo if _, exist := pool.beats[from]; !exist { pool.beats[from] = time.Now() } - return old != nil, nil } @@ -1108,7 +914,6 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) { 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) } @@ -1119,38 +924,20 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) { // // Note, this method assumes the pool lock is held! func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { - defer func() { - if pool.promoteTxCh == nil { - return - } - - select { - case pool.promoteTxCh <- struct{}{}: - default: - } - }() - // Try to insert the transaction into the pending queue - pool.pendingMu.Lock() if pool.pending[addr] == nil { pool.pending[addr] = newList(true) } - list := pool.pending[addr] inserted, old := list.Add(tx, pool.config.PriceBump) - pool.pendingCount++ - pool.pendingMu.Unlock() - 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()) @@ -1160,13 +947,11 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // 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 } @@ -1182,7 +967,8 @@ func (pool *TxPool) AddLocals(txs []*types.Transaction) []error { // AddLocal enqueues a single local transaction into the pool if it is valid. This is // a convenience wrapper around AddLocals. func (pool *TxPool) AddLocal(tx *types.Transaction) error { - return pool.addTx(tx, !pool.config.NoLocals, true) + errs := pool.AddLocals([]*types.Transaction{tx}) + return errs[0] } // AddRemotes enqueues a batch of transactions into the pool if they are valid. If the @@ -1199,10 +985,6 @@ func (pool *TxPool) AddRemotesSync(txs []*types.Transaction) []error { return pool.addTxs(txs, false, true) } -func (pool *TxPool) AddRemoteSync(txs *types.Transaction) error { - return pool.addTx(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}) @@ -1211,6 +993,8 @@ func (pool *TxPool) addRemoteSync(tx *types.Transaction) error { // 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] @@ -1222,42 +1006,26 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { var ( errs = make([]error, len(txs)) news = make([]*types.Transaction, 0, len(txs)) - - hash common.Hash ) - for i, tx := range txs { // If the transaction is known, pre-set the error slot - hash = tx.Hash() - - if pool.all.Get(hash) != nil { + if pool.all.Get(tx.Hash()) != nil { errs[i] = ErrAlreadyKnown - knownTxMeter.Mark(1) - continue } - - // Exclude transactions with basic errors, e.g. invalid signatures and + // 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 } - - if pool.config.AllowUnprotectedTxs { - pool.signer = types.NewFakeSigner(tx.ChainId()) - } - // Accumulate all unknown transactions for deeper processing news = append(news, tx) } - if len(news) == 0 { return errs } @@ -1272,9 +1040,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { for errs[nilSlot] != nil { nilSlot++ } - errs[nilSlot] = err - nilSlot++ } // Reorg the pool internals if needed and return @@ -1282,153 +1048,45 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { if sync { <-done } - return errs } -// addTxs attempts to queue a batch of transactions if they are valid. -func (pool *TxPool) addTx(tx *types.Transaction, local, sync bool) error { - // Filter out known ones without obtaining the pool lock or recovering signatures - var ( - err error - hash common.Hash - ) - - func() { - // If the transaction is known, pre-set the error slot - hash = tx.Hash() - - if pool.all.Get(hash) != nil { - err = ErrAlreadyKnown - - knownTxMeter.Mark(1) - - return - } - - // Exclude transactions with invalid signatures as soon as - // possible and cache senders in transactions before - // obtaining lock - if pool.config.AllowUnprotectedTxs { - pool.signer = types.NewFakeSigner(tx.ChainId()) - } - - _, err = types.Sender(pool.signer, tx) - if err != nil { - invalidTxMeter.Mark(1) - - return - } - }() - - if err != nil { - return err - } - - var dirtyAddrs *accountSet - - // Process all the new transaction and merge any errors into the original slice - pool.mu.Lock() - err, dirtyAddrs = pool.addTxLocked(tx, local) - pool.mu.Unlock() - - // Reorg the pool internals if needed and return - done := pool.requestPromoteExecutables(dirtyAddrs) - if sync { - <-done - } - - return err -} - // 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) - - var ( - replaced bool - errs []error - ) - - for _, tx := range txs { - var err error - - replaced, err = pool.add(tx, local) + 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) } - - if err != nil { - errs = append(errs, err) - } } - validTxMeter.Mark(int64(len(dirty.accounts))) - return errs, dirty } -func (pool *TxPool) addTxLocked(tx *types.Transaction, local bool) (error, *accountSet) { - dirty := newAccountSet(pool.signer) - - var ( - replaced bool - err error - ) - - replaced, err = pool.add(tx, local) - if err == nil && !replaced { - dirty.addTx(tx) - } - - validTxMeter.Mark(int64(len(dirty.accounts))) - - return err, dirty -} - // 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)) - - var ( - txList *list - isPending bool - ) - for i, hash := range hashes { tx := pool.Get(hash) if tx == nil { continue } - from, _ := types.Sender(pool.signer, tx) // already validated - - pool.pendingMu.RLock() - - if txList = pool.pending[from]; txList != nil && txList.txs.Has(tx.Nonce()) { + pool.mu.RLock() + if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { status[i] = TxStatusPending - isPending = true - } else { - isPending = false - } - - pool.pendingMu.RUnlock() - - if !isPending { - pool.mu.RLock() - - if txList := pool.queue[from]; txList != nil && txList.txs.Has(tx.Nonce()) { - status[i] = TxStatusQueued - } - - pool.mu.RUnlock() + } 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() } - return status } @@ -1452,67 +1110,46 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) int { 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 - pool.pendingMu.Lock() - if pending := pool.pending[addr]; pending != nil { if removed, invalids := pending.Remove(tx); removed { - pool.pendingCount-- - // If no more pending transactions are left, remove the list if pending.Empty() { delete(pool.pending, addr) } - - pool.pendingMu.Unlock() - // 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) } - - pool.pendingMu.TryLock() } - - pool.pendingMu.Unlock() - // 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 } @@ -1560,14 +1197,11 @@ func (pool *TxPool) scheduleReorgLoop() { dirtyAccounts *accountSet queuedEvents = make(map[common.Address]*sortedMap) ) - for { // Launch next background reorg if needed if curDone == nil && launchNextRun { - ctx := context.Background() - // Run the background reorg and announcements - go pool.runReorg(ctx, nextDone, reset, dirtyAccounts, queuedEvents) + go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents) // Prepare everything for the next round of reorg curDone, nextDone = nextDone, make(chan struct{}) @@ -1585,7 +1219,6 @@ func (pool *TxPool) scheduleReorgLoop() { } else { reset.newHead = req.newHead } - launchNextRun = true pool.reorgDoneCh <- nextDone @@ -1596,7 +1229,6 @@ func (pool *TxPool) scheduleReorgLoop() { } else { dirtyAccounts.merge(req) } - launchNextRun = true pool.reorgDoneCh <- nextDone @@ -1607,7 +1239,6 @@ func (pool *TxPool) scheduleReorgLoop() { if _, ok := queuedEvents[addr]; !ok { queuedEvents[addr] = newSortedMap() } - queuedEvents[addr].Put(tx) case <-curDone: @@ -1618,185 +1249,87 @@ func (pool *TxPool) scheduleReorgLoop() { if curDone != nil { <-curDone } - close(nextDone) - return } } } // runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. -// -//nolint:gocognit -func (pool *TxPool) runReorg(ctx context.Context, done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) { - tracing.Exec(ctx, "TxPoolReorg", "txpool-reorg", func(ctx context.Context, span trace.Span) { - defer func(t0 time.Time) { - reorgDurationTimer.Update(time.Since(t0)) - }(time.Now()) - - defer close(done) - - var promoteAddrs []common.Address - - tracing.ElapsedTime(ctx, span, "01 dirty accounts flattening", func(_ context.Context, innerSpan trace.Span) { - 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() +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) } - - tracing.SetAttributes( - innerSpan, - attribute.Int("promoteAddresses-flatten", len(promoteAddrs)), - ) - }) - - tracing.ElapsedTime(ctx, span, "02 obtaining pool.WMutex", func(_ context.Context, _ trace.Span) { - pool.mu.Lock() - }) - - if reset != nil { - tracing.ElapsedTime(ctx, span, "03 reset-head reorg", func(_ context.Context, innerSpan trace.Span) { - // Reset from the old head to the new, rescheduling any reorged transactions - tracing.ElapsedTime(ctx, innerSpan, "04 reset-head-itself reorg", func(_ context.Context, innerSpan trace.Span) { - pool.reset(reset.oldHead, reset.newHead) - }) - - tracing.SetAttributes( - innerSpan, - attribute.Int("events-reset-head", len(events)), - ) - - // 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) - } - - tracing.SetAttributes( - innerSpan, - attribute.Int("promoteAddresses-reset-head", len(promoteAddrs)), - ) - }) } - - // Check for pending transactions for every account that sent new ones - var promoted []*types.Transaction - - tracing.ElapsedTime(ctx, span, "05 promoteExecutables", func(_ context.Context, _ trace.Span) { - promoted = pool.promoteExecutables(promoteAddrs) - }) - - tracing.SetAttributes( - span, - attribute.Int("count.promoteAddresses-reset-head", len(promoteAddrs)), - attribute.Int("count.all", pool.all.Count()), - attribute.Int("count.pending", len(pool.pending)), - attribute.Int("count.queue", len(pool.queue)), - ) - - // 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). - - //nolint:nestif - if reset != nil { - tracing.ElapsedTime(ctx, span, "new block", func(_ context.Context, innerSpan trace.Span) { - tracing.ElapsedTime(ctx, innerSpan, "06 demoteUnexecutables", func(_ context.Context, _ trace.Span) { - pool.demoteUnexecutables() - }) - - var nonces map[common.Address]uint64 - - tracing.ElapsedTime(ctx, innerSpan, "07 set_base_fee", func(_ context.Context, _ trace.Span) { - if reset.newHead != nil { - if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { - // london fork enabled, reset given the base fee - pendingBaseFee := misc.CalcBaseFeeUint(pool.chainconfig, reset.newHead) - pool.priced.SetBaseFee(pendingBaseFee) - } else { - // london fork not enabled, reheap to "reset" the priced list - pool.priced.Reheap() - } - } - - // Update all accounts to the latest known pending nonce - nonces = make(map[common.Address]uint64, len(pool.pending)) - }) - - tracing.ElapsedTime(ctx, innerSpan, "08 obtaining pendingMu.RMutex", func(_ context.Context, _ trace.Span) { - pool.pendingMu.RLock() - }) - - var highestPending *types.Transaction - - tracing.ElapsedTime(ctx, innerSpan, "09 fill nonces", func(_ context.Context, innerSpan trace.Span) { - for addr, list := range pool.pending { - highestPending = list.LastElement() - if highestPending != nil { - nonces[addr] = highestPending.Nonce() + 1 - } - } - }) - - pool.pendingMu.RUnlock() - - tracing.ElapsedTime(ctx, innerSpan, "10 reset nonces", func(_ context.Context, _ trace.Span) { - pool.pendingNonces.setAll(nonces) - }) - }) + // 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) - // Ensure pool.queue and pool.pending sizes stay within the configured limits. - tracing.ElapsedTime(ctx, span, "11 truncatePending", func(_ context.Context, _ trace.Span) { - pool.truncatePending() - }) - - tracing.ElapsedTime(ctx, span, "12 truncateQueue", func(_ context.Context, _ trace.Span) { - pool.truncateQueue() - }) - - dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) - pool.changesSinceReorg = 0 // Reset change counter - - pool.mu.Unlock() - - // Notify subsystems for newly added transactions - tracing.ElapsedTime(ctx, span, "13 notify about new transactions", func(_ context.Context, _ trace.Span) { - 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 { - tracing.ElapsedTime(ctx, span, "14 txFeed", func(_ context.Context, _ trace.Span) { - var txs []*types.Transaction + // 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.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { + pendingBaseFee := misc.CalcBaseFeeUint(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() - for _, set := range events { - txs = append(txs, set.Flatten()...) - } + dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) + pool.changesSinceReorg = 0 // Reset change counter + pool.mu.Unlock() - pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) - }) + // 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 @@ -1815,12 +1348,10 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } 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. @@ -1839,38 +1370,30 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } 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) } } @@ -1879,13 +1402,11 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { 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.currentState = statedb pool.pendingNonces = newNoncer(statedb) pool.currentMaxGas.Store(newHead.GasLimit) @@ -1908,103 +1429,67 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // invalidated transactions (low nonce, low balance) are deleted. func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction { // Track the promoted transactions to broadcast them at once - var ( - promoted []*types.Transaction - promotedLen int - forwards types.Transactions - forwardsLen int - caps types.Transactions - capsLen int - drops types.Transactions - dropsLen int - list *list - hash common.Hash - readies types.Transactions - readiesLen int - ) + var promoted []*types.Transaction balance := uint256.NewInt(0) - pool.currentStateMutex.Lock() - defer pool.currentStateMutex.Unlock() - // Iterate over all accounts and promote any executable transactions for _, addr := range accounts { - list = pool.queue[addr] + 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)) - forwardsLen = len(forwards) - + forwards := list.Forward(pool.currentState.GetNonce(addr)) for _, tx := range forwards { - hash = tx.Hash() + hash := tx.Hash() pool.all.Remove(hash) } - - log.Trace("Removed old queued transactions", "count", forwardsLen) - + log.Trace("Removed old queued transactions", "count", len(forwards)) // Drop all transactions that are too costly (low balance or out of gas) balance.SetFromBig(pool.currentState.GetBalance(addr)) - - drops, _ = list.Filter(balance, pool.currentMaxGas.Load()) - dropsLen = len(drops) - + drops, _ := list.Filter(balance, pool.currentMaxGas.Load()) for _, tx := range drops { - hash = tx.Hash() + hash := tx.Hash() pool.all.Remove(hash) } - - log.Trace("Removed unpayable queued transactions", "count", dropsLen) - queuedNofundsMeter.Mark(int64(dropsLen)) + 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)) - readiesLen = len(readies) - + readies := list.Ready(pool.pendingNonces.get(addr)) for _, tx := range readies { - hash = tx.Hash() + hash := tx.Hash() if pool.promoteTx(addr, hash, tx) { promoted = append(promoted, tx) } } - - log.Trace("Promoted queued transactions", "count", promotedLen) - queuedGauge.Dec(int64(readiesLen)) + 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)) - capsLen = len(caps) - for _, tx := range caps { - hash = tx.Hash() + hash := tx.Hash() pool.all.Remove(hash) - log.Trace("Removed cap-exceeding queued transaction", "hash", hash) } - - queuedRateLimitMeter.Mark(int64(capsLen)) + queuedRateLimitMeter.Mark(int64(len(caps))) } - // Mark all the items dropped as removed - pool.priced.Removed(forwardsLen + dropsLen + capsLen) - - queuedGauge.Dec(int64(forwardsLen + dropsLen + capsLen)) - + 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(forwardsLen + dropsLen + capsLen)) + 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 } @@ -2012,160 +1497,86 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans // 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(pool.pendingCount) + pending := uint64(0) + for _, list := range pool.pending { + pending += uint64(list.Len()) + } if pending <= pool.config.GlobalSlots { return } pendingBeforeCap := pending - - var listLen int - - type pair struct { - address common.Address - value int64 - } - // Assemble a spam order to penalize large transactors first - spammers := make([]pair, 0, 8) - count := 0 - - var ok bool - - pool.pendingMu.RLock() + spammers := prque.New[int64, common.Address](nil) for addr, list := range pool.pending { // Only evict transactions from high rollers - listLen = len(list.txs.items) - - pool.pendingMu.RUnlock() - - pool.locals.m.RLock() - - if uint64(listLen) > pool.config.AccountSlots { - if _, ok = pool.locals.accounts[addr]; ok { - pool.locals.m.RUnlock() - - pool.pendingMu.RLock() - - continue - } - - count++ - - spammers = append(spammers, pair{addr, int64(listLen)}) + if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots { + spammers.Push(addr, int64(list.Len())) } - - pool.locals.m.RUnlock() - - pool.pendingMu.RLock() } - - pool.pendingMu.RUnlock() - // Gradually drop transactions from offenders - offenders := make([]common.Address, 0, len(spammers)) - sort.Slice(spammers, func(i, j int) bool { - return spammers[i].value < spammers[j].value - }) - - var ( - offender common.Address - caps types.Transactions - capsLen int - list *list - hash common.Hash - ) - - // todo: metrics: spammers, offenders, total loops - for len(spammers) != 0 && pending > pool.config.GlobalSlots { + offenders := []common.Address{} + for pending > pool.config.GlobalSlots && !spammers.Empty() { // Retrieve the next offender if not local address - offender, spammers = spammers[len(spammers)-1].address, spammers[:len(spammers)-1] + 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 - pool.pendingMu.RLock() 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(len(list.txs.items) - 1) - capsLen = len(caps) - - pool.pendingMu.RUnlock() + 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() + 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(capsLen) - - pendingGauge.Dec(int64(capsLen)) - + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) if pool.locals.contains(offenders[i]) { - localGauge.Dec(int64(capsLen)) + localGauge.Dec(int64(len(caps))) } - pending-- - - pool.pendingMu.RLock() } } - - pool.pendingMu.RUnlock() } } // If still above threshold, reduce to limit or min allowance if pending > pool.config.GlobalSlots && len(offenders) > 0 { - pool.pendingMu.RLock() - 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(len(list.txs.items) - 1) - capsLen = len(caps) - - pool.pendingMu.RUnlock() + 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() + 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(capsLen) - - pendingGauge.Dec(int64(capsLen)) - - if _, ok = pool.locals.accounts[addr]; ok { - localGauge.Dec(int64(capsLen)) + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(caps))) } - pending-- - - pool.pendingMu.RLock() } } - - pool.pendingMu.RUnlock() } - pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) } @@ -2175,68 +1586,40 @@ func (pool *TxPool) truncateQueue() { 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)) - var ( - tx *types.Transaction - txs types.Transactions - list *list - addr addressByHeartbeat - size uint64 - ) - // 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] + addr := addresses[len(addresses)-1] + list := pool.queue[addr.address] addresses = addresses[:len(addresses)-1] - var ( - listFlatten types.Transactions - isSet bool - ) - // Drop all transactions if they are less than the overflow - if size = uint64(list.Len()); size <= drop { - listFlatten = list.Flatten() - isSet = true - - for _, tx = range listFlatten { + 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 - if !isSet { - listFlatten = list.Flatten() - } - - txs = listFlatten + txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { pool.removeTx(txs[i].Hash(), true) - drop-- - queuedRateLimitMeter.Mark(1) } } @@ -2252,104 +1635,55 @@ func (pool *TxPool) truncateQueue() { func (pool *TxPool) demoteUnexecutables() { balance := uint256.NewInt(0) - var ( - olds types.Transactions - oldsLen int - hash common.Hash - dropsLen int - invalidsLen int - gapped types.Transactions - gappedLen int - ) - // Iterate over all accounts and demote any non-executable transactions - pool.pendingMu.RLock() - - pool.currentStateMutex.Lock() - defer pool.currentStateMutex.Unlock() - 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) - oldsLen = len(olds) - + olds := list.Forward(nonce) for _, tx := range olds { - hash = tx.Hash() + 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 balance.SetFromBig(pool.currentState.GetBalance(addr)) drops, invalids := list.Filter(balance, pool.currentMaxGas.Load()) - dropsLen = len(drops) - invalidsLen = len(invalids) - for _, tx := range drops { - hash = tx.Hash() - + hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) - pool.all.Remove(hash) } - - pendingNofundsMeter.Mark(int64(dropsLen)) + pendingNofundsMeter.Mark(int64(len(drops))) for _, tx := range invalids { - hash = tx.Hash() - + hash := tx.Hash() log.Trace("Demoting pending transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. pool.enqueueTx(hash, tx, false, false) } - - // Drop all transactions that no longer have valid TxOptions - txConditionalsRemoved := list.FilterTxConditional(pool.currentState) - - for _, tx := range txConditionalsRemoved { - hash := tx.Hash() - pool.all.Remove(hash) - log.Trace("Removed invalid conditional transaction", "hash", hash) - } - - pendingGauge.Dec(int64(oldsLen + dropsLen + invalidsLen + len(txConditionalsRemoved))) - + pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { - localGauge.Dec(int64(oldsLen + dropsLen + invalidsLen + len(txConditionalsRemoved))) + 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) - gappedLen = len(gapped) - + gapped := list.Cap(0) for _, tx := range gapped { - hash = tx.Hash() + hash := tx.Hash() log.Error("Demoting invalidated transaction", "hash", hash) // Internal shuffle shouldn't touch the lookup set. pool.enqueueTx(hash, tx, false, false) } - - pendingGauge.Dec(int64(gappedLen)) + pendingGauge.Dec(int64(len(gapped))) } - // Delete the entire pending entry if it became empty. if list.Empty() { - pool.pendingMu.RUnlock() - pool.pendingMu.Lock() - - pool.pendingCount -= pool.pending[addr].Len() delete(pool.pending, addr) - - pool.pendingMu.Unlock() - pool.pendingMu.RLock() } } - - pool.pendingMu.RUnlock() } // addressByHeartbeat is an account address tagged with its last activity timestamp. @@ -2367,10 +1701,9 @@ 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{} - accountsFlatted []common.Address - signer types.Signer - m sync.RWMutex + accounts map[common.Address]struct{} + signer types.Signer + cache *[]common.Address } // newAccountSet creates a new address set with an associated signer for sender @@ -2383,50 +1716,28 @@ func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { 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 { - as.m.RLock() - defer as.m.RUnlock() - _, exist := as.accounts[addr] - return exist } -func (as *accountSet) empty() bool { - as.m.RLock() - defer as.m.RUnlock() - - 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 { - as.m.RLock() - defer as.m.RUnlock() - 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.m.Lock() - defer as.m.Unlock() - - if _, ok := as.accounts[addr]; !ok { - as.accountsFlatted = append(as.accountsFlatted, addr) - } - as.accounts[addr] = struct{}{} + as.cache = nil } // addTx adds the sender of tx into the set. @@ -2439,26 +1750,22 @@ func (as *accountSet) addTx(tx *types.Transaction) { // 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 { - as.m.RLock() - defer as.m.RUnlock() - - return as.accountsFlatted + 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) { - var ok bool - - as.m.Lock() - defer as.m.Unlock() - for addr := range other.accounts { - if _, ok = as.accounts[addr]; !ok { - as.accountsFlatted = append(as.accountsFlatted, addr) - } - as.accounts[addr] = struct{}{} } + as.cache = nil } // lookup is used internally by TxPool to track transactions while allowing @@ -2502,7 +1809,6 @@ func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local boo } } } - if remote { for key, value := range t.remotes { if !f(key, value, false) { @@ -2520,7 +1826,6 @@ func (t *lookup) Get(hash common.Hash) *types.Transaction { if tx := t.locals[hash]; tx != nil { return tx } - return t.remotes[hash] } @@ -2596,12 +1901,10 @@ func (t *lookup) Remove(hash common.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)) @@ -2616,34 +1919,25 @@ func (t *lookup) RemoteToLocals(locals *accountSet) int { defer t.lock.Unlock() var migrated int - for hash, tx := range t.remotes { if locals.containsTx(tx) { - locals.m.Lock() t.locals[hash] = tx - locals.m.Unlock() - 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 } diff --git a/core/txpool/txpool_test.go b/core/txpool/txpool_test.go index c8c201b193..5efc410058 100644 --- a/core/txpool/txpool_test.go +++ b/core/txpool/txpool_test.go @@ -17,31 +17,18 @@ package txpool import ( - "context" "crypto/ecdsa" + crand "crypto/rand" "errors" "fmt" - "io" "math/big" "math/rand" "os" - "runtime" - "strings" - "sync" "sync/atomic" "testing" "time" - "github.com/holiman/uint256" - "github.com/maticnetwork/crand" - "go.uber.org/goleak" - "gonum.org/v1/gonum/floats" - "gonum.org/v1/gonum/stat" - "pgregory.net/rapid" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/debug" - "github.com/ethereum/go-ethereum/common/leak" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -50,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" ) var ( @@ -61,10 +49,6 @@ var ( eip1559Config *params.ChainConfig ) -const ( - txPoolGasLimit = 10_000_000 -) - func init() { testTxPoolConfig = DefaultConfig testTxPoolConfig.Journal = "" @@ -81,10 +65,9 @@ type testBlockChain struct { chainHeadFeed *event.Feed } -func newTestBlockChain(gasLimit uint64, statedb *state.StateDB, _ *event.Feed) *testBlockChain { +func newTestBlockChain(gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain { bc := testBlockChain{statedb: statedb, chainHeadFeed: new(event.Feed)} bc.gasLimit.Store(gasLimit) - return &bc } @@ -112,16 +95,15 @@ func transaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Tr } func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { - tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x01}, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) return tx } func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, bytes uint64) *types.Transaction { data := make([]byte, bytes) - rand.Read(data) + crand.Read(data) tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) - return tx } @@ -137,25 +119,22 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int, Data: nil, AccessList: nil, }) - return tx } func setupPool() (*TxPool, *ecdsa.PrivateKey) { - return setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, txPoolGasLimit) + return setupPoolWithConfig(params.TestChainConfig) } -func setupPoolWithConfig(config *params.ChainConfig, txPoolConfig Config, _ uint64, options ...func(pool *TxPool)) (*TxPool, *ecdsa.PrivateKey) { +func setupPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := newTestBlockChain(10000000, statedb, new(event.Feed)) key, _ := crypto.GenerateKey() - - pool := NewTxPool(txPoolConfig, config, blockchain, options...) + pool := NewTxPool(testTxPoolConfig, config, blockchain) // wait for the pool to initialize <-pool.initDoneCh - return pool, key } @@ -169,18 +148,12 @@ func validatePoolInternals(pool *TxPool) error { if total := pool.all.Count(); total != pending+queued { return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) } - pool.priced.Reheap() - priced, remote := pool.priced.urgent.Len()+pool.priced.floating.Len(), pool.all.RemoteCount() if priced != remote { return fmt.Errorf("total priced transaction count %d != %d", priced, remote) } - // Ensure the next nonce to assign is the correct one - pool.pendingMu.RLock() - defer pool.pendingMu.RUnlock() - for addr, txs := range pool.pending { // Find the last transaction var last uint64 @@ -189,16 +162,13 @@ func validatePoolInternals(pool *TxPool) error { last = nonce } } - if nonce := pool.pendingNonces.get(addr); nonce != last+1 { return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1) } - if txs.totalcost.Cmp(common.Big0) < 0 { return fmt.Errorf("totalcost went negative: %v", txs.totalcost) } } - return nil } @@ -215,7 +185,6 @@ func validateEvents(events chan core.NewTxsEvent, count int) error { return fmt.Errorf("event #%d not fired", len(received)) } } - if len(received) > count { return fmt.Errorf("more than %d events fired: %v", count, received[count:]) } @@ -228,7 +197,6 @@ func validateEvents(events chan core.NewTxsEvent, count int) error { // reading the event channel and pushing into it, so better wait a bit ensuring // really nothing gets injected. } - return nil } @@ -250,7 +218,6 @@ func (c *testChain) State() (*state.StateDB, error) { // state multiple times and by delaying it a bit we simulate // a state change between those fetches. stdb := c.statedb - if *c.trigger { c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) // simulate that the new head block included tx0 and tx1 @@ -258,7 +225,6 @@ func (c *testChain) State() (*state.StateDB, error) { c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) *c.trigger = false } - return stdb, nil } @@ -299,7 +265,6 @@ func TestStateChangeDuringReset(t *testing.T) { // trigger state change in the background trigger = true - <-pool.requestReset(nil, nil) nonce = pool.Nonce(address) @@ -320,16 +285,6 @@ func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) { pool.mu.Unlock() } -func getBalance(pool *TxPool, addr common.Address) *big.Int { - bal := big.NewInt(0) - - pool.mu.Lock() - bal.Set(pool.currentState.GetBalance(addr)) - pool.mu.Unlock() - - return bal -} - func TestInvalidTransactions(t *testing.T) { t.Parallel() @@ -341,7 +296,6 @@ func TestInvalidTransactions(t *testing.T) { // Intrinsic gas too low testAddBalance(pool, from, big.NewInt(1)) - if err, want := pool.AddRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } @@ -354,25 +308,18 @@ func TestInvalidTransactions(t *testing.T) { 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) { t.Errorf("want %v have %v", want, err) } tx = transaction(1, 100000, key) - - pool.gasPriceMu.Lock() - pool.gasPrice = big.NewInt(1000) pool.gasPriceUint = uint256.NewInt(1000) - pool.gasPriceMu.Unlock() - if err, want := pool.AddRemote(tx), ErrUnderpriced; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } - if err := pool.AddLocal(tx); err != nil { t.Error("expected", nil, "got", err) } @@ -391,12 +338,9 @@ func TestQueue(t *testing.T) { pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) - - pool.pendingMu.RLock() if len(pool.pending) != 1 { t.Error("expected valid txs to be 1 is", len(pool.pending)) } - pool.pendingMu.RUnlock() tx = transaction(1, 100, key) from, _ = deriveSender(tx) @@ -404,13 +348,9 @@ func TestQueue(t *testing.T) { pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) - - pool.pendingMu.RLock() if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { t.Error("expected transaction to be in tx pool") } - pool.pendingMu.RUnlock() - if len(pool.queue) > 0 { t.Error("expected transaction queue to be empty. is", len(pool.queue)) } @@ -434,13 +374,9 @@ func TestQueue2(t *testing.T) { pool.enqueueTx(tx3.Hash(), tx3, false, true) pool.promoteExecutables([]common.Address{from}) - - pool.pendingMu.RLock() if len(pool.pending) != 1 { t.Error("expected pending length to be 1, got", len(pool.pending)) } - pool.pendingMu.RUnlock() - if pool.queue[from].Len() != 2 { t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) } @@ -454,10 +390,8 @@ func TestNegativeValue(t *testing.T) { tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) from, _ := deriveSender(tx) - testAddBalance(pool, from, big.NewInt(1)) - - if err := pool.AddRemote(tx); !errors.Is(err, ErrNegativeValue) { + if err := pool.AddRemote(tx); err != ErrNegativeValue { t.Error("expected", ErrNegativeValue, "got", err) } } @@ -465,7 +399,7 @@ func TestNegativeValue(t *testing.T) { func TestTipAboveFeeCap(t *testing.T) { t.Parallel() - pool, key := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, key := setupPoolWithConfig(eip1559Config) defer pool.Stop() tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) @@ -478,7 +412,7 @@ func TestTipAboveFeeCap(t *testing.T) { func TestVeryHighValues(t *testing.T) { t.Parallel() - pool, key := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, key := setupPoolWithConfig(eip1559Config) defer pool.Stop() veryBigNumber := big.NewInt(1) @@ -515,12 +449,10 @@ func TestChainFork(t *testing.T) { if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } - pool.removeTx(tx.Hash(), true) // reset the pool's internal state resetState() - if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } @@ -551,38 +483,26 @@ func TestDoubleNonce(t *testing.T) { if replace, err := pool.add(tx1, false); err != nil || replace { t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace) } - if replace, err := pool.add(tx2, false); err != nil || !replace { t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace) } - <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) - - pool.pendingMu.RLock() if pool.pending[addr].Len() != 1 { t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) } - if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) } - pool.pendingMu.RUnlock() // Add the third transaction and ensure it's not saved (smaller price) pool.add(tx3, false) - <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) - - pool.pendingMu.RLock() if pool.pending[addr].Len() != 1 { t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) } - if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) } - pool.pendingMu.RUnlock() - // Ensure the total transaction count is correct if pool.all.Count() != 1 { t.Error("expected 1 total transactions, got", pool.all.Count()) @@ -597,22 +517,16 @@ func TestMissingNonce(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, addr, big.NewInt(100000000000000)) - tx := transaction(1, 100000, key) if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } - - pool.pendingMu.RLock() if len(pool.pending) != 0 { t.Error("expected 0 pending transactions, got", len(pool.pending)) } - pool.pendingMu.RUnlock() - if pool.queue[addr].Len() != 1 { t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) } - if pool.all.Count() != 1 { t.Error("expected 1 total transactions, got", pool.all.Count()) } @@ -622,7 +536,6 @@ func TestNonceRecovery(t *testing.T) { t.Parallel() const n = 10 - pool, key := setupPool() defer pool.Stop() @@ -638,7 +551,6 @@ func TestNonceRecovery(t *testing.T) { // simulate some weird re-order of transactions and missing nonce(s) testSetNonce(pool, addr, n-1) <-pool.requestReset(nil, nil) - if fn := pool.Nonce(addr); fn != n-1 { t.Errorf("expected nonce to be %d, got %d", n-1, fn) } @@ -665,7 +577,6 @@ func TestDropping(t *testing.T) { tx11 = transaction(11, 200, key) tx12 = transaction(12, 300, key) ) - pool.all.Add(tx0, false) pool.priced.Put(tx0, false) pool.promoteTx(account, tx0.Hash(), tx0) @@ -683,32 +594,22 @@ func TestDropping(t *testing.T) { pool.enqueueTx(tx12.Hash(), tx12, false, true) // Check that pre and post validations leave the pool as is - pool.pendingMu.RLock() if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - pool.pendingMu.RUnlock() - if pool.queue[account].Len() != 3 { t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) } - if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) } - <-pool.requestReset(nil, nil) - - pool.pendingMu.RLock() if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - pool.pendingMu.RUnlock() - if pool.queue[account].Len() != 3 { t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) } - if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) } @@ -716,32 +617,24 @@ func TestDropping(t *testing.T) { testAddBalance(pool, account, big.NewInt(-650)) <-pool.requestReset(nil, nil) - pool.pendingMu.RLock() if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } - if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } - if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { t.Errorf("out-of-fund pending transaction present: %v", tx1) } - pool.pendingMu.RUnlock() - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { t.Errorf("out-of-fund queued transaction present: %v", tx11) } - if pool.all.Count() != 4 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) } @@ -749,24 +642,18 @@ func TestDropping(t *testing.T) { pool.chain.(*testBlockChain).gasLimit.Store(100) <-pool.requestReset(nil, nil) - pool.pendingMu.RLock() if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } - if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { t.Errorf("over-gased pending transaction present: %v", tx1) } - pool.pendingMu.RUnlock() - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { t.Errorf("over-gased queued transaction present: %v", tx11) } - if pool.all.Count() != 2 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2) } @@ -775,7 +662,6 @@ func TestDropping(t *testing.T) { // Tests that if a transaction is dropped from the current pending pool (e.g. out // of fund), all consecutive (still valid, but not executable) transactions are // postponed back into the future queue to prevent broadcasting them. -// nolint:gocognit func TestPostponing(t *testing.T) { t.Parallel() @@ -798,7 +684,6 @@ func TestPostponing(t *testing.T) { } // Add a batch consecutive pending transactions for validation txs := []*types.Transaction{} - for i, key := range keys { for j := 0; j < 100; j++ { var tx *types.Transaction @@ -807,43 +692,31 @@ func TestPostponing(t *testing.T) { } else { tx = transaction(uint64(j), 50000, key) } - txs = append(txs, tx) } } - for i, err := range pool.AddRemotesSync(txs) { if err != nil { t.Fatalf("tx %d: failed to add transactions: %v", i, err) } } // Check that pre and post validations leave the pool as is - pool.pendingMu.RLock() if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - pool.pendingMu.RUnlock() - if len(pool.queue) != 0 { t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } - if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) } - <-pool.requestReset(nil, nil) - - pool.pendingMu.RLock() if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - pool.pendingMu.RUnlock() - if len(pool.queue) != 0 { t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } - if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) } @@ -851,28 +724,21 @@ func TestPostponing(t *testing.T) { for _, addr := range accs { testAddBalance(pool, addr, big.NewInt(-1)) } - <-pool.requestReset(nil, nil) // The first account's first transaction remains valid, check that subsequent // ones are either filtered out, or queued up for later. - pool.pendingMu.RLock() if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) } - pool.pendingMu.RUnlock() - if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) } - - pool.pendingMu.RLock() for i, tx := range txs[1:100] { if i%2 == 1 { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) } @@ -880,22 +746,16 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) } } } - pool.pendingMu.RUnlock() - // The second account's first transaction got invalid, check that all transactions // are either filtered out, or queued up for later. - pool.pendingMu.RLock() if pool.pending[accs[1]] != nil { t.Errorf("invalidated account still has pending transactions") } - pool.pendingMu.RUnlock() - for i, tx := range txs[100:] { if i%2 == 1 { if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { @@ -907,7 +767,6 @@ func TestPostponing(t *testing.T) { } } } - if pool.all.Count() != len(txs)/2 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2) } @@ -928,7 +787,6 @@ func TestGapFilling(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -937,20 +795,16 @@ func TestGapFilling(t *testing.T) { transaction(0, 100000, key), transaction(2, 100000, key), }) - pending, queued := pool.Stats() if pending != 1 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) } - if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 1); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -958,20 +812,16 @@ func TestGapFilling(t *testing.T) { if err := pool.addRemoteSync(transaction(1, 100000, key)); err != nil { t.Fatalf("failed to add gapped transaction: %v", err) } - pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("gap-filling event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -994,13 +844,9 @@ func TestQueueAccountLimiting(t *testing.T) { if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil { t.Fatalf("tx %d: failed to add transaction: %v", i, err) } - - pool.pendingMu.RLock() if len(pool.pending) != 0 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) } - pool.pendingMu.RUnlock() - if i <= testTxPoolConfig.AccountQueue { if pool.queue[account].Len() != int(i) { t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) @@ -1011,75 +857,25 @@ func TestQueueAccountLimiting(t *testing.T) { } } } - if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) } } -// Test that txpool rejects unprotected txs by default -// FIXME: The below test causes some tests to fail randomly (probably due to parallel execution) -// -//nolint:paralleltest -func TestRejectUnprotectedTransaction(t *testing.T) { - //nolint:paralleltest - t.Skip() - - pool, key := setupPool() - defer pool.Stop() - - tx := dynamicFeeTx(0, 22000, big.NewInt(5), big.NewInt(2), key) - from := crypto.PubkeyToAddress(key.PublicKey) - - pool.chainconfig.ChainID = big.NewInt(5) - pool.signer = types.LatestSignerForChainID(pool.chainconfig.ChainID) - testAddBalance(pool, from, big.NewInt(0xffffffffffffff)) - - if err := pool.AddRemote(tx); !errors.Is(err, types.ErrInvalidChainId) { - t.Error("expected", types.ErrInvalidChainId, "got", err) - } -} - -// Test that txpool allows unprotected txs when AllowUnprotectedTxs flag is set -// FIXME: The below test causes some tests to fail randomly (probably due to parallel execution) -// -//nolint:paralleltest -func TestAllowUnprotectedTransactionWhenSet(t *testing.T) { - t.Skip() - - pool, key := setupPool() - defer pool.Stop() - - tx := dynamicFeeTx(0, 22000, big.NewInt(5), big.NewInt(2), key) - from := crypto.PubkeyToAddress(key.PublicKey) - - // Allow unprotected txs - pool.config.AllowUnprotectedTxs = true - pool.chainconfig.ChainID = big.NewInt(5) - pool.signer = types.LatestSignerForChainID(pool.chainconfig.ChainID) - testAddBalance(pool, from, big.NewInt(0xffffffffffffff)) - - if err := pool.AddRemote(tx); err != nil { - t.Error("expected", nil, "got", err) - } -} - // Tests that if the transaction count belonging to multiple accounts go above // some threshold, the higher transactions are dropped to prevent DOS attacks. // // This logic should not hold for local transactions, unless the local tracking // mechanism is disabled. func TestQueueGlobalLimiting(t *testing.T) { - t.Parallel() testQueueGlobalLimiting(t, false) } func TestQueueGlobalLimitingNoLocals(t *testing.T) { - t.Parallel() testQueueGlobalLimiting(t, true) } func testQueueGlobalLimiting(t *testing.T, nolocals bool) { - t.Helper() + t.Parallel() // Create the pool to test the limit enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -1098,7 +894,6 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { keys[i], _ = crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } - local := keys[len(keys)-1] // Generate and queue a batch of transactions @@ -1116,15 +911,12 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { pool.AddRemotesSync(txs) queued := 0 - for addr, list := range pool.queue { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } - queued += list.Len() } - if queued > int(config.GlobalQueue) { t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) } @@ -1133,21 +925,17 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { for i := uint64(0); i < 3*config.GlobalQueue; i++ { txs = append(txs, transaction(i+1, 100000, local)) } - pool.AddLocals(txs) // If locals are disabled, the previous eviction algorithm should apply here too if nolocals { queued := 0 - for addr, list := range pool.queue { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } - queued += list.Len() } - if queued > int(config.GlobalQueue) { t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) } @@ -1169,18 +957,13 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { // // This logic should not hold for local transactions, unless the local tracking // mechanism is disabled. - -// nolint : paralleltest func TestQueueTimeLimiting(t *testing.T) { testQueueTimeLimiting(t, false) } - -// nolint : paralleltest func TestQueueTimeLimitingNoLocals(t *testing.T) { testQueueTimeLimiting(t, true) } -// nolint:gocognit,thelper func testQueueTimeLimiting(t *testing.T, nolocals bool) { // Reduce the eviction interval to a testable amount defer func(old time.Duration) { evictionInterval = old }(evictionInterval) @@ -1208,20 +991,16 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - pending, queued := pool.Stats() if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1234,11 +1013,9 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1250,7 +1027,6 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } - if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -1260,7 +1036,6 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1275,11 +1050,9 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1288,22 +1061,18 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - 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(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - time.Sleep(6 * evictionInterval) // All gapped transactions shouldn't be kicked out @@ -1311,23 +1080,19 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } // The whole life time pass after last promotion, kick out stale transactions time.Sleep(2 * config.Lifetime) - pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -1337,7 +1102,6 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1358,7 +1122,6 @@ func TestPendingLimiting(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1367,26 +1130,19 @@ func TestPendingLimiting(t *testing.T) { if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil { t.Fatalf("tx %d: failed to add transaction: %v", i, err) } - - pool.pendingMu.RLock() if pool.pending[account].Len() != int(i)+1 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) } - pool.pendingMu.RUnlock() - if len(pool.queue) != 0 { t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) } } - if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) } - if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { t.Fatalf("event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1418,7 +1174,6 @@ func TestPendingGlobalLimiting(t *testing.T) { nonces := make(map[common.Address]uint64) txs := types.Transactions{} - for _, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) for j := 0; j < int(config.GlobalSlots)/len(keys)*2; j++ { @@ -1430,17 +1185,12 @@ func TestPendingGlobalLimiting(t *testing.T) { pool.AddRemotesSync(txs) pending := 0 - - pool.pendingMu.RLock() for _, list := range pool.pending { pending += list.Len() } - pool.pendingMu.RUnlock() - if pending > int(config.GlobalSlots) { t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1494,11 +1244,9 @@ func TestAllowedTxSize(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1531,7 +1279,6 @@ func TestCapClearsFromAll(t *testing.T) { } // Import the batch and verify that limits have been enforced pool.AddRemotes(txs) - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1563,7 +1310,6 @@ func TestPendingMinimumAllowance(t *testing.T) { nonces := make(map[common.Address]uint64) txs := types.Transactions{} - for _, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) for j := 0; j < int(config.AccountSlots)*2; j++ { @@ -1574,14 +1320,11 @@ func TestPendingMinimumAllowance(t *testing.T) { // Import the batch and verify that limits have been enforced pool.AddRemotesSync(txs) - pool.pendingMu.RLock() for addr, list := range pool.pending { if list.Len() != int(config.AccountSlots) { t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), config.AccountSlots) } } - pool.pendingMu.RUnlock() - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1604,7 +1347,6 @@ func TestRepricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1639,19 +1381,15 @@ func TestRepricing(t *testing.T) { if pending != 7 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) } - if queued != 3 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) } - if err := validateEvents(events, 7); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // Reprice the pool and check that underpriced transactions get dropped pool.SetGasPrice(big.NewInt(2)) @@ -1659,76 +1397,58 @@ func TestRepricing(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 5 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) } - if err := validateEvents(events, 0); err != nil { t.Fatalf("reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { 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(1), keys[0])); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); 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(1), keys[1])); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); err != ErrUnderpriced { t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // However we can add local underpriced transactions tx := pricedTransaction(1, 100000, big.NewInt(1), keys[3]) - if err := pool.AddLocal(tx); err != nil { t.Fatalf("failed to add underpriced local transaction: %v", err) } - if pending, _ = pool.Stats(); pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if err := validateEvents(events, 1); err != nil { t.Fatalf("post-reprice local event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { 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(2), keys[0])); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil { t.Fatalf("failed to add queued transaction: %v", err) } - if err := validateEvents(events, 5); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1743,12 +1463,11 @@ func TestRepricingDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, _ := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1758,7 +1477,6 @@ func TestRepricingDynamicFee(t *testing.T) { keys[i], _ = crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } - // Generate and queue a batch of transactions, both pending and queued txs := types.Transactions{} @@ -1784,19 +1502,15 @@ func TestRepricingDynamicFee(t *testing.T) { if pending != 7 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) } - if queued != 3 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) } - if err := validateEvents(events, 7); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // Reprice the pool and check that underpriced transactions get dropped pool.SetGasPrice(big.NewInt(2)) @@ -1804,87 +1518,64 @@ func TestRepricingDynamicFee(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 5 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) } - if err := validateEvents(events, 0); err != nil { t.Fatalf("reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // Check that we can't add the old transactions back tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0]) - - if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(tx); err != ErrUnderpriced { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - - if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(tx); err != ErrUnderpriced { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2]) - if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(tx); err != ErrUnderpriced { t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // However we can add local underpriced transactions tx = dynamicFeeTx(1, 100000, big.NewInt(1), big.NewInt(1), keys[3]) - if err := pool.AddLocal(tx); err != nil { t.Fatalf("failed to add underpriced local transaction: %v", err) } - if pending, _ = pool.Stats(); pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if err := validateEvents(events, 1); err != nil { t.Fatalf("post-reprice local event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // And we can fill gaps with properly priced transactions tx = pricedTransaction(1, 100000, big.NewInt(2), keys[0]) - if err := pool.AddRemote(tx); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) - if err := pool.AddRemote(tx); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - tx = dynamicFeeTx(2, 100000, big.NewInt(2), big.NewInt(2), keys[2]) - if err := pool.AddRemote(tx); err != nil { t.Fatalf("failed to add queued transaction: %v", err) } - if err := validateEvents(events, 5); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1932,7 +1623,6 @@ func TestRepricingKeepsLocals(t *testing.T) { t.Fatal(err) } } - pending, queued := pool.Stats() expPending, expQueued := 1000, 1000 validate := func() { @@ -1940,7 +1630,6 @@ func TestRepricingKeepsLocals(t *testing.T) { if pending != expPending { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, expPending) } - if queued != expQueued { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) } @@ -1983,7 +1672,6 @@ func TestUnderpricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1993,7 +1681,6 @@ func TestUnderpricing(t *testing.T) { keys[i], _ = crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } - // Generate and queue a batch of transactions, both pending and queued txs := types.Transactions{} @@ -2012,21 +1699,17 @@ func TestUnderpricing(t *testing.T) { if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { 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(1), keys[1])); !errors.Is(err, ErrUnderpriced) { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } // Replace a future transaction with a future transaction @@ -2037,11 +1720,9 @@ func TestUnderpricing(t *testing.T) { if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), 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(4), 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(5), 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) } @@ -2049,48 +1730,38 @@ func TestUnderpricing(t *testing.T) { if err := pool.AddRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending { t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending) } - pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("additional event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // Ensure that adding local transactions can push out even higher priced ones ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } - ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } - pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2115,7 +1786,6 @@ func TestStableUnderpricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2136,15 +1806,12 @@ func TestStableUnderpricing(t *testing.T) { if pending != int(config.GlobalSlots) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, int(config.GlobalSlots)); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2152,20 +1819,16 @@ func TestStableUnderpricing(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { t.Fatalf("failed to add well priced transaction: %v", err) } - pending, queued = pool.Stats() if pending != int(config.GlobalSlots) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, 1); err != nil { t.Fatalf("additional event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2179,7 +1842,7 @@ func TestStableUnderpricing(t *testing.T) { func TestUnderpricingDynamicFee(t *testing.T) { t.Parallel() - pool, _ := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Stop() pool.config.GlobalSlots = 2 @@ -2187,7 +1850,6 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2215,22 +1877,19 @@ func TestUnderpricingDynamicFee(t *testing.T) { if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding an underpriced transaction fails tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 + if err := pool.AddRemote(tx); err != ErrUnderpriced { // Pend K0:0, K0:1, K2:0; Que K1:1 t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } @@ -2244,53 +1903,42 @@ func TestUnderpricingDynamicFee(t *testing.T) { 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(4), big.NewInt(1), keys[1]) if err := pool.AddRemote(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } - pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("additional event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - // Ensure that adding local transactions can push out even higher priced ones ltx = dynamicFeeTx(1, 100000, big.NewInt(0), big.NewInt(0), keys[2]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } - ltx = dynamicFeeTx(0, 100000, big.NewInt(0), big.NewInt(0), keys[3]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } - pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2301,7 +1949,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - pool, _ := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, _ := setupPoolWithConfig(eip1559Config) defer pool.Stop() pool.config.GlobalSlots = 10 @@ -2324,7 +1972,6 @@ func TestDualHeapEviction(t *testing.T) { // Create a test accounts and fund it key, _ := crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000)) - if urgent { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) highTip = tx @@ -2332,10 +1979,8 @@ func TestDualHeapEviction(t *testing.T) { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) highCap = tx } - pool.AddRemotesSync([]*types.Transaction{tx}) } - pending, queued := pool.Stats() if pending+queued != 20 { t.Fatalf("transaction count mismatch: have %d, want %d", pending+queued, 10) @@ -2343,7 +1988,6 @@ func TestDualHeapEviction(t *testing.T) { } add(false) - for baseFee = 0; baseFee <= 1000; baseFee += 100 { pool.priced.SetBaseFee(uint256.NewInt(uint64(baseFee))) add(true) @@ -2374,65 +2018,49 @@ func TestDeduplication(t *testing.T) { // Create a batch of transactions and add a few of them txs := make([]*types.Transaction, 16) - for i := 0; i < len(txs); i++ { txs[i] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key) } - var firsts []*types.Transaction - for i := 0; i < len(txs); i += 2 { firsts = append(firsts, txs[i]) } - errs := pool.AddRemotesSync(firsts) - if len(errs) != len(firsts) { - t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), 0) + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) } - for i, err := range errs { if err != nil { t.Errorf("add %d failed: %v", i, err) } } - pending, queued := pool.Stats() - if pending != 1 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) } - if queued != len(txs)/2-1 { 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) if len(errs) != len(txs) { - t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), 0) + t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs)) } - for i, err := range errs { if i%2 == 0 && err == nil { t.Errorf("add %d succeeded, should have failed as known", i) } - if i%2 == 1 && err != nil { t.Errorf("add %d failed: %v", i, err) } } - pending, queued = pool.Stats() - if pending != len(txs) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2452,7 +2080,6 @@ func TestReplacement(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2467,15 +2094,12 @@ func TestReplacement(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil { t.Fatalf("failed to add original cheap pending transaction: %v", err) } - - if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { t.Fatalf("failed to replace original cheap pending transaction: %v", err) } - if err := validateEvents(events, 2); err != nil { t.Fatalf("cheap replacement event firing failed: %v", err) } @@ -2483,15 +2107,12 @@ 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)); !errors.Is(err, ErrReplaceUnderpriced) { + 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, 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 { t.Fatalf("proper replacement event firing failed: %v", err) } @@ -2500,11 +2121,9 @@ func TestReplacement(t *testing.T) { if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { t.Fatalf("failed to add original cheap queued transaction: %v", err) } - - if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { t.Fatalf("failed to replace original cheap queued transaction: %v", err) } @@ -2512,11 +2131,9 @@ func TestReplacement(t *testing.T) { 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)); !errors.Is(err, ErrReplaceUnderpriced) { + 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, 100000, big.NewInt(threshold), key)); err != nil { t.Fatalf("failed to replace original proper queued transaction: %v", err) } @@ -2524,7 +2141,6 @@ func TestReplacement(t *testing.T) { if err := validateEvents(events, 0); err != nil { t.Fatalf("queued replacement event firing failed: %v", err) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2536,13 +2152,12 @@ func TestReplacementDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, key := setupPoolWithConfig(eip1559Config, testTxPoolConfig, txPoolGasLimit) + pool, key := setupPoolWithConfig(eip1559Config) defer pool.Stop() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) - sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2580,7 +2195,7 @@ func TestReplacementDynamicFee(t *testing.T) { } // 2. Don't bump tip or feecap => discard tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key) - if err := pool.AddRemote(tx); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) } // 3. Bump both more than min => accept @@ -2593,7 +2208,6 @@ func TestReplacementDynamicFee(t *testing.T) { if stage == "queued" { count = 0 } - if err := validateEvents(events, count); err != nil { t.Fatalf("cheap %s replacement event firing failed: %v", stage, err) } @@ -2604,22 +2218,22 @@ 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); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) } // 7. Bump fee cap max allowed so it's still underpriced => discard tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key) - if err := pool.AddRemote(tx); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) } // 8. Bump tip min for acceptance => accept tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key) - if err := pool.AddRemote(tx); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) } // 9. Bump fee cap min for acceptance => accept tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key) - if err := pool.AddRemote(tx); !errors.Is(err, ErrReplaceUnderpriced) { + if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) } // 10. Check events match expected (3 new executable txs during pending, 0 during queue) @@ -2632,7 +2246,6 @@ func TestReplacementDynamicFee(t *testing.T) { if stage == "queued" { count = 0 } - if err := validateEvents(events, count); err != nil { t.Fatalf("replacement %s event firing failed: %v", stage, err) } @@ -2645,24 +2258,17 @@ 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) { - t.Parallel() - testJournaling(t, false) -} -func TestJournalingNoLocals(t *testing.T) { - t.Parallel() - testJournaling(t, true) -} +func TestJournaling(t *testing.T) { testJournaling(t, false) } +func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true) } func testJournaling(t *testing.T, nolocals bool) { - t.Helper() + t.Parallel() // Create a temporary file for the journal file, err := os.CreateTemp("", "") if err != nil { t.Fatalf("failed to create temporary journal: %v", err) } - journal := file.Name() defer os.Remove(journal) @@ -2692,28 +2298,22 @@ func testJournaling(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } - pending, queued := pool.Stats() if pending != 4 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4) } - if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2728,7 +2328,6 @@ func testJournaling(t *testing.T, nolocals bool) { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if nolocals { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) @@ -2738,7 +2337,6 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2756,7 +2354,6 @@ func testJournaling(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } - if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -2766,11 +2363,9 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - pool.Stop() } @@ -2807,11 +2402,9 @@ func TestStatusCheck(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2820,7 +2413,6 @@ func TestStatusCheck(t *testing.T) { for i, tx := range txs { hashes[i] = tx.Hash() } - hashes = append(hashes, common.Hash{}) statuses := pool.Status(hashes) @@ -2871,8 +2463,6 @@ func benchmarkPendingDemotion(b *testing.B, size int) { } // Benchmark the speed of pool validation b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { pool.demoteUnexecutables() } @@ -2898,12 +2488,12 @@ func benchmarkFuturePromotion(b *testing.B, size int) { } // Benchmark the speed of pool validation b.ResetTimer() - for i := 0; i < b.N; i++ { pool.promoteExecutables(nil) } } +// Benchmarks the speed of batched transaction insertion. func BenchmarkBatchInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, false) } func BenchmarkBatchInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, false) } func BenchmarkBatchInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, false) } @@ -2912,10 +2502,7 @@ func BenchmarkBatchLocalInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, func BenchmarkBatchLocalInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, true) } func BenchmarkBatchLocalInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, true) } -// Benchmarks the speed of batched transaction insertion. func benchmarkBatchInsert(b *testing.B, size int, local bool) { - b.Helper() - // Generate a batch of transactions to enqueue into the pool pool, key := setupPool() defer pool.Stop() @@ -2923,154 +2510,22 @@ func benchmarkBatchInsert(b *testing.B, size int, local bool) { account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000000000000000)) - const format = "size %d, is local %t" - - cases := []struct { - name string - size int - isLocal bool - }{ - {size: size, isLocal: local}, - {size: size, isLocal: local}, - {size: size, isLocal: local}, - - {size: size, isLocal: local}, - {size: size, isLocal: local}, - {size: size, isLocal: local}, - } - - for i := range cases { - cases[i].name = fmt.Sprintf(format, cases[i].size, cases[i].isLocal) - } - - // Benchmark importing the transactions into the queue - - for _, testCase := range cases { - singleCase := testCase - - b.Run(singleCase.name, func(b *testing.B) { - batches := make([]types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - batches[i] = make(types.Transactions, singleCase.size) - - for j := 0; j < singleCase.size; j++ { - batches[i][j] = transaction(uint64(singleCase.size*i+j), 100000, key) - } - } - - b.ResetTimer() - b.ReportAllocs() - - for _, batch := range batches { - if testCase.isLocal { - pool.AddLocals(batch) - } else { - pool.AddRemotes(batch) - } - } - }) - } -} - -func BenchmarkPoolMining(b *testing.B) { - const format = "size %d" + batches := make([]types.Transactions, b.N) - cases := []struct { - name string - size int - }{ - {size: 1}, - {size: 5}, - {size: 10}, - {size: 20}, - } - - for i := range cases { - cases[i].name = fmt.Sprintf(format, cases[i].size) + for i := 0; i < b.N; i++ { + batches[i] = make(types.Transactions, size) + for j := 0; j < size; j++ { + batches[i][j] = transaction(uint64(size*i+j), 100000, key) + } } - - const blockGasLimit = 30_000_000 - // Benchmark importing the transactions into the queue - - for _, testCase := range cases { - singleCase := testCase - - b.Run(singleCase.name, func(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pendingAddedCh := make(chan struct{}, 1024) - - pool, localKey := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, txPoolGasLimit, MakeWithPromoteTxCh(pendingAddedCh)) - defer pool.Stop() - - localKeyPub := localKey.PublicKey - account := crypto.PubkeyToAddress(localKeyPub) - - const balanceStr = "1_000_000_000" - - balance, ok := big.NewInt(0).SetString(balanceStr, 0) - if !ok { - b.Fatal("incorrect initial balance", balanceStr) - } - - testAddBalance(pool, account, balance) - - signer := types.NewEIP155Signer(big.NewInt(1)) - baseFee := uint256.NewInt(1) - - const batchesSize = 100 - - batches := make([]types.Transactions, batchesSize) - - for i := 0; i < batchesSize; i++ { - batches[i] = make(types.Transactions, singleCase.size) - - for j := 0; j < singleCase.size; j++ { - batches[i][j] = transaction(uint64(singleCase.size*i+j), 100_000, localKey) - } - - for _, batch := range batches { - pool.AddRemotes(batch) - } - } - - var promoted int - - for range pendingAddedCh { - promoted++ - - if promoted >= batchesSize*singleCase.size/2 { - break - } - } - - var total int - - b.ResetTimer() - b.ReportAllocs() - - pendingDurations := make([]time.Duration, b.N) - - var added int - - for i := 0; i < b.N; i++ { - added, pendingDurations[i], _ = mining(b, pool, signer, baseFee, blockGasLimit, i) - total += added - } - - b.StopTimer() - - pendingDurationsFloat := make([]float64, len(pendingDurations)) - - for i, v := range pendingDurations { - pendingDurationsFloat[i] = float64(v.Nanoseconds()) - } - - mean, stddev := stat.MeanStdDev(pendingDurationsFloat, nil) - b.Logf("[%s] pending mean %v, stdev %v, %v-%v", - common.NowMilliseconds(), time.Duration(mean), time.Duration(stddev), time.Duration(floats.Min(pendingDurationsFloat)), time.Duration(floats.Max(pendingDurationsFloat))) - }) + b.ResetTimer() + for _, batch := range batches { + if local { + pool.AddLocals(batch) + } else { + pool.AddRemotes(batch) + } } } @@ -3086,28 +2541,22 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { for i := 0; i < len(locals); i++ { locals[i] = transaction(uint64(i), 100000, key) } - remotes := make([]*types.Transaction, 1000) for i := 0; i < len(remotes); i++ { remotes[i] = pricedTransaction(uint64(i), 100000, big.NewInt(2), remoteKey) // Higher gasprice } // Benchmark importing the transactions into the queue b.ResetTimer() - for i := 0; i < b.N; i++ { b.StopTimer() - pool, _ := setupPool() testAddBalance(pool, account, big.NewInt(100000000)) - for _, local := range locals { pool.AddLocal(local) } - b.StartTimer() // Assign a high enough balance for testing testAddBalance(pool, remoteAddr, big.NewInt(100000000)) - for i := 0; i < len(remotes); i++ { pool.AddRemotes([]*types.Transaction{remotes[i]}) } @@ -3116,1777 +2565,21 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { } // Benchmarks the speed of batch transaction insertion in case of multiple accounts. -func BenchmarkPoolAccountMultiBatchInsert(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pool, _ := setupPool() - defer pool.Stop() - - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - tx := transaction(uint64(0), 100000, key) - - batches[i] = tx - } - - // Benchmark importing the transactions into the queue - b.ReportAllocs() - b.ResetTimer() - - for _, tx := range batches { - pool.AddRemotesSync([]*types.Transaction{tx}) - } -} - -func BenchmarkPoolAccountMultiBatchInsertRace(b *testing.B) { +func BenchmarkMultiAccountBatchInsert(b *testing.B) { // Generate a batch of transactions to enqueue into the pool pool, _ := setupPool() defer pool.Stop() - - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - tx := transaction(uint64(0), 100000, key) - - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - batches[i] = tx - } - - done := make(chan struct{}) - - go func() { - t := time.NewTicker(time.Microsecond) - defer t.Stop() - - var pending map[common.Address]types.Transactions - - loop: - for { - select { - case <-t.C: - pending = pool.Pending(context.Background(), true) - case <-done: - break loop - } - } - - fmt.Fprint(io.Discard, pending) - }() - - b.ReportAllocs() - b.ResetTimer() - - for _, tx := range batches { - pool.AddRemotesSync([]*types.Transaction{tx}) - } - - close(done) -} - -func BenchmarkPoolAccountMultiBatchInsertNoLockRace(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pendingAddedCh := make(chan struct{}, 1024) - - pool, localKey := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, txPoolGasLimit, MakeWithPromoteTxCh(pendingAddedCh)) - defer pool.Stop() - - _ = localKey - - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - tx := transaction(uint64(0), 100000, key) - - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - batches[i] = tx - } - - done := make(chan struct{}) - - go func() { - t := time.NewTicker(time.Microsecond) - defer t.Stop() - - var pending map[common.Address]types.Transactions - - for range t.C { - pending = pool.Pending(context.Background(), true) - - if len(pending) >= b.N/2 { - close(done) - - return - } - } - }() - b.ReportAllocs() - b.ResetTimer() - - for _, tx := range batches { - pool.AddRemotes([]*types.Transaction{tx}) - } - - <-done -} - -func BenchmarkPoolAccountsBatchInsert(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pool, _ := setupPool() - defer pool.Stop() - batches := make(types.Transactions, b.N) - for i := 0; i < b.N; i++ { key, _ := crypto.GenerateKey() account := crypto.PubkeyToAddress(key.PublicKey) - pool.currentState.AddBalance(account, big.NewInt(1000000)) - tx := transaction(uint64(0), 100000, key) - batches[i] = tx } - // Benchmark importing the transactions into the queue - b.ReportAllocs() b.ResetTimer() - - for _, tx := range batches { - _ = pool.AddRemoteSync(tx) - } -} - -func BenchmarkPoolAccountsBatchInsertRace(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pool, _ := setupPool() - defer pool.Stop() - - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - tx := transaction(uint64(0), 100000, key) - - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - batches[i] = tx - } - - done := make(chan struct{}) - - go func() { - t := time.NewTicker(time.Microsecond) - defer t.Stop() - - var pending map[common.Address]types.Transactions - - loop: - for { - select { - case <-t.C: - pending = pool.Pending(context.Background(), true) - case <-done: - break loop - } - } - - fmt.Fprint(io.Discard, pending) - }() - - b.ReportAllocs() - b.ResetTimer() - - for _, tx := range batches { - _ = pool.AddRemoteSync(tx) - } - - close(done) -} - -func BenchmarkPoolAccountsBatchInsertNoLockRace(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pendingAddedCh := make(chan struct{}, 1024) - - pool, localKey := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, txPoolGasLimit, MakeWithPromoteTxCh(pendingAddedCh)) - defer pool.Stop() - - _ = localKey - - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - tx := transaction(uint64(0), 100000, key) - - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - batches[i] = tx - } - - done := make(chan struct{}) - - go func() { - t := time.NewTicker(time.Microsecond) - defer t.Stop() - - var pending map[common.Address]types.Transactions - - for range t.C { - pending = pool.Pending(context.Background(), true) - - if len(pending) >= b.N/2 { - close(done) - - return - } - } - }() - - b.ReportAllocs() - b.ResetTimer() - - for _, tx := range batches { - _ = pool.AddRemote(tx) - } - - <-done -} - -func TestPoolMultiAccountBatchInsertRace(t *testing.T) { - t.Parallel() - - // Generate a batch of transactions to enqueue into the pool - pool, _ := setupPool() - defer pool.Stop() - - const n = 5000 - - batches := make(types.Transactions, n) - batchesSecond := make(types.Transactions, n) - - for i := 0; i < n; i++ { - batches[i] = newTxs(pool) - batchesSecond[i] = newTxs(pool) - } - - done := make(chan struct{}) - - go func() { - t := time.NewTicker(time.Microsecond) - defer t.Stop() - - var ( - pending map[common.Address]types.Transactions - total int - ) - - for range t.C { - pending = pool.Pending(context.Background(), true) - total = len(pending) - - _ = pool.Locals() - - if total >= n { - close(done) - - return - } - } - }() - - for _, tx := range batches { - pool.AddRemotesSync([]*types.Transaction{tx}) - } - - for _, tx := range batchesSecond { - pool.AddRemotes([]*types.Transaction{tx}) - } - - <-done -} - -func newTxs(pool *TxPool) *types.Transaction { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - tx := transaction(uint64(0), 100000, key) - - pool.currentState.AddBalance(account, big.NewInt(1_000_000_000)) - - return tx -} - -type acc struct { - nonce uint64 - key *ecdsa.PrivateKey - account common.Address -} - -type testTx struct { - tx *types.Transaction - idx int - isLocal bool -} - -const localIdx = 0 - -func getTransactionGen(t *rapid.T, keys []*acc, nonces []uint64, localKey *acc, gasPriceMin, gasPriceMax, gasLimitMin, gasLimitMax uint64) *testTx { - idx := rapid.IntRange(0, len(keys)-1).Draw(t, "accIdx").(int) - - var ( - isLocal bool - key *ecdsa.PrivateKey - ) - - if idx == localIdx { - isLocal = true - key = localKey.key - } else { - key = keys[idx].key - } - - nonces[idx]++ - - gasPriceUint := rapid.Uint64Range(gasPriceMin, gasPriceMax).Draw(t, "gasPrice").(uint64) - gasPrice := big.NewInt(0).SetUint64(gasPriceUint) - gasLimit := rapid.Uint64Range(gasLimitMin, gasLimitMax).Draw(t, "gasLimit").(uint64) - - return &testTx{ - tx: pricedTransaction(nonces[idx]-1, gasLimit, gasPrice, key), - idx: idx, - isLocal: isLocal, - } -} - -type transactionBatches struct { - txs []*testTx - totalTxs int -} - -func transactionsGen(keys []*acc, nonces []uint64, localKey *acc, minTxs int, maxTxs int, gasPriceMin, gasPriceMax, gasLimitMin, gasLimitMax uint64, caseParams *strings.Builder) func(t *rapid.T) *transactionBatches { - return func(t *rapid.T) *transactionBatches { - totalTxs := rapid.IntRange(minTxs, maxTxs).Draw(t, "totalTxs").(int) - txs := make([]*testTx, totalTxs) - - gasValues := make([]float64, totalTxs) - - fmt.Fprintf(caseParams, " totalTxs = %d;", totalTxs) - - keys = keys[:len(nonces)] - - for i := 0; i < totalTxs; i++ { - txs[i] = getTransactionGen(t, keys, nonces, localKey, gasPriceMin, gasPriceMax, gasLimitMin, gasLimitMax) - - gasValues[i] = float64(txs[i].tx.Gas()) - } - - mean, stddev := stat.MeanStdDev(gasValues, nil) - fmt.Fprintf(caseParams, " gasValues mean %d, stdev %d, %d-%d);", int64(mean), int64(stddev), int64(floats.Min(gasValues)), int64(floats.Max(gasValues))) - - return &transactionBatches{txs, totalTxs} - } -} - -type txPoolRapidConfig struct { - gasLimit uint64 - avgBlockTxs uint64 - - minTxs int - maxTxs int - - minAccs int - maxAccs int - - // less tweakable, more like constants - gasPriceMin uint64 - gasPriceMax uint64 - - gasLimitMin uint64 - gasLimitMax uint64 - - balance int64 - - blockTime time.Duration - maxEmptyBlocks int - maxStuckBlocks int -} - -func defaultTxPoolRapidConfig() txPoolRapidConfig { - gasLimit := uint64(30_000_000) - avgBlockTxs := gasLimit/params.TxGas + 1 - maxTxs := int(25 * avgBlockTxs) - - return txPoolRapidConfig{ - gasLimit: gasLimit, - - avgBlockTxs: avgBlockTxs, - - minTxs: 1, - maxTxs: maxTxs, - - minAccs: 1, - maxAccs: maxTxs, - - // less tweakable, more like constants - gasPriceMin: 1, - gasPriceMax: 1_000, - - gasLimitMin: params.TxGas, - gasLimitMax: gasLimit / 2, - - balance: 0xffffffffffffff, - - blockTime: 2 * time.Second, - maxEmptyBlocks: 10, - maxStuckBlocks: 10, - } -} - -// TestSmallTxPool is not something to run in parallel as far it uses all CPUs -// nolint:paralleltest -func TestSmallTxPool(t *testing.T) { - t.Parallel() - - t.Skip("a red test to be fixed") - - cfg := defaultTxPoolRapidConfig() - - cfg.maxEmptyBlocks = 10 - cfg.maxStuckBlocks = 10 - - cfg.minTxs = 1 - cfg.maxTxs = 2 - - cfg.minAccs = 1 - cfg.maxAccs = 2 - - testPoolBatchInsert(t, cfg) -} - -// This test is not something to run in parallel as far it uses all CPUs -// nolint:paralleltest -func TestBigTxPool(t *testing.T) { - t.Parallel() - - t.Skip("a red test to be fixed") - - cfg := defaultTxPoolRapidConfig() - - testPoolBatchInsert(t, cfg) -} - -//nolint:gocognit,thelper -func testPoolBatchInsert(t *testing.T, cfg txPoolRapidConfig) { - t.Helper() - - t.Parallel() - - const debug = false - - initialBalance := big.NewInt(cfg.balance) - - keys := make([]*acc, cfg.maxAccs) - - var key *ecdsa.PrivateKey - - // prealloc keys - for idx := 0; idx < cfg.maxAccs; idx++ { - key, _ = crypto.GenerateKey() - - keys[idx] = &acc{ - key: key, - nonce: 0, - account: crypto.PubkeyToAddress(key.PublicKey), - } - } - - var threads = runtime.NumCPU() - - if debug { - // 1 is set only for debug - threads = 1 - } - - testsDone := new(uint64) - - for i := 0; i < threads; i++ { - t.Run(fmt.Sprintf("thread %d", i), func(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(rt *rapid.T) { - caseParams := new(strings.Builder) - - defer func() { - res := atomic.AddUint64(testsDone, 1) - - if res%100 == 0 { - fmt.Println("case-done", res) - } - }() - - // Generate a batch of transactions to enqueue into the pool - testTxPoolConfig := testTxPoolConfig - - // from sentry config - testTxPoolConfig.AccountQueue = 16 - testTxPoolConfig.AccountSlots = 16 - testTxPoolConfig.GlobalQueue = 32768 - testTxPoolConfig.GlobalSlots = 32768 - testTxPoolConfig.Lifetime = time.Hour + 30*time.Minute //"1h30m0s" - testTxPoolConfig.PriceLimit = 1 - - now := time.Now() - pendingAddedCh := make(chan struct{}, 1024) - - pool, key := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, cfg.gasLimit, MakeWithPromoteTxCh(pendingAddedCh)) - defer pool.Stop() - - totalAccs := rapid.IntRange(cfg.minAccs, cfg.maxAccs).Draw(rt, "totalAccs").(int) - - fmt.Fprintf(caseParams, "Case params: totalAccs = %d;", totalAccs) - - defer func() { - pending, queued := pool.Content() - - if len(pending) != 0 { - pendingGas := make([]float64, 0, len(pending)) - - for _, txs := range pending { - for _, tx := range txs { - pendingGas = append(pendingGas, float64(tx.Gas())) - } - } - - mean, stddev := stat.MeanStdDev(pendingGas, nil) - fmt.Fprintf(caseParams, "\tpending mean %d, stdev %d, %d-%d;\n", int64(mean), int64(stddev), int64(floats.Min(pendingGas)), int64(floats.Max(pendingGas))) - } - - if len(queued) != 0 { - queuedGas := make([]float64, 0, len(queued)) - - for _, txs := range queued { - for _, tx := range txs { - queuedGas = append(queuedGas, float64(tx.Gas())) - } - } - - mean, stddev := stat.MeanStdDev(queuedGas, nil) - fmt.Fprintf(caseParams, "\tqueued mean %d, stdev %d, %d-%d);\n\n", int64(mean), int64(stddev), int64(floats.Min(queuedGas)), int64(floats.Max(queuedGas))) - } - - rt.Log(caseParams) - }() - - // regenerate only local key - localKey := &acc{ - key: key, - account: crypto.PubkeyToAddress(key.PublicKey), - } - - if err := validatePoolInternals(pool); err != nil { - rt.Fatalf("pool internal state corrupted: %v", err) - } - - var wg sync.WaitGroup - - wg.Add(1) - - go func() { - defer wg.Done() - - now = time.Now() - - testAddBalance(pool, localKey.account, initialBalance) - - for idx := 0; idx < totalAccs; idx++ { - testAddBalance(pool, keys[idx].account, initialBalance) - } - }() - - nonces := make([]uint64, totalAccs) - gen := rapid.Custom(transactionsGen(keys, nonces, localKey, cfg.minTxs, cfg.maxTxs, cfg.gasPriceMin, cfg.gasPriceMax, cfg.gasLimitMin, cfg.gasLimitMax, caseParams)) - - txs := gen.Draw(rt, "batches").(*transactionBatches) - - wg.Wait() - - var ( - addIntoTxPool func(tx *types.Transaction) error - totalInBatch int - ) - - for _, tx := range txs.txs { - addIntoTxPool = pool.AddRemoteSync - - if tx.isLocal { - addIntoTxPool = pool.AddLocal - } - - err := addIntoTxPool(tx.tx) - if err != nil { - rt.Log("on adding a transaction to the tx pool", err, tx.tx.Gas(), tx.tx.GasPrice(), pool.GasPrice(), getBalance(pool, keys[tx.idx].account)) - } - } - - var ( - block int - emptyBlocks int - stuckBlocks int - lastTxPoolStats int - currentTxPoolStats int - ) - - for { - // we'd expect fulfilling block take comparable, but less than blockTime - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(cfg.maxStuckBlocks)*cfg.blockTime) - - select { - case <-pendingAddedCh: - case <-ctx.Done(): - pendingStat, queuedStat := pool.Stats() - if pendingStat+queuedStat == 0 { - cancel() - - break - } - - rt.Fatalf("got %ds block timeout (expected less then %s): total accounts %d. Pending %d, queued %d)", - block, 5*cfg.blockTime, txs.totalTxs, pendingStat, queuedStat) - } - - pendingStat, queuedStat := pool.Stats() - - currentTxPoolStats = pendingStat + queuedStat - if currentTxPoolStats == 0 { - cancel() - break - } - - // check if txPool got stuck - if currentTxPoolStats == lastTxPoolStats { - stuckBlocks++ //todo: need something better then that - } else { - stuckBlocks = 0 - lastTxPoolStats = currentTxPoolStats - } - - // copy-paste - start := time.Now() - pending := pool.Pending(context.Background(), true) - locals := pool.Locals() - - // from fillTransactions - removedFromPool, blockGasLeft, err := fillTransactions(ctx, pool, locals, pending, cfg.gasLimit) - - done := time.Since(start) - - if removedFromPool > 0 { - emptyBlocks = 0 - } else { - emptyBlocks++ - } - - if emptyBlocks >= cfg.maxEmptyBlocks || stuckBlocks >= cfg.maxStuckBlocks { - // check for nonce gaps - var lastNonce, currentNonce int - - pending = pool.Pending(context.Background(), true) - - for txAcc, pendingTxs := range pending { - lastNonce = int(pool.Nonce(txAcc)) - len(pendingTxs) - 1 - - isFirst := true - - for _, tx := range pendingTxs { - currentNonce = int(tx.Nonce()) - if currentNonce-lastNonce != 1 { - rt.Fatalf("got a nonce gap for account %q. Current pending nonce %d, previous %d %v; emptyBlocks - %v; stuckBlocks - %v", - txAcc, currentNonce, lastNonce, isFirst, emptyBlocks >= cfg.maxEmptyBlocks, stuckBlocks >= cfg.maxStuckBlocks) - } - - lastNonce = currentNonce - } - } - } - - if emptyBlocks >= cfg.maxEmptyBlocks { - rt.Fatalf("got %d empty blocks in a row(expected less then %d): total time %s, total accounts %d. Pending %d, locals %d)", - emptyBlocks, cfg.maxEmptyBlocks, done, txs.totalTxs, len(pending), len(locals)) - } - - if stuckBlocks >= cfg.maxStuckBlocks { - rt.Fatalf("got %d empty blocks in a row(expected less then %d): total time %s, total accounts %d. Pending %d, locals %d)", - emptyBlocks, cfg.maxEmptyBlocks, done, txs.totalTxs, len(pending), len(locals)) - } - - if err != nil { - rt.Fatalf("took too long: total time %s(expected %s), total accounts %d. Pending %d, locals %d)", - done, cfg.blockTime, txs.totalTxs, len(pending), len(locals)) - } - - rt.Log("current_total", txs.totalTxs, "in_batch", totalInBatch, "removed", removedFromPool, "emptyBlocks", emptyBlocks, "blockGasLeft", blockGasLeft, "pending", len(pending), "locals", len(locals), - "locals+pending", done) - - rt.Log("block", block, "pending", pendingStat, "queued", queuedStat, "elapsed", done) - - block++ - - cancel() - // time.Sleep(time.Second) - } - - rt.Logf("case completed totalTxs %d %v\n\n", txs.totalTxs, time.Since(now)) - }) - }) - } - - t.Log("done test cases", atomic.LoadUint64(testsDone)) -} - -func fillTransactions(ctx context.Context, pool *TxPool, locals []common.Address, pending map[common.Address]types.Transactions, gasLimit uint64) (int, uint64, error) { - localTxs := make(map[common.Address]types.Transactions) - remoteTxs := pending - - for _, txAcc := range locals { - if txs := remoteTxs[txAcc]; len(txs) > 0 { - delete(remoteTxs, txAcc) - - localTxs[txAcc] = txs - } - } - - // fake signer - signer := types.NewLondonSigner(big.NewInt(1)) - - // fake baseFee - baseFee := uint256.NewInt(1) - - blockGasLimit := gasLimit - - var ( - txLocalCount int - txRemoteCount int - ) - - if len(localTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(signer, localTxs, baseFee) - - select { - case <-ctx.Done(): - return txLocalCount + txRemoteCount, blockGasLimit, ctx.Err() - default: - } - - blockGasLimit, txLocalCount = commitTransactions(pool, txs, blockGasLimit) - } - - select { - case <-ctx.Done(): - return txLocalCount + txRemoteCount, blockGasLimit, ctx.Err() - default: - } - - if len(remoteTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(signer, remoteTxs, baseFee) - - select { - case <-ctx.Done(): - return txLocalCount + txRemoteCount, blockGasLimit, ctx.Err() - default: - } - - blockGasLimit, txRemoteCount = commitTransactions(pool, txs, blockGasLimit) - } - - return txLocalCount + txRemoteCount, blockGasLimit, nil -} - -func commitTransactions(pool *TxPool, txs *types.TransactionsByPriceAndNonce, blockGasLimit uint64) (uint64, int) { - var ( - tx *types.Transaction - txCount int - ) - - for { - tx = txs.Peek() - - if tx == nil { - return blockGasLimit, txCount - } - - if tx.Gas() <= blockGasLimit { - blockGasLimit -= tx.Gas() - - pool.mu.Lock() - pool.removeTx(tx.Hash(), false) - pool.mu.Unlock() - - txCount++ - } else { - // we don't maximize fulfilment of the block. just fill somehow - return blockGasLimit, txCount - } - } -} - -func MakeWithPromoteTxCh(ch chan struct{}) func(*TxPool) { - return func(pool *TxPool) { - pool.promoteTxCh = ch - } -} - -func BenchmarkBigs(b *testing.B) { - // max 256-bit - max := new(big.Int) - max.Exp(big.NewInt(2), big.NewInt(256), nil).Sub(max, big.NewInt(1)) - - ints := make([]*big.Int, 1000000) - intUs := make([]*uint256.Int, 1000000) - - var over bool - - for i := 0; i < len(ints); i++ { - ints[i] = crand.BigInt(max) - intUs[i], over = uint256.FromBig(ints[i]) - - if over { - b.Fatal(ints[i], over) - } - } - - b.Run("*big.Int", func(b *testing.B) { - var r int - - for i := 0; i < b.N; i++ { - r = ints[i%len(ints)%b.N].Cmp(ints[(i+1)%len(ints)%b.N]) - } - - fmt.Fprintln(io.Discard, r) - }) - b.Run("*uint256.Int", func(b *testing.B) { - var r int - - for i := 0; i < b.N; i++ { - r = intUs[i%len(intUs)%b.N].Cmp(intUs[(i+1)%len(intUs)%b.N]) - } - - fmt.Fprintln(io.Discard, r) - }) -} - -//nolint:thelper -func mining(tb testing.TB, pool *TxPool, signer types.Signer, baseFee *uint256.Int, blockGasLimit uint64, totalBlocks int) (int, time.Duration, time.Duration) { - var ( - localTxsCount int - remoteTxsCount int - localTxs = make(map[common.Address]types.Transactions) - remoteTxs map[common.Address]types.Transactions - total int - ) - - start := time.Now() - - pending := pool.Pending(context.Background(), true) - - pendingDuration := time.Since(start) - - remoteTxs = pending - - locals := pool.Locals() - - pendingLen, queuedLen := pool.Stats() - - for _, account := range locals { - if txs := remoteTxs[account]; len(txs) > 0 { - delete(remoteTxs, account) - - localTxs[account] = txs - } - } - - localTxsCount = len(localTxs) - remoteTxsCount = len(remoteTxs) - - var txLocalCount int - - if localTxsCount > 0 { - txs := types.NewTransactionsByPriceAndNonce(signer, localTxs, baseFee) - - blockGasLimit, txLocalCount = commitTransactions(pool, txs, blockGasLimit) - - total += txLocalCount - } - - var txRemoteCount int - - if remoteTxsCount > 0 { - txs := types.NewTransactionsByPriceAndNonce(signer, remoteTxs, baseFee) - - _, txRemoteCount = commitTransactions(pool, txs, blockGasLimit) - - total += txRemoteCount - } - - miningDuration := time.Since(start) - - tb.Logf("[%s] mining block. block %d. total %d: pending %d(added %d), local %d(added %d), queued %d, localTxsCount %d, remoteTxsCount %d, pending %v, mining %v", - common.NowMilliseconds(), totalBlocks, total, pendingLen, txRemoteCount, localTxsCount, txLocalCount, queuedLen, localTxsCount, remoteTxsCount, pendingDuration, miningDuration) - - return total, pendingDuration, miningDuration -} - -//nolint:paralleltest -func TestPoolMiningDataRaces(t *testing.T) { - if testing.Short() { - t.Skip("only for data race testing") - } - - const format = "size %d, txs ticker %v, api ticker %v" - - cases := []struct { - name string - size int - txsTickerDuration time.Duration - apiTickerDuration time.Duration - }{ - { - size: 1, - txsTickerDuration: 200 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 1, - txsTickerDuration: 400 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 1, - txsTickerDuration: 600 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 1, - txsTickerDuration: 800 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - - { - size: 5, - txsTickerDuration: 200 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 5, - txsTickerDuration: 400 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 5, - txsTickerDuration: 600 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 5, - txsTickerDuration: 800 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - - { - size: 10, - txsTickerDuration: 200 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 10, - txsTickerDuration: 400 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 10, - txsTickerDuration: 600 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 10, - txsTickerDuration: 800 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - - { - size: 20, - txsTickerDuration: 200 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 20, - txsTickerDuration: 400 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 20, - txsTickerDuration: 600 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 20, - txsTickerDuration: 800 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - - { - size: 30, - txsTickerDuration: 200 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 30, - txsTickerDuration: 400 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 30, - txsTickerDuration: 600 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - { - size: 30, - txsTickerDuration: 800 * time.Millisecond, - apiTickerDuration: 10 * time.Millisecond, - }, - } - - for i := range cases { - cases[i].name = fmt.Sprintf(format, cases[i].size, cases[i].txsTickerDuration, cases[i].apiTickerDuration) - } - - //nolint:paralleltest - for _, testCase := range cases { - singleCase := testCase - - t.Run(singleCase.name, func(t *testing.T) { - defer goleak.VerifyNone(t, leak.IgnoreList()...) - - const ( - blocks = 300 - blockGasLimit = 40_000_000 - blockPeriod = time.Second - threads = 10 - batchesSize = 10_000 - timeoutDuration = 10 * blockPeriod - - balanceStr = "1_000_000_000_000" - ) - - apiWithMining(t, balanceStr, batchesSize, singleCase, timeoutDuration, threads, blockPeriod, blocks, blockGasLimit) - }) - } -} - -//nolint:gocognit,thelper -func apiWithMining(tb testing.TB, balanceStr string, batchesSize int, singleCase struct { - name string - size int - txsTickerDuration time.Duration - apiTickerDuration time.Duration -}, timeoutDuration time.Duration, threads int, blockPeriod time.Duration, blocks int, blockGasLimit uint64) { - done := make(chan struct{}) - - var wg sync.WaitGroup - - defer func() { - close(done) - - tb.Logf("[%s] finishing apiWithMining", common.NowMilliseconds()) - - wg.Wait() - - tb.Logf("[%s] apiWithMining finished", common.NowMilliseconds()) - }() - - // Generate a batch of transactions to enqueue into the pool - pendingAddedCh := make(chan struct{}, 1024) - - pool, localKey := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, txPoolGasLimit, MakeWithPromoteTxCh(pendingAddedCh)) - defer pool.Stop() - - localKeyPub := localKey.PublicKey - account := crypto.PubkeyToAddress(localKeyPub) - - balance, ok := big.NewInt(0).SetString(balanceStr, 0) - if !ok { - tb.Fatal("incorrect initial balance", balanceStr) - } - - testAddBalance(pool, account, balance) - - signer := types.NewEIP155Signer(big.NewInt(1)) - baseFee := uint256.NewInt(1) - - batchesLocal := make([]types.Transactions, batchesSize) - batchesRemote := make([]types.Transactions, batchesSize) - batchesRemotes := make([]types.Transactions, batchesSize) - batchesRemoteSync := make([]types.Transactions, batchesSize) - batchesRemotesSync := make([]types.Transactions, batchesSize) - - for i := 0; i < batchesSize; i++ { - batchesLocal[i] = make(types.Transactions, singleCase.size) - - for j := 0; j < singleCase.size; j++ { - batchesLocal[i][j] = pricedTransaction(uint64(singleCase.size*i+j), 100_000, big.NewInt(int64(i+1)), localKey) - } - - batchesRemote[i] = make(types.Transactions, singleCase.size) - - remoteKey, _ := crypto.GenerateKey() - remoteAddr := crypto.PubkeyToAddress(remoteKey.PublicKey) - testAddBalance(pool, remoteAddr, balance) - - for j := 0; j < singleCase.size; j++ { - batchesRemote[i][j] = pricedTransaction(uint64(j), 100_000, big.NewInt(int64(i+1)), remoteKey) - } - - batchesRemotes[i] = make(types.Transactions, singleCase.size) - - remotesKey, _ := crypto.GenerateKey() - remotesAddr := crypto.PubkeyToAddress(remotesKey.PublicKey) - testAddBalance(pool, remotesAddr, balance) - - for j := 0; j < singleCase.size; j++ { - batchesRemotes[i][j] = pricedTransaction(uint64(j), 100_000, big.NewInt(int64(i+1)), remotesKey) - } - - batchesRemoteSync[i] = make(types.Transactions, singleCase.size) - - remoteSyncKey, _ := crypto.GenerateKey() - remoteSyncAddr := crypto.PubkeyToAddress(remoteSyncKey.PublicKey) - testAddBalance(pool, remoteSyncAddr, balance) - - for j := 0; j < singleCase.size; j++ { - batchesRemoteSync[i][j] = pricedTransaction(uint64(j), 100_000, big.NewInt(int64(i+1)), remoteSyncKey) - } - - batchesRemotesSync[i] = make(types.Transactions, singleCase.size) - - remotesSyncKey, _ := crypto.GenerateKey() - remotesSyncAddr := crypto.PubkeyToAddress(remotesSyncKey.PublicKey) - testAddBalance(pool, remotesSyncAddr, balance) - - for j := 0; j < singleCase.size; j++ { - batchesRemotesSync[i][j] = pricedTransaction(uint64(j), 100_000, big.NewInt(int64(i+1)), remotesSyncKey) - } - } - - tb.Logf("[%s] starting goroutines", common.NowMilliseconds()) - - txsTickerDuration := singleCase.txsTickerDuration - apiTickerDuration := singleCase.apiTickerDuration - - // locals - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping AddLocal(s)", common.NowMilliseconds()) - - wg.Done() - - tb.Logf("[%s] stopped AddLocal(s)", common.NowMilliseconds()) - }() - - tb.Logf("[%s] starting AddLocal(s)", common.NowMilliseconds()) - - for _, batch := range batchesLocal { - batch := batch - - select { - case <-done: - return - default: - } - - if rand.Int()%2 == 0 { - runWithTimeout(tb, func(_ chan struct{}) { - errs := pool.AddLocals(batch) - if len(errs) != 0 { - tb.Logf("[%s] AddLocals error, %v", common.NowMilliseconds(), errs) - } - }, done, "AddLocals", timeoutDuration, 0, 0) - } else { - for _, tx := range batch { - tx := tx - - runWithTimeout(tb, func(_ chan struct{}) { - err := pool.AddLocal(tx) - if err != nil { - tb.Logf("[%s] AddLocal error %s", common.NowMilliseconds(), err) - } - }, done, "AddLocal", timeoutDuration, 0, 0) - - time.Sleep(txsTickerDuration) - } - } - - time.Sleep(txsTickerDuration) - } - }() - - // remotes - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping AddRemotes", common.NowMilliseconds()) - - wg.Done() - - tb.Logf("[%s] stopped AddRemotes", common.NowMilliseconds()) - }() - - addTransactionsBatches(tb, batchesRemotes, getFnForBatches(pool.AddRemotes), done, timeoutDuration, txsTickerDuration, "AddRemotes", 0) - }() - - // remote - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping AddRemote", common.NowMilliseconds()) - - wg.Done() - - tb.Logf("[%s] stopped AddRemote", common.NowMilliseconds()) - }() - - addTransactions(tb, batchesRemote, pool.AddRemote, done, timeoutDuration, txsTickerDuration, "AddRemote", 0) - }() - - // sync - // remotes - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping AddRemotesSync", common.NowMilliseconds()) - - wg.Done() - - tb.Logf("[%s] stopped AddRemotesSync", common.NowMilliseconds()) - }() - - addTransactionsBatches(tb, batchesRemotesSync, getFnForBatches(pool.AddRemotesSync), done, timeoutDuration, txsTickerDuration, "AddRemotesSync", 0) - }() - - // remote - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping AddRemoteSync", common.NowMilliseconds()) - - wg.Done() - - tb.Logf("[%s] stopped AddRemoteSync", common.NowMilliseconds()) - }() - - addTransactions(tb, batchesRemoteSync, pool.AddRemoteSync, done, timeoutDuration, txsTickerDuration, "AddRemoteSync", 0) - }() - - // tx pool API - for i := 0; i < threads; i++ { - i := i - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Pending-no-tips, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Pending-no-tips, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - p := pool.Pending(context.Background(), false) - fmt.Fprint(io.Discard, p) - }, done, "Pending-no-tips", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Pending-with-tips, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Pending-with-tips, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - p := pool.Pending(context.Background(), true) - fmt.Fprint(io.Discard, p) - }, done, "Pending-with-tips", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Locals, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Locals, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - l := pool.Locals() - fmt.Fprint(io.Discard, l) - }, done, "Locals", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Content, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Content, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - p, q := pool.Content() - fmt.Fprint(io.Discard, p, q) - }, done, "Content", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping GasPriceUint256, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped GasPriceUint256, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - res := pool.GasPriceUint256() - fmt.Fprint(io.Discard, res) - }, done, "GasPriceUint256", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping GasPrice, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped GasPrice, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - res := pool.GasPrice() - fmt.Fprint(io.Discard, res) - }, done, "GasPrice", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping SetGasPrice, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped SetGasPrice, , thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - pool.SetGasPrice(pool.GasPrice()) - }, done, "SetGasPrice", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping ContentFrom, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped ContentFrom, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - p, q := pool.ContentFrom(account) - fmt.Fprint(io.Discard, p, q) - }, done, "ContentFrom", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Has, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Has, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - res := pool.Has(batchesRemotes[0][0].Hash()) - fmt.Fprint(io.Discard, res) - }, done, "Has", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Get, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Get, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - tx := pool.Get(batchesRemotes[0][0].Hash()) - fmt.Fprint(io.Discard, tx == nil) - }, done, "Get", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Nonce, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Nonce, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - res := pool.Nonce(account) - fmt.Fprint(io.Discard, res) - }, done, "Nonce", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Stats, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Stats, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - p, q := pool.Stats() - fmt.Fprint(io.Discard, p, q) - }, done, "Stats", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping Status, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped Status, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(_ chan struct{}) { - st := pool.Status([]common.Hash{batchesRemotes[1][0].Hash()}) - fmt.Fprint(io.Discard, st) - }, done, "Status", apiTickerDuration, timeoutDuration, i) - }() - - wg.Add(1) - - go func() { - defer func() { - tb.Logf("[%s] stopping SubscribeNewTxsEvent, thread %d", common.NowMilliseconds(), i) - - wg.Done() - - tb.Logf("[%s] stopped SubscribeNewTxsEvent, thread %d", common.NowMilliseconds(), i) - }() - - runWithTicker(tb, func(c chan struct{}) { - ch := make(chan core.NewTxsEvent, 10) - sub := pool.SubscribeNewTxsEvent(ch) - - if sub == nil { - return - } - - defer sub.Unsubscribe() - - select { - case <-done: - return - case <-c: - case res := <-ch: - fmt.Fprint(io.Discard, res) - } - }, done, "SubscribeNewTxsEvent", apiTickerDuration, timeoutDuration, i) - }() - } - - // wait for the start - tb.Logf("[%s] before the first propagated transaction", common.NowMilliseconds()) - <-pendingAddedCh - tb.Logf("[%s] after the first propagated transaction", common.NowMilliseconds()) - - var ( - totalTxs int - totalBlocks int - ) - - pendingDurations := make([]time.Duration, 0, blocks) - - var ( - added int - pendingDuration time.Duration - miningDuration time.Duration - diff time.Duration - ) - - for { - added, pendingDuration, miningDuration = mining(tb, pool, signer, baseFee, blockGasLimit, totalBlocks) - - totalTxs += added - - pendingDurations = append(pendingDurations, pendingDuration) - - totalBlocks++ - - if totalBlocks > blocks { - fmt.Fprint(io.Discard, totalTxs) - break - } - - diff = blockPeriod - miningDuration - if diff > 0 { - time.Sleep(diff) - } - } - - pendingDurationsFloat := make([]float64, len(pendingDurations)) - - for i, v := range pendingDurations { - pendingDurationsFloat[i] = float64(v.Nanoseconds()) - } - - mean, stddev := stat.MeanStdDev(pendingDurationsFloat, nil) - tb.Logf("[%s] pending mean %v, stddev %v, %v-%v", - common.NowMilliseconds(), time.Duration(mean), time.Duration(stddev), time.Duration(floats.Min(pendingDurationsFloat)), time.Duration(floats.Max(pendingDurationsFloat))) -} - -func addTransactionsBatches(tb testing.TB, batches []types.Transactions, fn func(types.Transactions) error, done chan struct{}, timeoutDuration time.Duration, tickerDuration time.Duration, name string, thread int) { - tb.Helper() - - tb.Logf("[%s] starting %s", common.NowMilliseconds(), name) - - defer func() { - tb.Logf("[%s] stop %s", common.NowMilliseconds(), name) - }() - - for _, batch := range batches { - batch := batch - - select { - case <-done: - return - default: - } - - runWithTimeout(tb, func(_ chan struct{}) { - err := fn(batch) - if err != nil { - tb.Logf("[%s] %s error: %s", common.NowMilliseconds(), name, err) - } - }, done, name, timeoutDuration, 0, thread) - - time.Sleep(tickerDuration) - } -} - -func addTransactions(tb testing.TB, batches []types.Transactions, fn func(*types.Transaction) error, done chan struct{}, timeoutDuration time.Duration, tickerDuration time.Duration, name string, thread int) { - tb.Helper() - - tb.Logf("[%s] starting %s", common.NowMilliseconds(), name) - - defer func() { - tb.Logf("[%s] stop %s", common.NowMilliseconds(), name) - }() - - for _, batch := range batches { - for _, tx := range batch { - tx := tx - - select { - case <-done: - return - default: - } - - runWithTimeout(tb, func(_ chan struct{}) { - err := fn(tx) - if err != nil { - tb.Logf("%s error: %s", name, err) - } - }, done, name, timeoutDuration, 0, thread) - - time.Sleep(tickerDuration) - } - - time.Sleep(tickerDuration) - } -} - -func getFnForBatches(fn func([]*types.Transaction) []error) func(types.Transactions) error { - return func(batch types.Transactions) error { - errs := fn(batch) - if len(errs) != 0 { - return errs[0] - } - - return nil - } -} - -//nolint:unparam -func runWithTicker(tb testing.TB, fn func(c chan struct{}), done chan struct{}, name string, tickerDuration, timeoutDuration time.Duration, thread int) { - tb.Helper() - - select { - case <-done: - tb.Logf("[%s] Short path. finishing outer runWithTicker for %q, thread %d", common.NowMilliseconds(), name, thread) - - return - default: - } - - defer func() { - tb.Logf("[%s] finishing outer runWithTicker for %q, thread %d", common.NowMilliseconds(), name, thread) - }() - - localTicker := time.NewTicker(tickerDuration) - defer localTicker.Stop() - - n := 0 - - for range localTicker.C { - select { - case <-done: - return - default: - } - - runWithTimeout(tb, fn, done, name, timeoutDuration, n, thread) - - n++ - } -} - -func runWithTimeout(tb testing.TB, fn func(chan struct{}), outerDone chan struct{}, name string, timeoutDuration time.Duration, n, thread int) { - tb.Helper() - - select { - case <-outerDone: - tb.Logf("[%s] Short path. exiting inner runWithTimeout by outer exit event for %q, thread %d, iteration %d", common.NowMilliseconds(), name, thread, n) - - return - default: - } - - timeout := time.NewTimer(timeoutDuration) - defer timeout.Stop() - - doneCh := make(chan struct{}) - - isError := new(int32) - *isError = 0 - - go func() { - defer close(doneCh) - - select { - case <-outerDone: - return - default: - fn(doneCh) - } - }() - - const isDebug = false - - var stack string - - select { - case <-outerDone: - tb.Logf("[%s] exiting inner runWithTimeout by outer exit event for %q, thread %d, iteration %d", common.NowMilliseconds(), name, thread, n) - case <-doneCh: - // only for debug - //tb.Logf("[%s] exiting inner runWithTimeout by successful call for %q, thread %d, iteration %d", common.NowMilliseconds(), name, thread, n) - case <-timeout.C: - atomic.StoreInt32(isError, 1) - - if isDebug { - stack = string(debug.Stack(true)) - } - - tb.Errorf("[%s] %s timeouted, thread %d, iteration %d. Stack %s", common.NowMilliseconds(), name, thread, n, stack) - } -} - -// Benchmarks the speed of batch transaction insertion in case of multiple accounts. -func BenchmarkMultiAccountBatchInsert(b *testing.B) { - // Generate a batch of transactions to enqueue into the pool - pool, _ := setupPool() - defer pool.Stop() - b.ReportAllocs() - batches := make(types.Transactions, b.N) - - for i := 0; i < b.N; i++ { - key, _ := crypto.GenerateKey() - account := crypto.PubkeyToAddress(key.PublicKey) - pool.currentState.AddBalance(account, big.NewInt(1000000)) - - tx := transaction(uint64(0), 100000, key) - batches[i] = tx - } - // Benchmark importing the transactions into the queue - b.ResetTimer() - for _, tx := range batches { pool.AddRemotesSync([]*types.Transaction{tx}) } diff --git a/packaging/templates/package_scripts/control b/packaging/templates/package_scripts/control index aaecd85740..2dd86bdb21 100644 --- a/packaging/templates/package_scripts/control +++ b/packaging/templates/package_scripts/control @@ -1,5 +1,5 @@ Source: bor -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/packaging/templates/package_scripts/control.arm64 b/packaging/templates/package_scripts/control.arm64 index 32d014ed57..f78d30ae0f 100644 --- a/packaging/templates/package_scripts/control.arm64 +++ b/packaging/templates/package_scripts/control.arm64 @@ -1,5 +1,5 @@ Source: bor -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/packaging/templates/package_scripts/control.profile.amd64 b/packaging/templates/package_scripts/control.profile.amd64 index a51a03803e..124c994656 100644 --- a/packaging/templates/package_scripts/control.profile.amd64 +++ b/packaging/templates/package_scripts/control.profile.amd64 @@ -1,5 +1,5 @@ Source: bor-profile -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/packaging/templates/package_scripts/control.profile.arm64 b/packaging/templates/package_scripts/control.profile.arm64 index 0d1da4ede5..6db19c667f 100644 --- a/packaging/templates/package_scripts/control.profile.arm64 +++ b/packaging/templates/package_scripts/control.profile.arm64 @@ -1,5 +1,5 @@ Source: bor-profile -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/packaging/templates/package_scripts/control.validator b/packaging/templates/package_scripts/control.validator index 7c1405194a..db5ec2538f 100644 --- a/packaging/templates/package_scripts/control.validator +++ b/packaging/templates/package_scripts/control.validator @@ -1,5 +1,5 @@ Source: bor-profile -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/packaging/templates/package_scripts/control.validator.arm64 b/packaging/templates/package_scripts/control.validator.arm64 index 1412d9325e..bcb01629b3 100644 --- a/packaging/templates/package_scripts/control.validator.arm64 +++ b/packaging/templates/package_scripts/control.validator.arm64 @@ -1,5 +1,5 @@ Source: bor-profile -Version: 1.0.6 +Version: 1.0.6-beta-txpool Section: develop Priority: standard Maintainer: Polygon diff --git a/params/version.go b/params/version.go index 6e09af0ad9..3700bf4206 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 0 // Minor version component of the current release - VersionPatch = 6 // Patch version component of the current release - VersionMeta = "" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 0 // Minor version component of the current release + VersionPatch = 6 // Patch version component of the current release + VersionMeta = "beta-txpool" // Version metadata to append to the version string ) var GitCommit string