core: ensure transactions correctly drop on pool limiting

parent b0b3cf2e
...@@ -35,16 +35,41 @@ import ( ...@@ -35,16 +35,41 @@ import (
) )
var ( var (
// Transaction Pool Errors // ErrInvalidSender is returned if the transaction contains an invalid signature.
ErrInvalidSender = errors.New("invalid sender") ErrInvalidSender = errors.New("invalid sender")
ErrNonce = errors.New("nonce too low")
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
// one present in the local chain.
ErrNonceTooLow = errors.New("nonce too low")
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
// configured for the transaction pool.
ErrUnderpriced = errors.New("transaction underpriced") ErrUnderpriced = errors.New("transaction underpriced")
// 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") ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
ErrBalance = errors.New("insufficient balance")
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
// than required to start the invocation.
ErrIntrinsicGas = errors.New("intrinsic gas too low") ErrIntrinsicGas = errors.New("intrinsic gas too low")
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
// maximum allowance of the current block.
ErrGasLimit = errors.New("exceeds block gas limit") ErrGasLimit = errors.New("exceeds block gas limit")
// ErrNegativeValue is a sanity error to ensure noone is able to specify a
// transaction with a negative value.
ErrNegativeValue = errors.New("negative value") ErrNegativeValue = errors.New("negative value")
// ErrOversizedData is returned if the input data of a transaction is greater
// than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")
) )
var ( var (
...@@ -56,13 +81,13 @@ var ( ...@@ -56,13 +81,13 @@ var (
// Metrics for the pending pool // Metrics for the pending pool
pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard") pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard")
pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace") pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace")
pendingRLCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting pendingRateLimitCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds
// Metrics for the queued pool // Metrics for the queued pool
queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard") queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard")
queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace") queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace")
queuedRLCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting queuedRateLimitCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
// General tx metrics // General tx metrics
...@@ -301,19 +326,6 @@ func (pool *TxPool) Stats() (int, int) { ...@@ -301,19 +326,6 @@ func (pool *TxPool) Stats() (int, int) {
return pool.stats() return pool.stats()
} }
// validateInternals checks if the content in pool.all
// is consistent with the numbers reported in pending and queued
func (pool *TxPool) validateInternals() error {
pool.mu.RLock()
defer pool.mu.RUnlock()
p, q := pool.stats()
a := len(pool.all)
if a != p+q {
return fmt.Errorf("Pool.all size %d != %d pending + %d queued", a, p, q)
}
return nil
}
// stats retrieves the current pool stats, namely the number of pending and the // stats retrieves the current pool stats, namely the number of pending and the
// number of queued (non-executable) transactions. // number of queued (non-executable) transactions.
func (pool *TxPool) stats() (int, int) { func (pool *TxPool) stats() (int, int) {
...@@ -387,7 +399,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { ...@@ -387,7 +399,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
} }
// Last but not least check for nonce errors // Last but not least check for nonce errors
if currentState.GetNonce(from) > tx.Nonce() { if currentState.GetNonce(from) > tx.Nonce() {
return ErrNonce return ErrNonceTooLow
} }
// Check the transaction doesn't exceed the current // Check the transaction doesn't exceed the current
...@@ -408,12 +420,15 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { ...@@ -408,12 +420,15 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds return ErrInsufficientFunds
} }
intrGas := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead) intrGas := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
if tx.Gas().Cmp(intrGas) < 0 { if tx.Gas().Cmp(intrGas) < 0 {
return ErrIntrinsicGas return ErrIntrinsicGas
} }
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
}
return nil return nil
} }
...@@ -651,8 +666,9 @@ func (pool *TxPool) removeTx(hash common.Hash) { ...@@ -651,8 +666,9 @@ func (pool *TxPool) removeTx(hash common.Hash) {
} }
// Update the account nonce if needed // Update the account nonce if needed
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce { if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, tx.Nonce()) pool.pendingState.SetNonce(addr, nonce)
} }
return
} }
} }
// Transaction is in the future queue // Transaction is in the future queue
...@@ -709,10 +725,10 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A ...@@ -709,10 +725,10 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
// Drop all transactions over the allowed limit // Drop all transactions over the allowed limit
for _, tx := range list.Cap(int(pool.config.AccountQueue)) { for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
delete(pool.all, hash) delete(pool.all, hash)
pool.priced.Removed() pool.priced.Removed()
queuedRLCounter.Inc(1) queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
} }
queued += uint64(list.Len()) queued += uint64(list.Len())
...@@ -758,7 +774,18 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A ...@@ -758,7 +774,18 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold { for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
for i := 0; i < len(offenders)-1; i++ { for i := 0; i < len(offenders)-1; i++ {
list := pool.pending[offenders[i]] list := pool.pending[offenders[i]]
list.Cap(list.Len() - 1) for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
pool.pendingState.SetNonce(offenders[i], nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending-- pending--
} }
} }
...@@ -769,12 +796,23 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A ...@@ -769,12 +796,23 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
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 {
for _, addr := range offenders { for _, addr := range offenders {
list := pool.pending[addr] list := pool.pending[addr]
list.Cap(list.Len() - 1) for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending-- pending--
} }
} }
} }
pendingRLCounter.Inc(int64(pendingBeforeCap - pending)) pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
} }
// If we've queued more transactions than the hard limit, drop oldest ones // If we've queued more transactions than the hard limit, drop oldest ones
if queued > pool.config.GlobalQueue { if queued > pool.config.GlobalQueue {
...@@ -798,7 +836,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A ...@@ -798,7 +836,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
pool.removeTx(tx.Hash()) pool.removeTx(tx.Hash())
} }
drop -= size drop -= size
queuedRLCounter.Inc(int64(size)) queuedRateLimitCounter.Inc(int64(size))
continue continue
} }
// Otherwise drop only last few transactions // Otherwise drop only last few transactions
...@@ -806,7 +844,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A ...@@ -806,7 +844,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
for i := len(txs) - 1; i >= 0 && drop > 0; i-- { for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
pool.removeTx(txs[i].Hash()) pool.removeTx(txs[i].Hash())
drop-- drop--
queuedRLCounter.Inc(1) queuedRateLimitCounter.Inc(1)
} }
} }
} }
......
This diff is collapsed.
...@@ -360,7 +360,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error ...@@ -360,7 +360,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
currentState := pool.currentState() currentState := pool.currentState()
if n, err := currentState.GetNonce(ctx, from); err == nil { if n, err := currentState.GetNonce(ctx, from); err == nil {
if n > tx.Nonce() { if n > tx.Nonce() {
return core.ErrNonce return core.ErrNonceTooLow
} }
} else { } else {
return err return err
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment