mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 02:10:46 +00:00
core: move TxPool reorg and events to background goroutine (#19705)
This commit is contained in:
parent
ddbf5d2782
commit
74c72363d0
2 changed files with 540 additions and 449 deletions
641
core/tx_pool.go
641
core/tx_pool.go
|
|
@ -234,8 +234,6 @@ type TxPool struct {
|
||||||
gasPrice *big.Int
|
gasPrice *big.Int
|
||||||
txFeed event.Feed
|
txFeed event.Feed
|
||||||
scope event.SubscriptionScope
|
scope event.SubscriptionScope
|
||||||
chainHeadCh chan ChainHeadEvent
|
|
||||||
chainHeadSub event.Subscription
|
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
|
@ -252,13 +250,23 @@ type TxPool struct {
|
||||||
all *txLookup // All transactions to allow lookups
|
all *txLookup // All transactions to allow lookups
|
||||||
priced *txPricedList // All transactions sorted by price
|
priced *txPricedList // All transactions sorted by price
|
||||||
|
|
||||||
wg sync.WaitGroup // for shutdown sync
|
chainHeadCh chan ChainHeadEvent
|
||||||
|
chainHeadSub event.Subscription
|
||||||
|
reqResetCh chan *txpoolResetRequest
|
||||||
|
reqPromoteCh chan *accountSet
|
||||||
|
queueTxEventCh chan *types.Transaction
|
||||||
|
reorgDoneCh chan chan struct{}
|
||||||
|
reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop
|
||||||
|
wg sync.WaitGroup // tracks loop, scheduleReorgLoop
|
||||||
|
|
||||||
homestead bool
|
|
||||||
IsSigner func(address common.Address) bool
|
IsSigner func(address common.Address) bool
|
||||||
trc21FeeCapacity map[common.Address]*big.Int
|
trc21FeeCapacity map[common.Address]*big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type txpoolResetRequest struct {
|
||||||
|
oldHead, newHead *types.Header
|
||||||
|
}
|
||||||
|
|
||||||
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
||||||
// transactions from the network.
|
// transactions from the network.
|
||||||
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
||||||
|
|
@ -276,6 +284,11 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||||
beats: make(map[common.Address]time.Time),
|
beats: make(map[common.Address]time.Time),
|
||||||
all: newTxLookup(),
|
all: newTxLookup(),
|
||||||
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
|
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
|
||||||
|
reqResetCh: make(chan *txpoolResetRequest),
|
||||||
|
reqPromoteCh: make(chan *accountSet),
|
||||||
|
queueTxEventCh: make(chan *types.Transaction),
|
||||||
|
reorgDoneCh: make(chan chan struct{}),
|
||||||
|
reorgShutdownCh: make(chan struct{}),
|
||||||
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
||||||
trc21FeeCapacity: map[common.Address]*big.Int{},
|
trc21FeeCapacity: map[common.Address]*big.Int{},
|
||||||
}
|
}
|
||||||
|
|
@ -287,6 +300,10 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||||
pool.priced = newTxPricedList(pool.all)
|
pool.priced = newTxPricedList(pool.all)
|
||||||
pool.reset(nil, chain.CurrentBlock().Header())
|
pool.reset(nil, chain.CurrentBlock().Header())
|
||||||
|
|
||||||
|
// Start the reorg loop early so it can handle requests generated during journal loading.
|
||||||
|
pool.wg.Add(1)
|
||||||
|
go pool.scheduleReorgLoop()
|
||||||
|
|
||||||
// If local transactions and journaling is enabled, load from disk
|
// If local transactions and journaling is enabled, load from disk
|
||||||
if !config.NoLocals && config.Journal != "" {
|
if !config.NoLocals && config.Journal != "" {
|
||||||
pool.journal = newTxJournal(config.Journal)
|
pool.journal = newTxJournal(config.Journal)
|
||||||
|
|
@ -298,10 +315,9 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||||
log.Warn("Failed to rotate transaction journal", "err", err)
|
log.Warn("Failed to rotate transaction journal", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Subscribe events from blockchain
|
|
||||||
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
|
|
||||||
|
|
||||||
// Start the event loop and return
|
// Subscribe events from blockchain and start the main event loop.
|
||||||
|
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
|
||||||
pool.wg.Add(1)
|
pool.wg.Add(1)
|
||||||
go pool.loop()
|
go pool.loop()
|
||||||
|
|
||||||
|
|
@ -314,38 +330,31 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||||
func (pool *TxPool) loop() {
|
func (pool *TxPool) loop() {
|
||||||
defer pool.wg.Done()
|
defer pool.wg.Done()
|
||||||
|
|
||||||
|
var (
|
||||||
|
prevPending, prevQueued, prevStales int
|
||||||
// Start the stats reporting and transaction eviction tickers
|
// Start the stats reporting and transaction eviction tickers
|
||||||
var prevPending, prevQueued, prevStales int
|
report = time.NewTicker(statsReportInterval)
|
||||||
|
evict = time.NewTicker(evictionInterval)
|
||||||
report := time.NewTicker(statsReportInterval)
|
journal = time.NewTicker(pool.config.Rejournal)
|
||||||
|
// Track the previous head headers for transaction reorgs
|
||||||
|
head = pool.chain.CurrentBlock()
|
||||||
|
)
|
||||||
defer report.Stop()
|
defer report.Stop()
|
||||||
|
|
||||||
evict := time.NewTicker(evictionInterval)
|
|
||||||
defer evict.Stop()
|
defer evict.Stop()
|
||||||
|
|
||||||
journal := time.NewTicker(pool.config.Rejournal)
|
|
||||||
defer journal.Stop()
|
defer journal.Stop()
|
||||||
|
|
||||||
// Track the previous head headers for transaction reorgs
|
|
||||||
head := pool.chain.CurrentBlock()
|
|
||||||
|
|
||||||
// Keep waiting for and reacting to the various events
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
// Handle ChainHeadEvent
|
// Handle ChainHeadEvent
|
||||||
case ev := <-pool.chainHeadCh:
|
case ev := <-pool.chainHeadCh:
|
||||||
if ev.Block != nil {
|
if ev.Block != nil {
|
||||||
pool.mu.Lock()
|
pool.requestReset(head.Header(), ev.Block.Header())
|
||||||
if pool.chainconfig.IsHomestead(ev.Block.Number()) {
|
|
||||||
pool.homestead = true
|
|
||||||
}
|
|
||||||
pool.reset(head.Header(), ev.Block.Header())
|
|
||||||
head = ev.Block
|
head = ev.Block
|
||||||
|
|
||||||
pool.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
// Be unsubscribed due to system stopped
|
|
||||||
|
// System shutdown.
|
||||||
case <-pool.chainHeadSub.Err():
|
case <-pool.chainHeadSub.Err():
|
||||||
|
close(pool.reorgShutdownCh)
|
||||||
return
|
return
|
||||||
|
|
||||||
// Handle stats reporting ticks
|
// Handle stats reporting ticks
|
||||||
|
|
@ -390,116 +399,6 @@ func (pool *TxPool) loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lockedReset is a wrapper around reset to allow calling it in a thread safe
|
|
||||||
// manner. This method is only ever used in the tester!
|
|
||||||
func (pool *TxPool) lockedReset(oldHead, newHead *types.Header) {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
|
|
||||||
pool.reset(oldHead, newHead)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset retrieves the current state of the blockchain and ensures the content
|
|
||||||
// of the transaction pool is valid with regard to the chain state.
|
|
||||||
func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
|
||||||
// If we're reorging an old state, reinject all dropped transactions
|
|
||||||
var reinject types.Transactions
|
|
||||||
|
|
||||||
if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
|
|
||||||
// If the reorg is too deep, avoid doing it (will happen during fast sync)
|
|
||||||
oldNum := oldHead.Number.Uint64()
|
|
||||||
newNum := newHead.Number.Uint64()
|
|
||||||
|
|
||||||
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
|
||||||
log.Debug("Skipping deep transaction reorg", "depth", depth)
|
|
||||||
} else {
|
|
||||||
// Reorg seems shallow enough to pull in all transactions into memory
|
|
||||||
var discarded, included types.Transactions
|
|
||||||
|
|
||||||
var (
|
|
||||||
rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
|
|
||||||
add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
|
|
||||||
)
|
|
||||||
if rem == nil {
|
|
||||||
// This can happen if a setHead is performed, where we simply discard the old
|
|
||||||
// head from the chain.
|
|
||||||
// If that is the case, we don't have the lost transactions any more, and
|
|
||||||
// there's nothing to add
|
|
||||||
if newNum < oldNum {
|
|
||||||
// If the reorg ended up on a lower number, it's indicative of setHead being the cause
|
|
||||||
log.Debug("Skipping transaction reset caused by setHead",
|
|
||||||
"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
|
|
||||||
} else {
|
|
||||||
// If we reorged to a same or higher number, then it's not a case of setHead
|
|
||||||
log.Warn("Transaction pool reset with missing oldhead",
|
|
||||||
"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for rem.NumberU64() > add.NumberU64() {
|
|
||||||
discarded = append(discarded, rem.Transactions()...)
|
|
||||||
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
|
|
||||||
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for add.NumberU64() > rem.NumberU64() {
|
|
||||||
included = append(included, add.Transactions()...)
|
|
||||||
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
|
|
||||||
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for rem.Hash() != add.Hash() {
|
|
||||||
discarded = append(discarded, rem.Transactions()...)
|
|
||||||
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
|
|
||||||
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
included = append(included, add.Transactions()...)
|
|
||||||
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
|
|
||||||
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reinject = types.TxDifference(discarded, included)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Initialize the internal state to the current head
|
|
||||||
if newHead == nil {
|
|
||||||
newHead = pool.chain.CurrentBlock().Header() // 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.trc21FeeCapacity = state.GetTRC21FeeCapacityFromStateWithCache(newHead.Root, statedb)
|
|
||||||
pool.pendingState = state.ManageState(statedb)
|
|
||||||
pool.currentMaxGas = newHead.GasLimit
|
|
||||||
|
|
||||||
// Inject any transactions discarded due to reorgs
|
|
||||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
|
||||||
senderCacher.recover(pool.signer, reinject)
|
|
||||||
pool.addTxsLocked(reinject, false)
|
|
||||||
|
|
||||||
// validate the pool of pending transactions, this will remove
|
|
||||||
// any transactions that have been included in the block or
|
|
||||||
// have been invalidated because of another transaction (e.g.
|
|
||||||
// higher gas price)
|
|
||||||
pool.demoteUnexecutables()
|
|
||||||
|
|
||||||
// Update all accounts to the latest known pending nonce
|
|
||||||
for addr, list := range pool.pending {
|
|
||||||
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
|
|
||||||
pool.pendingState.SetNonce(addr, txs[len(txs)-1].Nonce()+1)
|
|
||||||
}
|
|
||||||
// Check the queue and move transactions over to the pending if possible
|
|
||||||
// or remove those that have become invalid
|
|
||||||
pool.promoteExecutables(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop terminates the transaction pool.
|
// Stop terminates the transaction pool.
|
||||||
func (pool *TxPool) Stop() {
|
func (pool *TxPool) Stop() {
|
||||||
// Unsubscribe all subscriptions registered from txpool
|
// Unsubscribe all subscriptions registered from txpool
|
||||||
|
|
@ -705,7 +604,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) {
|
if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) {
|
||||||
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
|
// Ensure the transaction has more gas than the basic tx fee.
|
||||||
|
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -715,7 +615,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check zero gas price.
|
// Check zero gas price.
|
||||||
if tx.GasPrice().Cmp(new(big.Int).SetInt64(0)) == 0 {
|
if tx.GasPrice().Sign() == 0 {
|
||||||
return ErrZeroGasPrice
|
return ErrZeroGasPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -746,15 +646,14 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// add validates a transaction and inserts it into the non-executable queue for
|
// add validates a transaction and inserts it into the non-executable queue for later
|
||||||
// later pending promotion and execution. If the transaction is a replacement for
|
// pending promotion and execution. If the transaction is a replacement for an already
|
||||||
// an already pending or queued one, it overwrites the previous and returns this
|
// pending or queued one, it overwrites the previous transaction if its price is higher.
|
||||||
// so outer code doesn't uselessly call promote.
|
|
||||||
//
|
//
|
||||||
// If a newly added transaction is marked as local, its sending account will be
|
// If a newly added transaction is marked as local, its sending account will be
|
||||||
// whitelisted, preventing any associated transaction from being dropped out of
|
// whitelisted, preventing any associated transaction from being dropped out of the pool
|
||||||
// the pool due to pricing constraints.
|
// due to pricing constraints.
|
||||||
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) {
|
||||||
// If the transaction is already known, discard it
|
// If the transaction is already known, discard it
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
if pool.all.Get(hash) != nil {
|
if pool.all.Get(hash) != nil {
|
||||||
|
|
@ -768,10 +667,12 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
invalidTxMeter.Mark(1)
|
invalidTxMeter.Mark(1)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
if tx.IsSpecialTransaction() && pool.IsSigner != nil && pool.IsSigner(from) && pool.pendingState.GetNonce(from) == tx.Nonce() {
|
if tx.IsSpecialTransaction() && pool.IsSigner != nil && pool.IsSigner(from) && pool.pendingState.GetNonce(from) == tx.Nonce() {
|
||||||
return pool.promoteSpecialTx(from, tx)
|
return pool.promoteSpecialTx(from, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the transaction pool is full, discard underpriced transactions
|
// If the transaction pool is full, discard underpriced transactions
|
||||||
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
|
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
|
||||||
log.Debug("Add transaction to pool full", "hash", hash, "nonce", tx.Nonce())
|
log.Debug("Add transaction to pool full", "hash", hash, "nonce", tx.Nonce())
|
||||||
|
|
@ -789,7 +690,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
pool.removeTx(tx.Hash(), false)
|
pool.removeTx(tx.Hash(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the transaction is replacing an already pending one, do directly
|
|
||||||
|
// Try to replace an existing transaction in the pending pool
|
||||||
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
|
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
|
||||||
// Nonce already pending, check if required price bump is met
|
// Nonce already pending, check if required price bump is met
|
||||||
inserted, old := list.Add(tx, pool.config.PriceBump)
|
inserted, old := list.Add(tx, pool.config.PriceBump)
|
||||||
|
|
@ -806,19 +708,17 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
pool.all.Add(tx)
|
pool.all.Add(tx)
|
||||||
pool.priced.Put(tx)
|
pool.priced.Put(tx)
|
||||||
pool.journalTx(from, tx)
|
pool.journalTx(from, tx)
|
||||||
|
pool.queueTxEvent(tx)
|
||||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
|
||||||
// We've directly injected a replacement transaction, notify subsystems
|
|
||||||
go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}})
|
|
||||||
|
|
||||||
return old != nil, nil
|
return old != nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New transaction isn't replacing a pending one, push into queue
|
// New transaction isn't replacing a pending one, push into queue
|
||||||
replace, err := pool.enqueueTx(hash, tx)
|
replaced, err = pool.enqueueTx(hash, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark local addresses and journal local transactions
|
// Mark local addresses and journal local transactions
|
||||||
if local {
|
if local {
|
||||||
if !pool.locals.contains(from) {
|
if !pool.locals.contains(from) {
|
||||||
|
|
@ -832,7 +732,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
pool.journalTx(from, tx)
|
pool.journalTx(from, tx)
|
||||||
|
|
||||||
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
return replace, nil
|
return replaced, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enqueueTx inserts a new transaction into the non-executable transaction queue.
|
// enqueueTx inserts a new transaction into the non-executable transaction queue.
|
||||||
|
|
@ -957,94 +857,85 @@ func (pool *TxPool) promoteSpecialTx(addr common.Address, tx *types.Transaction)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLocal enqueues a single transaction into the pool if it is valid, marking
|
// AddLocals enqueues a batch of transactions into the pool if they are valid, marking the
|
||||||
// the sender as a local one in the mean time, ensuring it goes around the local
|
// senders as a local ones, ensuring they go around the local pricing constraints.
|
||||||
// pricing constraints.
|
//
|
||||||
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
|
// This method is used to add transactions from the RPC API and performs synchronous pool
|
||||||
return pool.addTx(tx, !pool.config.NoLocals)
|
// reorganization and event propagation.
|
||||||
}
|
|
||||||
|
|
||||||
// AddRemote enqueues a single transaction into the pool if it is valid. If the
|
|
||||||
// sender is not among the locally tracked ones, full pricing constraints will
|
|
||||||
// apply.
|
|
||||||
func (pool *TxPool) AddRemote(tx *types.Transaction) error {
|
|
||||||
return pool.addTx(tx, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLocals enqueues a batch of transactions into the pool if they are valid,
|
|
||||||
// marking the senders as a local ones in the mean time, ensuring they go around
|
|
||||||
// the local pricing constraints.
|
|
||||||
func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
|
func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
|
||||||
return pool.addTxs(txs, !pool.config.NoLocals)
|
return pool.addTxs(txs, !pool.config.NoLocals, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRemotes enqueues a batch of transactions into the pool if they are valid.
|
// AddLocal enqueues a single local transaction into the pool if it is valid. This is
|
||||||
// If the senders are not among the locally tracked ones, full pricing constraints
|
// a convenience wrapper aroundd AddLocals.
|
||||||
// will apply.
|
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
|
||||||
|
errs := pool.AddLocals([]*types.Transaction{tx})
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRemotes enqueues a batch of transactions into the pool if they are valid. If the
|
||||||
|
// senders are not among the locally tracked ones, full pricing constraints will apply.
|
||||||
|
//
|
||||||
|
// This method is used to add transactions from the p2p network and does not wait for pool
|
||||||
|
// reorganization and internal event propagation.
|
||||||
func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
|
func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||||
return pool.addTxs(txs, false)
|
return pool.addTxs(txs, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTx enqueues a single transaction into the pool if it is valid.
|
// This is like AddRemotes, but waits for pool reorganization. Tests use this method.
|
||||||
func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
|
func (pool *TxPool) addRemotesSync(txs []*types.Transaction) []error {
|
||||||
// Cache sender in transaction before obtaining lock (pool.signer is immutable)
|
return pool.addTxs(txs, false, true)
|
||||||
types.Sender(pool.signer, tx)
|
|
||||||
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
|
|
||||||
// Try to inject the transaction and update any state
|
|
||||||
replace, err := pool.add(tx, local)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
validMeter.Mark(1)
|
|
||||||
|
|
||||||
// If we added a new transaction, run promotion checks and return
|
// This is like AddRemotes with a single transaction, but waits for pool reorganization. Tests use this method.
|
||||||
if !replace {
|
func (pool *TxPool) addRemoteSync(tx *types.Transaction) error {
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
errs := pool.addRemotesSync([]*types.Transaction{tx})
|
||||||
pool.promoteExecutables([]common.Address{from})
|
return errs[0]
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// AddRemote enqueues a single transaction into the pool if it is valid. This is a convenience
|
||||||
|
// wrapper around AddRemotes.
|
||||||
|
//
|
||||||
|
// Deprecated: use AddRemotes
|
||||||
|
func (pool *TxPool) AddRemote(tx *types.Transaction) error {
|
||||||
|
errs := pool.AddRemotes([]*types.Transaction{tx})
|
||||||
|
return errs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTxs attempts to queue a batch of transactions if they are valid.
|
// addTxs attempts to queue a batch of transactions if they are valid.
|
||||||
func (pool *TxPool) addTxs(txs []*types.Transaction, local bool) []error {
|
func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
|
||||||
// Cache senders in transactions before obtaining lock (pool.signer is immutable)
|
// Cache senders in transactions before obtaining lock (pool.signer is immutable)
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
types.Sender(pool.signer, tx)
|
types.Sender(pool.signer, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
errs, dirtyAddrs := pool.addTxsLocked(txs, local)
|
||||||
|
pool.mu.Unlock()
|
||||||
|
|
||||||
return pool.addTxsLocked(txs, local)
|
done := pool.requestPromoteExecutables(dirtyAddrs)
|
||||||
}
|
if sync {
|
||||||
|
<-done
|
||||||
// addTxsLocked attempts to queue a batch of transactions if they are valid,
|
|
||||||
// whilst assuming the transaction pool lock is already held.
|
|
||||||
func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) []error {
|
|
||||||
// Add the batch of transactions, tracking the accepted ones
|
|
||||||
dirty := make(map[common.Address]struct{})
|
|
||||||
errs := make([]error, len(txs))
|
|
||||||
|
|
||||||
for i, tx := range txs {
|
|
||||||
var replace bool
|
|
||||||
if replace, errs[i] = pool.add(tx, local); errs[i] == nil && !replace {
|
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
|
||||||
dirty[from] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Only reprocess the internal state if something was actually added
|
|
||||||
if len(dirty) > 0 {
|
|
||||||
addrs := make([]common.Address, 0, len(dirty))
|
|
||||||
for addr := range dirty {
|
|
||||||
addrs = append(addrs, addr)
|
|
||||||
}
|
|
||||||
pool.promoteExecutables(addrs)
|
|
||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
||||||
|
// The transaction pool lock must be held.
|
||||||
|
func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) {
|
||||||
|
dirty := newAccountSet(pool.signer)
|
||||||
|
errs := make([]error, len(txs))
|
||||||
|
for i, tx := range txs {
|
||||||
|
replaced, err := pool.add(tx, local)
|
||||||
|
errs[i] = err
|
||||||
|
if err == nil && !replaced {
|
||||||
|
dirty.addTx(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validMeter.Mark(int64(len(dirty.accounts)))
|
||||||
|
return errs, dirty
|
||||||
|
}
|
||||||
|
|
||||||
// Status returns the status (unknown/pending/queued) of a batch of transactions
|
// Status returns the status (unknown/pending/queued) of a batch of transactions
|
||||||
// identified by their hashes.
|
// identified by their hashes.
|
||||||
func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||||
|
|
@ -1065,8 +956,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a transaction if it is contained in the pool
|
// Get returns a transaction if it is contained in the pool and nil otherwise.
|
||||||
// and nil otherwise.
|
|
||||||
func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
|
func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
|
||||||
return pool.all.Get(hash)
|
return pool.all.Get(hash)
|
||||||
}
|
}
|
||||||
|
|
@ -1122,10 +1012,259 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requestPromoteExecutables requests a pool reset to the new head block.
|
||||||
|
// The returned channel is closed when the reset has occurred.
|
||||||
|
func (pool *TxPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} {
|
||||||
|
select {
|
||||||
|
case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}:
|
||||||
|
return <-pool.reorgDoneCh
|
||||||
|
case <-pool.reorgShutdownCh:
|
||||||
|
return pool.reorgShutdownCh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestPromoteExecutables requests transaction promotion checks for the given addresses.
|
||||||
|
// The returned channel is closed when the promotion checks have occurred.
|
||||||
|
func (pool *TxPool) requestPromoteExecutables(set *accountSet) chan struct{} {
|
||||||
|
select {
|
||||||
|
case pool.reqPromoteCh <- set:
|
||||||
|
return <-pool.reorgDoneCh
|
||||||
|
case <-pool.reorgShutdownCh:
|
||||||
|
return pool.reorgShutdownCh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// queueTxEvent enqueues a transaction event to be sent in the next reorg run.
|
||||||
|
func (pool *TxPool) queueTxEvent(tx *types.Transaction) {
|
||||||
|
select {
|
||||||
|
case pool.queueTxEventCh <- tx:
|
||||||
|
case <-pool.reorgShutdownCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not
|
||||||
|
// call those methods directly, but request them being run using requestReset and
|
||||||
|
// requestPromoteExecutables instead.
|
||||||
|
func (pool *TxPool) scheduleReorgLoop() {
|
||||||
|
defer pool.wg.Done()
|
||||||
|
|
||||||
|
var (
|
||||||
|
curDone chan struct{} // non-nil while runReorg is active
|
||||||
|
nextDone = make(chan struct{})
|
||||||
|
launchNextRun bool
|
||||||
|
reset *txpoolResetRequest
|
||||||
|
dirtyAccounts *accountSet
|
||||||
|
queuedEvents = make(map[common.Address]*txSortedMap)
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// Launch next background reorg if needed
|
||||||
|
if curDone == nil && launchNextRun {
|
||||||
|
// Run the background reorg and announcements
|
||||||
|
go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents)
|
||||||
|
|
||||||
|
// Prepare everything for the next round of reorg
|
||||||
|
curDone, nextDone = nextDone, make(chan struct{})
|
||||||
|
launchNextRun = false
|
||||||
|
|
||||||
|
reset, dirtyAccounts = nil, nil
|
||||||
|
queuedEvents = make(map[common.Address]*txSortedMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case req := <-pool.reqResetCh:
|
||||||
|
// Reset request: update head if request is already pending.
|
||||||
|
if reset == nil {
|
||||||
|
reset = req
|
||||||
|
} else {
|
||||||
|
reset.newHead = req.newHead
|
||||||
|
}
|
||||||
|
launchNextRun = true
|
||||||
|
pool.reorgDoneCh <- nextDone
|
||||||
|
|
||||||
|
case req := <-pool.reqPromoteCh:
|
||||||
|
// Promote request: update address set if request is already pending.
|
||||||
|
if dirtyAccounts == nil {
|
||||||
|
dirtyAccounts = req
|
||||||
|
} else {
|
||||||
|
dirtyAccounts.merge(req)
|
||||||
|
}
|
||||||
|
launchNextRun = true
|
||||||
|
pool.reorgDoneCh <- nextDone
|
||||||
|
|
||||||
|
case tx := <-pool.queueTxEventCh:
|
||||||
|
// Queue up the event, but don't schedule a reorg. It's up to the caller to
|
||||||
|
// request one later if they want the events sent.
|
||||||
|
addr, _ := types.Sender(pool.signer, tx)
|
||||||
|
if _, ok := queuedEvents[addr]; !ok {
|
||||||
|
queuedEvents[addr] = newTxSortedMap()
|
||||||
|
}
|
||||||
|
queuedEvents[addr].Put(tx)
|
||||||
|
|
||||||
|
case <-curDone:
|
||||||
|
curDone = nil
|
||||||
|
|
||||||
|
case <-pool.reorgShutdownCh:
|
||||||
|
// Wait for current run to finish.
|
||||||
|
if curDone != nil {
|
||||||
|
<-curDone
|
||||||
|
}
|
||||||
|
close(nextDone)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
|
||||||
|
func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*txSortedMap) {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
var promoteAddrs []common.Address
|
||||||
|
if dirtyAccounts != nil {
|
||||||
|
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.pendingState.GetNonce(addr))
|
||||||
|
if events[addr].Len() == 0 {
|
||||||
|
delete(events, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reset needs promote for all addresses
|
||||||
|
promoteAddrs = promoteAddrs[:0]
|
||||||
|
for addr := range pool.queue {
|
||||||
|
promoteAddrs = append(promoteAddrs, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for pending transactions for every account that sent new ones
|
||||||
|
promoted := pool.promoteExecutables(promoteAddrs)
|
||||||
|
for _, tx := range promoted {
|
||||||
|
addr, _ := types.Sender(pool.signer, tx)
|
||||||
|
if _, ok := events[addr]; !ok {
|
||||||
|
events[addr] = newTxSortedMap()
|
||||||
|
}
|
||||||
|
events[addr].Put(tx)
|
||||||
|
}
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
// Ensure pool.queue and pool.pending sizes stay within the configured limits.
|
||||||
|
pool.truncatePending()
|
||||||
|
pool.truncateQueue()
|
||||||
|
|
||||||
|
// Update all accounts to the latest known pending nonce
|
||||||
|
for addr, list := range pool.pending {
|
||||||
|
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
|
||||||
|
pool.pendingState.SetNonce(addr, txs[len(txs)-1].Nonce()+1)
|
||||||
|
}
|
||||||
|
pool.mu.Unlock()
|
||||||
|
|
||||||
|
// Notify subsystems for newly added transactions
|
||||||
|
if len(events) > 0 {
|
||||||
|
var txs []*types.Transaction
|
||||||
|
for _, set := range events {
|
||||||
|
txs = append(txs, set.Flatten()...)
|
||||||
|
}
|
||||||
|
pool.txFeed.Send(NewTxsEvent{txs})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset retrieves the current state of the blockchain and ensures the content
|
||||||
|
// of the transaction pool is valid with regard to the chain state.
|
||||||
|
func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||||
|
// If we're reorging an old state, reinject all dropped transactions
|
||||||
|
var reinject types.Transactions
|
||||||
|
|
||||||
|
if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
|
||||||
|
// If the reorg is too deep, avoid doing it (will happen during fast sync)
|
||||||
|
oldNum := oldHead.Number.Uint64()
|
||||||
|
newNum := newHead.Number.Uint64()
|
||||||
|
|
||||||
|
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
||||||
|
log.Debug("Skipping deep transaction reorg", "depth", depth)
|
||||||
|
} else {
|
||||||
|
// Reorg seems shallow enough to pull in all transactions into memory
|
||||||
|
var discarded, included types.Transactions
|
||||||
|
var (
|
||||||
|
rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
|
||||||
|
add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
|
||||||
|
)
|
||||||
|
if rem == nil {
|
||||||
|
// This can happen if a setHead is performed, where we simply discard the old
|
||||||
|
// head from the chain.
|
||||||
|
// If that is the case, we don't have the lost transactions any more, and
|
||||||
|
// there's nothing to add
|
||||||
|
if newNum < oldNum {
|
||||||
|
// If the reorg ended up on a lower number, it's indicative of setHead being the cause
|
||||||
|
log.Debug("Skipping transaction reset caused by setHead",
|
||||||
|
"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
|
||||||
|
} else {
|
||||||
|
// If we reorged to a same or higher number, then it's not a case of setHead
|
||||||
|
log.Warn("Transaction pool reset with missing oldhead",
|
||||||
|
"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for rem.NumberU64() > add.NumberU64() {
|
||||||
|
discarded = append(discarded, rem.Transactions()...)
|
||||||
|
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
|
||||||
|
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for add.NumberU64() > rem.NumberU64() {
|
||||||
|
included = append(included, add.Transactions()...)
|
||||||
|
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
|
||||||
|
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for rem.Hash() != add.Hash() {
|
||||||
|
discarded = append(discarded, rem.Transactions()...)
|
||||||
|
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
|
||||||
|
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
included = append(included, add.Transactions()...)
|
||||||
|
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
|
||||||
|
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reinject = types.TxDifference(discarded, included)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Initialize the internal state to the current head
|
||||||
|
if newHead == nil {
|
||||||
|
newHead = pool.chain.CurrentBlock().Header() // 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.trc21FeeCapacity = state.GetTRC21FeeCapacityFromStateWithCache(newHead.Root, statedb)
|
||||||
|
pool.pendingState = state.ManageState(statedb)
|
||||||
|
pool.currentMaxGas = newHead.GasLimit
|
||||||
|
|
||||||
|
// Inject any transactions discarded due to reorgs
|
||||||
|
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||||
|
senderCacher.recover(pool.signer, reinject)
|
||||||
|
pool.addTxsLocked(reinject, false)
|
||||||
|
}
|
||||||
|
|
||||||
// promoteExecutables moves transactions that have become processable from the
|
// promoteExecutables moves transactions that have become processable from the
|
||||||
// future queue to the set of pending transactions. During this process, all
|
// future queue to the set of pending transactions. During this process, all
|
||||||
// invalidated transactions (low nonce, low balance) are deleted.
|
// invalidated transactions (low nonce, low balance) are deleted.
|
||||||
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
|
||||||
log.Debug("start promoteExecutables")
|
log.Debug("start promoteExecutables")
|
||||||
defer func(start time.Time) {
|
defer func(start time.Time) {
|
||||||
log.Debug("end promoteExecutables", "time", common.PrettyDuration(time.Since(start)))
|
log.Debug("end promoteExecutables", "time", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
@ -1134,13 +1273,6 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
// Track the promoted transactions to broadcast them at once
|
// Track the promoted transactions to broadcast them at once
|
||||||
var promoted []*types.Transaction
|
var promoted []*types.Transaction
|
||||||
|
|
||||||
// Gather all the accounts potentially needing updates
|
|
||||||
if accounts == nil {
|
|
||||||
accounts = make([]common.Address, 0, len(pool.queue))
|
|
||||||
for addr := range pool.queue {
|
|
||||||
accounts = append(accounts, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Iterate over all accounts and promote any executable transactions
|
// Iterate over all accounts and promote any executable transactions
|
||||||
for _, addr := range accounts {
|
for _, addr := range accounts {
|
||||||
list := pool.queue[addr]
|
list := pool.queue[addr]
|
||||||
|
|
@ -1200,16 +1332,21 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
delete(pool.queue, addr)
|
delete(pool.queue, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Notify subsystem for new promoted transactions.
|
return promoted
|
||||||
if len(promoted) > 0 {
|
|
||||||
go pool.txFeed.Send(NewTxsEvent{promoted})
|
|
||||||
}
|
}
|
||||||
// If the pending limit is overflown, start equalizing allowances
|
|
||||||
|
// truncatePending removes transactions from the pending queue if the pool is above the
|
||||||
|
// pending limit. The algorithm tries to reduce transaction counts by an approximately
|
||||||
|
// equal number for all for accounts with many pending transactions.
|
||||||
|
func (pool *TxPool) truncatePending() {
|
||||||
pending := uint64(0)
|
pending := uint64(0)
|
||||||
for _, list := range pool.pending {
|
for _, list := range pool.pending {
|
||||||
pending += uint64(list.Len())
|
pending += uint64(list.Len())
|
||||||
}
|
}
|
||||||
if pending > pool.config.GlobalSlots {
|
if pending <= pool.config.GlobalSlots {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pendingBeforeCap := pending
|
pendingBeforeCap := pending
|
||||||
// Assemble a spam order to penalize large transactors first
|
// Assemble a spam order to penalize large transactors first
|
||||||
spammers := prque.New(nil)
|
spammers := prque.New(nil)
|
||||||
|
|
@ -1258,6 +1395,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still above threshold, reduce to limit or min allowance
|
// If still above threshold, reduce to limit or min allowance
|
||||||
if pending > pool.config.GlobalSlots && len(offenders) > 0 {
|
if pending > pool.config.GlobalSlots && len(offenders) > 0 {
|
||||||
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
|
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
|
||||||
|
|
@ -1287,12 +1425,17 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
}
|
}
|
||||||
pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending))
|
pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending))
|
||||||
}
|
}
|
||||||
// If we've queued more transactions than the hard limit, drop oldest ones
|
|
||||||
|
// truncateQueue drops the oldes transactions in the queue if the pool is above the global queue limit.
|
||||||
|
func (pool *TxPool) truncateQueue() {
|
||||||
queued := uint64(0)
|
queued := uint64(0)
|
||||||
for _, list := range pool.queue {
|
for _, list := range pool.queue {
|
||||||
queued += uint64(list.Len())
|
queued += uint64(list.Len())
|
||||||
}
|
}
|
||||||
if queued > pool.config.GlobalQueue {
|
if queued <= pool.config.GlobalQueue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Sort all accounts with queued transactions by heartbeat
|
// Sort all accounts with queued transactions by heartbeat
|
||||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
||||||
for addr := range pool.queue {
|
for addr := range pool.queue {
|
||||||
|
|
@ -1327,7 +1470,6 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// demoteUnexecutables removes invalid and processed transactions from the pools
|
// demoteUnexecutables removes invalid and processed transactions from the pools
|
||||||
// executable/pending queue and any subsequent transactions that become unexecutable
|
// executable/pending queue and any subsequent transactions that become unexecutable
|
||||||
|
|
@ -1375,7 +1517,7 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||||
log.Warn("Demoting invalidated transaction", "hash", hash)
|
log.Warn("Demoting invalidated transaction", "hash", hash)
|
||||||
pool.enqueueTx(hash, tx)
|
pool.enqueueTx(hash, tx)
|
||||||
}
|
}
|
||||||
pendingCounter.Inc(int64(len(gapped)))
|
pendingCounter.Dec(int64(len(gapped)))
|
||||||
}
|
}
|
||||||
// Delete the entire queue entry if it became empty.
|
// Delete the entire queue entry if it became empty.
|
||||||
if list.Empty() {
|
if list.Empty() {
|
||||||
|
|
@ -1407,11 +1549,15 @@ type accountSet struct {
|
||||||
|
|
||||||
// newAccountSet creates a new address set with an associated signer for sender
|
// newAccountSet creates a new address set with an associated signer for sender
|
||||||
// derivations.
|
// derivations.
|
||||||
func newAccountSet(signer types.Signer) *accountSet {
|
func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet {
|
||||||
return &accountSet{
|
as := &accountSet{
|
||||||
accounts: make(map[common.Address]struct{}),
|
accounts: make(map[common.Address]struct{}),
|
||||||
signer: signer,
|
signer: signer,
|
||||||
}
|
}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
as.add(addr)
|
||||||
|
}
|
||||||
|
return as
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains checks if a given address is contained within the set.
|
// contains checks if a given address is contained within the set.
|
||||||
|
|
@ -1435,6 +1581,13 @@ func (as *accountSet) add(addr common.Address) {
|
||||||
as.cache = nil
|
as.cache = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addTx adds the sender of tx into the set.
|
||||||
|
func (as *accountSet) addTx(tx *types.Transaction) {
|
||||||
|
if addr, err := types.Sender(as.signer, tx); err == nil {
|
||||||
|
as.add(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// flatten returns the list of addresses within this set, also caching it for later
|
// flatten returns the list of addresses within this set, also caching it for later
|
||||||
// reuse. The returned slice should not be changed!
|
// reuse. The returned slice should not be changed!
|
||||||
func (as *accountSet) flatten() []common.Address {
|
func (as *accountSet) flatten() []common.Address {
|
||||||
|
|
@ -1448,6 +1601,14 @@ func (as *accountSet) flatten() []common.Address {
|
||||||
return *as.cache
|
return *as.cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// merge adds all addresses from the 'other' set into 'as'.
|
||||||
|
func (as *accountSet) merge(other *accountSet) {
|
||||||
|
for addr := range other.accounts {
|
||||||
|
as.accounts[addr] = struct{}{}
|
||||||
|
}
|
||||||
|
as.cache = nil
|
||||||
|
}
|
||||||
|
|
||||||
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
||||||
// mutex contention.
|
// mutex contention.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||||
t.Fatalf("Invalid nonce, want 0, got %d", nonce)
|
t.Fatalf("Invalid nonce, want 0, got %d", nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.AddRemotes(types.Transactions{tx0, tx1})
|
pool.addRemotesSync([]*types.Transaction{tx0, tx1})
|
||||||
|
|
||||||
nonce = pool.State().GetNonce(address)
|
nonce = pool.State().GetNonce(address)
|
||||||
if nonce != 2 {
|
if nonce != 2 {
|
||||||
|
|
@ -229,8 +229,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||||
|
|
||||||
// trigger state change in the background
|
// trigger state change in the background
|
||||||
trigger = true
|
trigger = true
|
||||||
|
<-pool.requestReset(nil, nil)
|
||||||
pool.lockedReset(nil, nil)
|
|
||||||
|
|
||||||
_, err := pool.Pending()
|
_, err := pool.Pending()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -288,10 +287,10 @@ func TestTransactionQueue(t *testing.T) {
|
||||||
tx := transaction(0, 100, key)
|
tx := transaction(0, 100, key)
|
||||||
from, _ := deriveSender(tx)
|
from, _ := deriveSender(tx)
|
||||||
pool.currentState.AddBalance(from, big.NewInt(1000))
|
pool.currentState.AddBalance(from, big.NewInt(1000))
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
pool.enqueueTx(tx.Hash(), tx)
|
|
||||||
|
|
||||||
pool.promoteExecutables([]common.Address{from})
|
pool.enqueueTx(tx.Hash(), tx)
|
||||||
|
<-pool.requestPromoteExecutables(newAccountSet(pool.signer, from))
|
||||||
if len(pool.pending) != 1 {
|
if len(pool.pending) != 1 {
|
||||||
t.Error("expected valid txs to be 1 is", len(pool.pending))
|
t.Error("expected valid txs to be 1 is", len(pool.pending))
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +299,8 @@ func TestTransactionQueue(t *testing.T) {
|
||||||
from, _ = deriveSender(tx)
|
from, _ = deriveSender(tx)
|
||||||
pool.currentState.SetNonce(from, 2)
|
pool.currentState.SetNonce(from, 2)
|
||||||
pool.enqueueTx(tx.Hash(), tx)
|
pool.enqueueTx(tx.Hash(), tx)
|
||||||
pool.promoteExecutables([]common.Address{from})
|
|
||||||
|
<-pool.requestPromoteExecutables(newAccountSet(pool.signer, from))
|
||||||
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
||||||
t.Error("expected transaction to be in tx pool")
|
t.Error("expected transaction to be in tx pool")
|
||||||
}
|
}
|
||||||
|
|
@ -308,25 +308,28 @@ func TestTransactionQueue(t *testing.T) {
|
||||||
if len(pool.queue) > 0 {
|
if len(pool.queue) > 0 {
|
||||||
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pool, key = setupTxPool()
|
func TestTransactionQueue2(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
tx1 := transaction(0, 100, key)
|
tx1 := transaction(0, 100, key)
|
||||||
tx2 := transaction(10, 100, key)
|
tx2 := transaction(10, 100, key)
|
||||||
tx3 := transaction(11, 100, key)
|
tx3 := transaction(11, 100, key)
|
||||||
from, _ = deriveSender(tx1)
|
from, _ := deriveSender(tx1)
|
||||||
pool.currentState.AddBalance(from, big.NewInt(1000))
|
pool.currentState.AddBalance(from, big.NewInt(1000))
|
||||||
pool.lockedReset(nil, nil)
|
pool.reset(nil, nil)
|
||||||
|
|
||||||
pool.enqueueTx(tx1.Hash(), tx1)
|
pool.enqueueTx(tx1.Hash(), tx1)
|
||||||
pool.enqueueTx(tx2.Hash(), tx2)
|
pool.enqueueTx(tx2.Hash(), tx2)
|
||||||
pool.enqueueTx(tx3.Hash(), tx3)
|
pool.enqueueTx(tx3.Hash(), tx3)
|
||||||
|
|
||||||
pool.promoteExecutables([]common.Address{from})
|
pool.promoteExecutables([]common.Address{from})
|
||||||
|
|
||||||
if len(pool.pending) != 1 {
|
if len(pool.pending) != 1 {
|
||||||
t.Error("expected tx pool to be 1, got", len(pool.pending))
|
t.Error("expected pending length to be 1, got", len(pool.pending))
|
||||||
}
|
}
|
||||||
if pool.queue[from].Len() != 2 {
|
if pool.queue[from].Len() != 2 {
|
||||||
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
||||||
|
|
@ -360,7 +363,7 @@ func TestTransactionChainFork(t *testing.T) {
|
||||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||||
|
|
||||||
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
}
|
}
|
||||||
resetState()
|
resetState()
|
||||||
|
|
||||||
|
|
@ -390,7 +393,7 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||||
|
|
||||||
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
}
|
}
|
||||||
resetState()
|
resetState()
|
||||||
|
|
||||||
|
|
@ -406,16 +409,17 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||||
if replace, err := pool.add(tx2, false); err != nil || !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)
|
t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace)
|
||||||
}
|
}
|
||||||
pool.promoteExecutables([]common.Address{addr})
|
<-pool.requestPromoteExecutables(newAccountSet(signer, addr))
|
||||||
if pool.pending[addr].Len() != 1 {
|
if pool.pending[addr].Len() != 1 {
|
||||||
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
||||||
}
|
}
|
||||||
if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() {
|
if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() {
|
||||||
t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash())
|
t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the third transaction and ensure it's not saved (smaller price)
|
// Add the third transaction and ensure it's not saved (smaller price)
|
||||||
pool.add(tx3, false)
|
pool.add(tx3, false)
|
||||||
pool.promoteExecutables([]common.Address{addr})
|
<-pool.requestPromoteExecutables(newAccountSet(signer, addr))
|
||||||
if pool.pending[addr].Len() != 1 {
|
if pool.pending[addr].Len() != 1 {
|
||||||
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +465,7 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
pool.currentState.SetNonce(addr, n)
|
pool.currentState.SetNonce(addr, n)
|
||||||
pool.currentState.AddBalance(addr, big.NewInt(100000000000000))
|
pool.currentState.AddBalance(addr, big.NewInt(100000000000000))
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
|
|
||||||
tx := transaction(n, 100000, key)
|
tx := transaction(n, 100000, key)
|
||||||
if err := pool.AddRemote(tx); err != nil {
|
if err := pool.AddRemote(tx); err != nil {
|
||||||
|
|
@ -469,7 +473,7 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||||
}
|
}
|
||||||
// simulate some weird re-order of transactions and missing nonce(s)
|
// simulate some weird re-order of transactions and missing nonce(s)
|
||||||
pool.currentState.SetNonce(addr, n-1)
|
pool.currentState.SetNonce(addr, n-1)
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
if fn := pool.pendingState.GetNonce(addr); fn != n-1 {
|
if fn := pool.pendingState.GetNonce(addr); fn != n-1 {
|
||||||
t.Errorf("expected nonce to be %d, got %d", n-1, fn)
|
t.Errorf("expected nonce to be %d, got %d", n-1, fn)
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +517,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||||
if pool.all.Count() != 6 {
|
if pool.all.Count() != 6 {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||||
}
|
}
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
if pool.pending[account].Len() != 3 {
|
if pool.pending[account].Len() != 3 {
|
||||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||||
}
|
}
|
||||||
|
|
@ -525,7 +529,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Reduce the balance of the account, and check that invalidated transactions are dropped
|
// Reduce the balance of the account, and check that invalidated transactions are dropped
|
||||||
pool.currentState.AddBalance(account, big.NewInt(-650))
|
pool.currentState.AddBalance(account, big.NewInt(-650))
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
|
|
||||||
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
||||||
t.Errorf("funded pending transaction missing: %v", tx0)
|
t.Errorf("funded pending transaction missing: %v", tx0)
|
||||||
|
|
@ -550,7 +554,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Reduce the block gas limit, check that invalidated transactions are dropped
|
// Reduce the block gas limit, check that invalidated transactions are dropped
|
||||||
pool.chain.(*testBlockChain).gasLimit = 100
|
pool.chain.(*testBlockChain).gasLimit = 100
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
|
|
||||||
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
||||||
t.Errorf("funded pending transaction missing: %v", tx0)
|
t.Errorf("funded pending transaction missing: %v", tx0)
|
||||||
|
|
@ -607,7 +611,7 @@ func TestTransactionPostponing(t *testing.T) {
|
||||||
txs = append(txs, tx)
|
txs = append(txs, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, err := range pool.AddRemotes(txs) {
|
for i, err := range pool.addRemotesSync(txs) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("tx %d: failed to add transactions: %v", i, err)
|
t.Fatalf("tx %d: failed to add transactions: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
@ -622,7 +626,7 @@ func TestTransactionPostponing(t *testing.T) {
|
||||||
if pool.all.Count() != len(txs) {
|
if pool.all.Count() != len(txs) {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||||
}
|
}
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
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))
|
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||||
}
|
}
|
||||||
|
|
@ -636,7 +640,7 @@ func TestTransactionPostponing(t *testing.T) {
|
||||||
for _, addr := range accs {
|
for _, addr := range accs {
|
||||||
pool.currentState.AddBalance(addr, big.NewInt(-1))
|
pool.currentState.AddBalance(addr, big.NewInt(-1))
|
||||||
}
|
}
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
|
|
||||||
// The first account's first transaction remains valid, check that subsequent
|
// The first account's first transaction remains valid, check that subsequent
|
||||||
// ones are either filtered out, or queued up for later.
|
// ones are either filtered out, or queued up for later.
|
||||||
|
|
@ -703,12 +707,10 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||||
defer sub.Unsubscribe()
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Create a pending and a queued transaction with a nonce-gap in between
|
// Create a pending and a queued transaction with a nonce-gap in between
|
||||||
if err := pool.AddRemote(transaction(0, 100000, key)); err != nil {
|
pool.addRemotesSync([]*types.Transaction{
|
||||||
t.Fatalf("failed to add pending transaction: %v", err)
|
transaction(0, 100000, key),
|
||||||
}
|
transaction(2, 100000, key),
|
||||||
if err := pool.AddRemote(transaction(2, 100000, key)); err != nil {
|
})
|
||||||
t.Fatalf("failed to add queued transaction: %v", err)
|
|
||||||
}
|
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
if pending != 1 {
|
if pending != 1 {
|
||||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
|
||||||
|
|
@ -723,7 +725,7 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
// Fill the nonce gap and ensure all transactions become pending
|
// Fill the nonce gap and ensure all transactions become pending
|
||||||
if err := pool.AddRemote(transaction(1, 100000, key)); err != nil {
|
if err := pool.addRemoteSync(transaction(1, 100000, key)); err != nil {
|
||||||
t.Fatalf("failed to add gapped transaction: %v", err)
|
t.Fatalf("failed to add gapped transaction: %v", err)
|
||||||
}
|
}
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
|
|
@ -755,7 +757,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||||
testTxPoolConfig.AccountQueue = 10
|
testTxPoolConfig.AccountQueue = 10
|
||||||
// Keep queuing up transactions and make sure all above a limit are dropped
|
// Keep queuing up transactions and make sure all above a limit are dropped
|
||||||
for i := uint64(1); i <= testTxPoolConfig.AccountQueue; i++ {
|
for i := uint64(1); i <= testTxPoolConfig.AccountQueue; i++ {
|
||||||
if err := pool.AddRemote(transaction(i, 100000, key)); err != nil {
|
if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil {
|
||||||
t.Fatalf("tx %d: failed to add transaction: %v", i, err)
|
t.Fatalf("tx %d: failed to add transaction: %v", i, err)
|
||||||
}
|
}
|
||||||
if len(pool.pending) != 0 {
|
if len(pool.pending) != 0 {
|
||||||
|
|
@ -824,7 +826,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||||
nonces[addr]++
|
nonces[addr]++
|
||||||
}
|
}
|
||||||
// Import the batch and verify that limits have been enforced
|
// Import the batch and verify that limits have been enforced
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
queued := 0
|
queued := 0
|
||||||
for addr, list := range pool.queue {
|
for addr, list := range pool.queue {
|
||||||
|
|
@ -961,7 +963,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||||
|
|
||||||
// Keep queuing up transactions and make sure all above a limit are dropped
|
// Keep queuing up transactions and make sure all above a limit are dropped
|
||||||
for i := uint64(0); i < testTxPoolConfig.AccountQueue; i++ {
|
for i := uint64(0); i < testTxPoolConfig.AccountQueue; i++ {
|
||||||
if err := pool.AddRemote(transaction(i, 100000, key)); err != nil {
|
if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil {
|
||||||
t.Fatalf("tx %d: failed to add transaction: %v", i, err)
|
t.Fatalf("tx %d: failed to add transaction: %v", i, err)
|
||||||
}
|
}
|
||||||
if pool.pending[account].Len() != int(i)+1 {
|
if pool.pending[account].Len() != int(i)+1 {
|
||||||
|
|
@ -982,59 +984,6 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the transaction limits are enforced the same way irrelevant whether
|
|
||||||
// the transactions are added one by one or in batches.
|
|
||||||
func TestTransactionQueueLimitingEquivalency(t *testing.T) { testTransactionLimitingEquivalency(t, 1) }
|
|
||||||
func TestTransactionPendingLimitingEquivalency(t *testing.T) {
|
|
||||||
testTransactionLimitingEquivalency(t, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Add a batch of transactions to a pool one by one
|
|
||||||
pool1, key1 := setupTxPool()
|
|
||||||
defer pool1.Stop()
|
|
||||||
|
|
||||||
account1, _ := deriveSender(transaction(0, 0, key1))
|
|
||||||
pool1.currentState.AddBalance(account1, big.NewInt(1000000))
|
|
||||||
testTxPoolConfig.AccountQueue = 10
|
|
||||||
for i := uint64(0); i < testTxPoolConfig.AccountQueue; i++ {
|
|
||||||
if err := pool1.AddRemote(transaction(origin+i, 100000, key1)); err != nil {
|
|
||||||
t.Fatalf("tx %d: failed to add transaction: %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add a batch of transactions to a pool in one big batch
|
|
||||||
pool2, key2 := setupTxPool()
|
|
||||||
defer pool2.Stop()
|
|
||||||
|
|
||||||
account2, _ := deriveSender(transaction(0, 0, key2))
|
|
||||||
pool2.currentState.AddBalance(account2, big.NewInt(1000000))
|
|
||||||
|
|
||||||
txs := []*types.Transaction{}
|
|
||||||
for i := uint64(0); i < testTxPoolConfig.AccountQueue; i++ {
|
|
||||||
txs = append(txs, transaction(origin+i, 100000, key2))
|
|
||||||
}
|
|
||||||
pool2.AddRemotes(txs)
|
|
||||||
|
|
||||||
// Ensure the batch optimization honors the same pool mechanics
|
|
||||||
if len(pool1.pending) != len(pool2.pending) {
|
|
||||||
t.Errorf("pending transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.pending), len(pool2.pending))
|
|
||||||
}
|
|
||||||
if len(pool1.queue) != len(pool2.queue) {
|
|
||||||
t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue))
|
|
||||||
}
|
|
||||||
if pool1.all.Count() != pool2.all.Count() {
|
|
||||||
t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", pool1.all.Count(), pool2.all.Count())
|
|
||||||
}
|
|
||||||
if err := validateTxPoolInternals(pool1); err != nil {
|
|
||||||
t.Errorf("pool 1 internal state corrupted: %v", err)
|
|
||||||
}
|
|
||||||
if err := validateTxPoolInternals(pool2); err != nil {
|
|
||||||
t.Errorf("pool 2 internal state corrupted: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if the transaction count belonging to multiple accounts go above
|
// Tests that if the transaction count belonging to multiple accounts go above
|
||||||
// some hard threshold, the higher transactions are dropped to prevent DOS
|
// some hard threshold, the higher transactions are dropped to prevent DOS
|
||||||
// attacks.
|
// attacks.
|
||||||
|
|
@ -1070,7 +1019,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Import the batch and verify that limits have been enforced
|
// Import the batch and verify that limits have been enforced
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
pending := 0
|
pending := 0
|
||||||
for _, list := range pool.pending {
|
for _, list := range pool.pending {
|
||||||
|
|
@ -1152,7 +1101,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Import the batch and verify that limits have been enforced
|
// Import the batch and verify that limits have been enforced
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
for addr, list := range pool.pending {
|
for addr, list := range pool.pending {
|
||||||
if list.Len() != int(config.AccountSlots) {
|
if list.Len() != int(config.AccountSlots) {
|
||||||
|
|
@ -1209,7 +1158,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3])
|
ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3])
|
||||||
|
|
||||||
// Import the batch and that both pending and queued transactions match up
|
// Import the batch and that both pending and queued transactions match up
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
pool.AddLocal(ltx)
|
pool.AddLocal(ltx)
|
||||||
|
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
|
|
@ -1493,7 +1442,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||||
for i := uint64(0); i < config.GlobalSlots; i++ {
|
for i := uint64(0); i < config.GlobalSlots; i++ {
|
||||||
txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0]))
|
txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0]))
|
||||||
}
|
}
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
if pending != int(config.GlobalSlots) {
|
if pending != int(config.GlobalSlots) {
|
||||||
|
|
@ -1509,7 +1458,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap
|
// Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil {
|
||||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
}
|
}
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
|
|
@ -1553,7 +1502,7 @@ func TestTransactionReplacement(t *testing.T) {
|
||||||
price := int64(100)
|
price := int64(100)
|
||||||
threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100
|
threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100
|
||||||
|
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
||||||
|
|
@ -1566,7 +1515,7 @@ func TestTransactionReplacement(t *testing.T) {
|
||||||
t.Fatalf("cheap replacement event firing failed: %v", err)
|
t.Fatalf("cheap replacement event firing failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
|
||||||
|
|
@ -1578,6 +1527,7 @@ func TestTransactionReplacement(t *testing.T) {
|
||||||
if err := validateEvents(events, 2); err != nil {
|
if err := validateEvents(events, 2); err != nil {
|
||||||
t.Fatalf("proper replacement event firing failed: %v", err)
|
t.Fatalf("proper replacement event firing failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
||||||
|
|
@ -1656,7 +1606,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil {
|
if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil {
|
||||||
t.Fatalf("failed to add local transaction: %v", err)
|
t.Fatalf("failed to add local transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil {
|
||||||
t.Fatalf("failed to add remote transaction: %v", err)
|
t.Fatalf("failed to add remote transaction: %v", err)
|
||||||
}
|
}
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
|
|
@ -1694,7 +1644,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
}
|
}
|
||||||
// Bump the nonce temporarily and ensure the newly invalidated transaction is removed
|
// Bump the nonce temporarily and ensure the newly invalidated transaction is removed
|
||||||
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2)
|
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2)
|
||||||
pool.lockedReset(nil, nil)
|
<-pool.requestReset(nil, nil)
|
||||||
time.Sleep(2 * config.Rejournal)
|
time.Sleep(2 * config.Rejournal)
|
||||||
pool.Stop()
|
pool.Stop()
|
||||||
|
|
||||||
|
|
@ -1749,7 +1699,7 @@ func TestTransactionStatusCheck(t *testing.T) {
|
||||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) // Queued only
|
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) // Queued only
|
||||||
|
|
||||||
// Import the transaction and ensure they are correctly added
|
// Import the transaction and ensure they are correctly added
|
||||||
pool.AddRemotes(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
if pending != 2 {
|
if pending != 2 {
|
||||||
|
|
@ -1828,26 +1778,6 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmarks the speed of iterative transaction insertion.
|
|
||||||
func BenchmarkPoolInsert(b *testing.B) {
|
|
||||||
// Generate a batch of transactions to enqueue into the pool
|
|
||||||
pool, key := setupTxPool()
|
|
||||||
defer pool.Stop()
|
|
||||||
|
|
||||||
account, _ := deriveSender(transaction(0, 0, key))
|
|
||||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
|
||||||
|
|
||||||
txs := make(types.Transactions, b.N)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
txs[i] = transaction(uint64(i), 100000, key)
|
|
||||||
}
|
|
||||||
// Benchmark importing the transactions into the queue
|
|
||||||
b.ResetTimer()
|
|
||||||
for _, tx := range txs {
|
|
||||||
pool.AddRemote(tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Benchmarks the speed of batched transaction insertion.
|
// Benchmarks the speed of batched transaction insertion.
|
||||||
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) }
|
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) }
|
||||||
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) }
|
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue