|
|
|
@ -63,6 +63,10 @@ var ( |
|
|
|
|
// configured for the transaction pool.
|
|
|
|
|
ErrUnderpriced = errors.New("transaction underpriced") |
|
|
|
|
|
|
|
|
|
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet
|
|
|
|
|
// another remote transaction.
|
|
|
|
|
ErrTxPoolOverflow = errors.New("txpool is full") |
|
|
|
|
|
|
|
|
|
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
|
|
|
|
// with a different one without the required price bump.
|
|
|
|
|
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") |
|
|
|
@ -105,6 +109,7 @@ var ( |
|
|
|
|
validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) |
|
|
|
|
invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) |
|
|
|
|
underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) |
|
|
|
|
overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) |
|
|
|
|
|
|
|
|
|
pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) |
|
|
|
|
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) |
|
|
|
@ -421,7 +426,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { |
|
|
|
|
defer pool.mu.Unlock() |
|
|
|
|
|
|
|
|
|
pool.gasPrice = price |
|
|
|
|
for _, tx := range pool.priced.Cap(price, pool.locals) { |
|
|
|
|
for _, tx := range pool.priced.Cap(price) { |
|
|
|
|
pool.removeTx(tx.Hash(), false) |
|
|
|
|
} |
|
|
|
|
log.Info("Transaction pool price threshold updated", "price", price) |
|
|
|
@ -536,7 +541,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { |
|
|
|
|
return ErrInvalidSender |
|
|
|
|
} |
|
|
|
|
// Drop non-local transactions under our own minimal accepted gas price
|
|
|
|
|
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
|
|
|
|
|
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { |
|
|
|
|
return ErrUnderpriced |
|
|
|
|
} |
|
|
|
@ -575,22 +579,36 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e |
|
|
|
|
knownTxMeter.Mark(1) |
|
|
|
|
return false, ErrAlreadyKnown |
|
|
|
|
} |
|
|
|
|
// Make the local flag. If it's from local source or it's from the network but
|
|
|
|
|
// the sender is marked as local previously, treat it as the local transaction.
|
|
|
|
|
isLocal := local || pool.locals.containsTx(tx) |
|
|
|
|
|
|
|
|
|
// If the transaction fails basic validation, discard it
|
|
|
|
|
if err := pool.validateTx(tx, local); err != nil { |
|
|
|
|
if err := pool.validateTx(tx, isLocal); err != nil { |
|
|
|
|
log.Trace("Discarding invalid transaction", "hash", hash, "err", err) |
|
|
|
|
invalidTxMeter.Mark(1) |
|
|
|
|
return false, err |
|
|
|
|
} |
|
|
|
|
// 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()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { |
|
|
|
|
// If the new transaction is underpriced, don't accept it
|
|
|
|
|
if !local && pool.priced.Underpriced(tx, pool.locals) { |
|
|
|
|
if !isLocal && pool.priced.Underpriced(tx) { |
|
|
|
|
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) |
|
|
|
|
underpricedTxMeter.Mark(1) |
|
|
|
|
return false, ErrUnderpriced |
|
|
|
|
} |
|
|
|
|
// New transaction is better than our worse ones, make room for it
|
|
|
|
|
drop := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), pool.locals) |
|
|
|
|
// New transaction is better than our worse ones, make room for it.
|
|
|
|
|
// If it's a local transaction, forcibly discard all available transactions.
|
|
|
|
|
// Otherwise if we can't make enough room for new one, abort the operation.
|
|
|
|
|
drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) |
|
|
|
|
|
|
|
|
|
// Special case, we still can't make the room for the new remote one.
|
|
|
|
|
if !isLocal && !success { |
|
|
|
|
log.Trace("Discarding overflown transaction", "hash", hash) |
|
|
|
|
overflowedTxMeter.Mark(1) |
|
|
|
|
return false, ErrTxPoolOverflow |
|
|
|
|
} |
|
|
|
|
// Kick out the underpriced remote transactions.
|
|
|
|
|
for _, tx := range drop { |
|
|
|
|
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) |
|
|
|
|
underpricedTxMeter.Mark(1) |
|
|
|
@ -612,8 +630,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e |
|
|
|
|
pool.priced.Removed(1) |
|
|
|
|
pendingReplaceMeter.Mark(1) |
|
|
|
|
} |
|
|
|
|
pool.all.Add(tx) |
|
|
|
|
pool.priced.Put(tx) |
|
|
|
|
pool.all.Add(tx, isLocal) |
|
|
|
|
pool.priced.Put(tx, isLocal) |
|
|
|
|
pool.journalTx(from, tx) |
|
|
|
|
pool.queueTxEvent(tx) |
|
|
|
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) |
|
|
|
@ -623,18 +641,17 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e |
|
|
|
|
return old != nil, nil |
|
|
|
|
} |
|
|
|
|
// New transaction isn't replacing a pending one, push into queue
|
|
|
|
|
replaced, err = pool.enqueueTx(hash, tx) |
|
|
|
|
replaced, err = pool.enqueueTx(hash, tx, isLocal, true) |
|
|
|
|
if err != nil { |
|
|
|
|
return false, err |
|
|
|
|
} |
|
|
|
|
// Mark local addresses and journal local transactions
|
|
|
|
|
if local { |
|
|
|
|
if !pool.locals.contains(from) { |
|
|
|
|
if local && !pool.locals.contains(from) { |
|
|
|
|
log.Info("Setting new local account", "address", from) |
|
|
|
|
pool.locals.add(from) |
|
|
|
|
pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time.
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if local || pool.locals.contains(from) { |
|
|
|
|
if isLocal { |
|
|
|
|
localGauge.Inc(1) |
|
|
|
|
} |
|
|
|
|
pool.journalTx(from, tx) |
|
|
|
@ -646,7 +663,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e |
|
|
|
|
// enqueueTx inserts a new transaction into the non-executable transaction queue.
|
|
|
|
|
//
|
|
|
|
|
// Note, this method assumes the pool lock is held!
|
|
|
|
|
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) { |
|
|
|
|
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { |
|
|
|
|
// Try to insert the transaction into the future queue
|
|
|
|
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
|
|
|
|
if pool.queue[from] == nil { |
|
|
|
@ -667,9 +684,14 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er |
|
|
|
|
// Nothing was replaced, bump the queued counter
|
|
|
|
|
queuedGauge.Inc(1) |
|
|
|
|
} |
|
|
|
|
if pool.all.Get(hash) == nil { |
|
|
|
|
pool.all.Add(tx) |
|
|
|
|
pool.priced.Put(tx) |
|
|
|
|
// If the transaction isn't in lookup set but it's expected to be there,
|
|
|
|
|
// show the error log.
|
|
|
|
|
if pool.all.Get(hash) == nil && !addAll { |
|
|
|
|
log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) |
|
|
|
|
} |
|
|
|
|
if addAll { |
|
|
|
|
pool.all.Add(tx, local) |
|
|
|
|
pool.priced.Put(tx, local) |
|
|
|
|
} |
|
|
|
|
// If we never record the heartbeat, do it right now.
|
|
|
|
|
if _, exist := pool.beats[from]; !exist { |
|
|
|
@ -718,11 +740,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T |
|
|
|
|
// Nothing was replaced, bump the pending counter
|
|
|
|
|
pendingGauge.Inc(1) |
|
|
|
|
} |
|
|
|
|
// Failsafe to work around direct pending inserts (tests)
|
|
|
|
|
if pool.all.Get(hash) == nil { |
|
|
|
|
pool.all.Add(tx) |
|
|
|
|
pool.priced.Put(tx) |
|
|
|
|
} |
|
|
|
|
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
|
|
|
|
pool.pendingNonces.set(addr, tx.Nonce()+1) |
|
|
|
|
|
|
|
|
@ -904,7 +921,8 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { |
|
|
|
|
} |
|
|
|
|
// Postpone any invalidated transactions
|
|
|
|
|
for _, tx := range invalids { |
|
|
|
|
pool.enqueueTx(tx.Hash(), tx) |
|
|
|
|
// 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()) |
|
|
|
@ -1408,7 +1426,9 @@ func (pool *TxPool) demoteUnexecutables() { |
|
|
|
|
for _, tx := range invalids { |
|
|
|
|
hash := tx.Hash() |
|
|
|
|
log.Trace("Demoting pending transaction", "hash", hash) |
|
|
|
|
pool.enqueueTx(hash, tx) |
|
|
|
|
|
|
|
|
|
// Internal shuffle shouldn't touch the lookup set.
|
|
|
|
|
pool.enqueueTx(hash, tx, false, false) |
|
|
|
|
} |
|
|
|
|
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) |
|
|
|
|
if pool.locals.contains(addr) { |
|
|
|
@ -1420,7 +1440,9 @@ func (pool *TxPool) demoteUnexecutables() { |
|
|
|
|
for _, tx := range gapped { |
|
|
|
|
hash := tx.Hash() |
|
|
|
|
log.Error("Demoting invalidated transaction", "hash", hash) |
|
|
|
|
pool.enqueueTx(hash, tx) |
|
|
|
|
|
|
|
|
|
// Internal shuffle shouldn't touch the lookup set.
|
|
|
|
|
pool.enqueueTx(hash, tx, false, false) |
|
|
|
|
} |
|
|
|
|
pendingGauge.Dec(int64(len(gapped))) |
|
|
|
|
// This might happen in a reorg, so log it to the metering
|
|
|
|
@ -1519,8 +1541,8 @@ func (as *accountSet) merge(other *accountSet) { |
|
|
|
|
as.cache = nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
|
|
|
|
// mutex contention.
|
|
|
|
|
// txLookup is used internally by TxPool to track transactions while allowing
|
|
|
|
|
// lookup without mutex contention.
|
|
|
|
|
//
|
|
|
|
|
// Note, although this type is properly protected against concurrent access, it
|
|
|
|
|
// is **not** a type that should ever be mutated or even exposed outside of the
|
|
|
|
@ -1528,27 +1550,43 @@ func (as *accountSet) merge(other *accountSet) { |
|
|
|
|
// internal mechanisms. The sole purpose of the type is to permit out-of-bound
|
|
|
|
|
// peeking into the pool in TxPool.Get without having to acquire the widely scoped
|
|
|
|
|
// TxPool.mu mutex.
|
|
|
|
|
//
|
|
|
|
|
// This lookup set combines the notion of "local transactions", which is useful
|
|
|
|
|
// to build upper-level structure.
|
|
|
|
|
type txLookup struct { |
|
|
|
|
all map[common.Hash]*types.Transaction |
|
|
|
|
slots int |
|
|
|
|
lock sync.RWMutex |
|
|
|
|
locals map[common.Hash]*types.Transaction |
|
|
|
|
remotes map[common.Hash]*types.Transaction |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newTxLookup returns a new txLookup structure.
|
|
|
|
|
func newTxLookup() *txLookup { |
|
|
|
|
return &txLookup{ |
|
|
|
|
all: make(map[common.Hash]*types.Transaction), |
|
|
|
|
locals: make(map[common.Hash]*types.Transaction), |
|
|
|
|
remotes: make(map[common.Hash]*types.Transaction), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Range calls f on each key and value present in the map.
|
|
|
|
|
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) { |
|
|
|
|
// Range calls f on each key and value present in the map. The callback passed
|
|
|
|
|
// should return the indicator whether the iteration needs to be continued.
|
|
|
|
|
// Callers need to specify which set (or both) to be iterated.
|
|
|
|
|
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
for key, value := range t.all { |
|
|
|
|
if !f(key, value) { |
|
|
|
|
break |
|
|
|
|
if local { |
|
|
|
|
for key, value := range t.locals { |
|
|
|
|
if !f(key, value, true) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if remote { |
|
|
|
|
for key, value := range t.remotes { |
|
|
|
|
if !f(key, value, false) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -1558,15 +1596,50 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return t.all[hash] |
|
|
|
|
if tx := t.locals[hash]; tx != nil { |
|
|
|
|
return tx |
|
|
|
|
} |
|
|
|
|
return t.remotes[hash] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetLocal returns a transaction if it exists in the lookup, or nil if not found.
|
|
|
|
|
func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return t.locals[hash] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetRemote returns a transaction if it exists in the lookup, or nil if not found.
|
|
|
|
|
func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return t.remotes[hash] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Count returns the current number of items in the lookup.
|
|
|
|
|
// Count returns the current number of transactions in the lookup.
|
|
|
|
|
func (t *txLookup) Count() int { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return len(t.all) |
|
|
|
|
return len(t.locals) + len(t.remotes) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// LocalCount returns the current number of local transactions in the lookup.
|
|
|
|
|
func (t *txLookup) LocalCount() int { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return len(t.locals) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// RemoteCount returns the current number of remote transactions in the lookup.
|
|
|
|
|
func (t *txLookup) RemoteCount() int { |
|
|
|
|
t.lock.RLock() |
|
|
|
|
defer t.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
return len(t.remotes) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Slots returns the current number of slots used in the lookup.
|
|
|
|
@ -1578,14 +1651,18 @@ func (t *txLookup) Slots() int { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Add adds a transaction to the lookup.
|
|
|
|
|
func (t *txLookup) Add(tx *types.Transaction) { |
|
|
|
|
func (t *txLookup) Add(tx *types.Transaction, local bool) { |
|
|
|
|
t.lock.Lock() |
|
|
|
|
defer t.lock.Unlock() |
|
|
|
|
|
|
|
|
|
t.slots += numSlots(tx) |
|
|
|
|
slotsGauge.Update(int64(t.slots)) |
|
|
|
|
|
|
|
|
|
t.all[tx.Hash()] = tx |
|
|
|
|
if local { |
|
|
|
|
t.locals[tx.Hash()] = tx |
|
|
|
|
} else { |
|
|
|
|
t.remotes[tx.Hash()] = tx |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Remove removes a transaction from the lookup.
|
|
|
|
@ -1593,10 +1670,36 @@ func (t *txLookup) Remove(hash common.Hash) { |
|
|
|
|
t.lock.Lock() |
|
|
|
|
defer t.lock.Unlock() |
|
|
|
|
|
|
|
|
|
t.slots -= numSlots(t.all[hash]) |
|
|
|
|
tx, ok := t.locals[hash] |
|
|
|
|
if !ok { |
|
|
|
|
tx, ok = t.remotes[hash] |
|
|
|
|
} |
|
|
|
|
if !ok { |
|
|
|
|
log.Error("No transaction found to be deleted", "hash", hash) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
t.slots -= numSlots(tx) |
|
|
|
|
slotsGauge.Update(int64(t.slots)) |
|
|
|
|
|
|
|
|
|
delete(t.all, hash) |
|
|
|
|
delete(t.locals, hash) |
|
|
|
|
delete(t.remotes, hash) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// RemoteToLocals migrates the transactions belongs to the given locals to locals
|
|
|
|
|
// set. The assumption is held the locals set is thread-safe to be used.
|
|
|
|
|
func (t *txLookup) RemoteToLocals(locals *accountSet) int { |
|
|
|
|
t.lock.Lock() |
|
|
|
|
defer t.lock.Unlock() |
|
|
|
|
|
|
|
|
|
var migrated int |
|
|
|
|
for hash, tx := range t.remotes { |
|
|
|
|
if locals.containsTx(tx) { |
|
|
|
|
t.locals[hash] = tx |
|
|
|
|
delete(t.remotes, hash) |
|
|
|
|
migrated += 1 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return migrated |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// numSlots calculates the number of slots needed for a single transaction.
|
|
|
|
|