Skip to content

Commit eebde1a

Browse files
committed
core: ensure transactions correctly drop on pool limiting
1 parent b0b3cf2 commit eebde1a

File tree

3 files changed

+167
-88
lines changed

3 files changed

+167
-88
lines changed

core/tx_pool.go

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,41 @@ import (
3535
)
3636

3737
var (
38-
// Transaction Pool Errors
39-
ErrInvalidSender = errors.New("invalid sender")
40-
ErrNonce = errors.New("nonce too low")
41-
ErrUnderpriced = errors.New("transaction underpriced")
38+
// ErrInvalidSender is returned if the transaction contains an invalid signature.
39+
ErrInvalidSender = errors.New("invalid sender")
40+
41+
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
42+
// one present in the local chain.
43+
ErrNonceTooLow = errors.New("nonce too low")
44+
45+
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
46+
// configured for the transaction pool.
47+
ErrUnderpriced = errors.New("transaction underpriced")
48+
49+
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
50+
// with a different one without the required price bump.
4251
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
43-
ErrBalance = errors.New("insufficient balance")
44-
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
45-
ErrIntrinsicGas = errors.New("intrinsic gas too low")
46-
ErrGasLimit = errors.New("exceeds block gas limit")
47-
ErrNegativeValue = errors.New("negative value")
52+
53+
// ErrInsufficientFunds is returned if the total cost of executing a transaction
54+
// is higher than the balance of the user's account.
55+
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
56+
57+
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
58+
// than required to start the invocation.
59+
ErrIntrinsicGas = errors.New("intrinsic gas too low")
60+
61+
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
62+
// maximum allowance of the current block.
63+
ErrGasLimit = errors.New("exceeds block gas limit")
64+
65+
// ErrNegativeValue is a sanity error to ensure noone is able to specify a
66+
// transaction with a negative value.
67+
ErrNegativeValue = errors.New("negative value")
68+
69+
// ErrOversizedData is returned if the input data of a transaction is greater
70+
// than some meaningful limit a user might use. This is not a consensus error
71+
// making the transaction invalid, rather a DOS protection.
72+
ErrOversizedData = errors.New("oversized data")
4873
)
4974

5075
var (
@@ -54,16 +79,16 @@ var (
5479

5580
var (
5681
// Metrics for the pending pool
57-
pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard")
58-
pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace")
59-
pendingRLCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
60-
pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds
82+
pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard")
83+
pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace")
84+
pendingRateLimitCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
85+
pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds
6186

6287
// Metrics for the queued pool
63-
queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard")
64-
queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace")
65-
queuedRLCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
66-
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
88+
queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard")
89+
queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace")
90+
queuedRateLimitCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
91+
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
6792

6893
// General tx metrics
6994
invalidTxCounter = metrics.NewCounter("txpool/invalid")
@@ -301,19 +326,6 @@ func (pool *TxPool) Stats() (int, int) {
301326
return pool.stats()
302327
}
303328

304-
// validateInternals checks if the content in pool.all
305-
// is consistent with the numbers reported in pending and queued
306-
func (pool *TxPool) validateInternals() error {
307-
pool.mu.RLock()
308-
defer pool.mu.RUnlock()
309-
p, q := pool.stats()
310-
a := len(pool.all)
311-
if a != p+q {
312-
return fmt.Errorf("Pool.all size %d != %d pending + %d queued", a, p, q)
313-
}
314-
return nil
315-
}
316-
317329
// stats retrieves the current pool stats, namely the number of pending and the
318330
// number of queued (non-executable) transactions.
319331
func (pool *TxPool) stats() (int, int) {
@@ -387,7 +399,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
387399
}
388400
// Last but not least check for nonce errors
389401
if currentState.GetNonce(from) > tx.Nonce() {
390-
return ErrNonce
402+
return ErrNonceTooLow
391403
}
392404

393405
// Check the transaction doesn't exceed the current
@@ -408,12 +420,15 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
408420
if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
409421
return ErrInsufficientFunds
410422
}
411-
412423
intrGas := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
413424
if tx.Gas().Cmp(intrGas) < 0 {
414425
return ErrIntrinsicGas
415426
}
416427

428+
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
429+
if tx.Size() > 32*1024 {
430+
return ErrOversizedData
431+
}
417432
return nil
418433
}
419434

@@ -651,8 +666,9 @@ func (pool *TxPool) removeTx(hash common.Hash) {
651666
}
652667
// Update the account nonce if needed
653668
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
654-
pool.pendingState.SetNonce(addr, tx.Nonce())
669+
pool.pendingState.SetNonce(addr, nonce)
655670
}
671+
return
656672
}
657673
}
658674
// Transaction is in the future queue
@@ -709,10 +725,10 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
709725
// Drop all transactions over the allowed limit
710726
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
711727
hash := tx.Hash()
712-
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
713728
delete(pool.all, hash)
714729
pool.priced.Removed()
715-
queuedRLCounter.Inc(1)
730+
queuedRateLimitCounter.Inc(1)
731+
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
716732
}
717733
queued += uint64(list.Len())
718734

@@ -758,7 +774,18 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
758774
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
759775
for i := 0; i < len(offenders)-1; i++ {
760776
list := pool.pending[offenders[i]]
761-
list.Cap(list.Len() - 1)
777+
for _, tx := range list.Cap(list.Len() - 1) {
778+
// Drop the transaction from the global pools too
779+
hash := tx.Hash()
780+
delete(pool.all, hash)
781+
pool.priced.Removed()
782+
783+
// Update the account nonce to the dropped transaction
784+
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
785+
pool.pendingState.SetNonce(offenders[i], nonce)
786+
}
787+
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
788+
}
762789
pending--
763790
}
764791
}
@@ -769,12 +796,23 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
769796
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
770797
for _, addr := range offenders {
771798
list := pool.pending[addr]
772-
list.Cap(list.Len() - 1)
799+
for _, tx := range list.Cap(list.Len() - 1) {
800+
// Drop the transaction from the global pools too
801+
hash := tx.Hash()
802+
delete(pool.all, hash)
803+
pool.priced.Removed()
804+
805+
// Update the account nonce to the dropped transaction
806+
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
807+
pool.pendingState.SetNonce(addr, nonce)
808+
}
809+
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
810+
}
773811
pending--
774812
}
775813
}
776814
}
777-
pendingRLCounter.Inc(int64(pendingBeforeCap - pending))
815+
pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
778816
}
779817
// If we've queued more transactions than the hard limit, drop oldest ones
780818
if queued > pool.config.GlobalQueue {
@@ -798,15 +836,15 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
798836
pool.removeTx(tx.Hash())
799837
}
800838
drop -= size
801-
queuedRLCounter.Inc(int64(size))
839+
queuedRateLimitCounter.Inc(int64(size))
802840
continue
803841
}
804842
// Otherwise drop only last few transactions
805843
txs := list.Flatten()
806844
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
807845
pool.removeTx(txs[i].Hash())
808846
drop--
809-
queuedRLCounter.Inc(1)
847+
queuedRateLimitCounter.Inc(1)
810848
}
811849
}
812850
}

0 commit comments

Comments
 (0)