From b0b3cf2eeb74e45b373d4708375e899688c7f9c8 Mon Sep 17 00:00:00 2001
From: Martin Holst Swende <martin@swende.se>
Date: Thu, 22 Jun 2017 10:14:31 +0200
Subject: [PATCH 1/2] core: add testcase for txpool

---
 core/tx_pool.go      | 13 +++++++++
 core/tx_pool_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+)

diff --git a/core/tx_pool.go b/core/tx_pool.go
index 04ffa8a988..86a2ed2323 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -301,6 +301,19 @@ func (pool *TxPool) Stats() (int, int) {
 	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
 // number of queued (non-executable) transactions.
 func (pool *TxPool) stats() (int, int) {
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index 94b07170df..d82a8ef5af 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -719,6 +719,12 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
 		txns = append(txns, transaction(origin+i, big.NewInt(100000), key2))
 	}
 	pool2.AddBatch(txns)
+	if err := pool2.validateInternals(); err != nil {
+		t.Error(err)
+	}
+	if err := pool1.validateInternals(); err != nil {
+		t.Error(err)
+	}
 
 	// Ensure the batch optimization honors the same pool mechanics
 	if len(pool1.pending) != len(pool2.pending) {
@@ -769,6 +775,9 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
 	// Import the batch and verify that limits have been enforced
 	pool.AddBatch(txs)
 
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
 	pending := 0
 	for _, list := range pool.pending {
 		pending += list.Len()
@@ -778,6 +787,42 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
 	}
 }
 
+// Tests that if transactions start being capped, transasctions are also removed from 'all'
+func TestTransactionCapClearsFromAll(t *testing.T) {
+	// Reduce the queue limits to shorten test time
+	defer func(old uint64) { DefaultTxPoolConfig.GlobalSlots = old }(DefaultTxPoolConfig.GlobalSlots)
+	DefaultTxPoolConfig.AccountSlots = 2
+	DefaultTxPoolConfig.AccountQueue = 2
+	DefaultTxPoolConfig.GlobalSlots = 8
+
+	// Create the pool to test the limit enforcement with
+	db, _ := ethdb.NewMemDatabase()
+	statedb, _ := state.New(common.Hash{}, db)
+
+	pool := NewTxPool(DefaultTxPoolConfig, params.TestChainConfig, new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) })
+	pool.resetState()
+
+	// Create a number of test accounts and fund them
+	state, _ := pool.currentState()
+
+	key, _ := crypto.GenerateKey()
+	addr := crypto.PubkeyToAddress(key.PublicKey)
+	state.AddBalance(addr, big.NewInt(1000000))
+
+	txs := types.Transactions{}
+	nonce := uint64(0)
+	for j := 0; j < int(DefaultTxPoolConfig.GlobalSlots)*2; j++ {
+		tx := transaction(nonce, big.NewInt(100000), key)
+		txs = append(txs, tx)
+		nonce++
+	}
+	// Import the batch and verify that limits have been enforced
+	pool.AddBatch(txs)
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
+}
+
 // Tests that if the transaction count belonging to multiple accounts go above
 // some hard threshold, if they are under the minimum guaranteed slot count then
 // the transactions are still kept.
@@ -815,6 +860,10 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
 	// Import the batch and verify that limits have been enforced
 	pool.AddBatch(txs)
 
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
+
 	for addr, list := range pool.pending {
 		if list.Len() != int(DefaultTxPoolConfig.AccountSlots) {
 			t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), DefaultTxPoolConfig.AccountSlots)
@@ -860,6 +909,10 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	// Import the batch and that both pending and queued transactions match up
 	pool.AddBatch(txs)
 
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
+
 	pending, queued := pool.stats()
 	if pending != 4 {
 		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4)
@@ -894,6 +947,10 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	if pending, _ = pool.stats(); pending != 3 {
 		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
 	}
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
+
 }
 
 // Tests that when the pool reaches its global transaction limit, underpriced
@@ -937,6 +994,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 
 	// Import the batch and that both pending and queued transactions match up
 	pool.AddBatch(txs)
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
 
 	pending, queued := pool.stats()
 	if pending != 3 {
@@ -980,6 +1040,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 	if queued != 2 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
 	}
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
 }
 
 // Tests that the pool rejects replacement transactions that don't meet the minimum
@@ -1041,6 +1104,9 @@ func TestTransactionReplacement(t *testing.T) {
 	if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
 		t.Fatalf("failed to replace original queued transaction: %v", err)
 	}
+	if err := pool.validateInternals(); err != nil {
+		t.Error(err)
+	}
 }
 
 // Benchmarks the speed of validating the contents of the pending queue of the

From eebde1a2e202e92eee722ff0841cda4bd0257a62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= <peterke@gmail.com>
Date: Thu, 22 Jun 2017 17:01:49 +0300
Subject: [PATCH 2/2] core: ensure transactions correctly drop on pool limiting

---
 core/tx_pool.go      | 118 ++++++++++++++++++++++++-------------
 core/tx_pool_test.go | 135 ++++++++++++++++++++++++++++---------------
 light/txpool.go      |   2 +-
 3 files changed, 167 insertions(+), 88 deletions(-)

diff --git a/core/tx_pool.go b/core/tx_pool.go
index 86a2ed2323..2f3cd1e93d 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -35,16 +35,41 @@ import (
 )
 
 var (
-	// Transaction Pool Errors
-	ErrInvalidSender      = errors.New("invalid sender")
-	ErrNonce              = errors.New("nonce too low")
-	ErrUnderpriced        = errors.New("transaction underpriced")
+	// ErrInvalidSender is returned if the transaction contains an invalid signature.
+	ErrInvalidSender = errors.New("invalid sender")
+
+	// 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")
+
+	// 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")
-	ErrBalance            = errors.New("insufficient balance")
-	ErrInsufficientFunds  = errors.New("insufficient funds for gas * price + value")
-	ErrIntrinsicGas       = errors.New("intrinsic gas too low")
-	ErrGasLimit           = errors.New("exceeds block gas limit")
-	ErrNegativeValue      = errors.New("negative value")
+
+	// 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")
+
+	// 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")
+
+	// 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")
+
+	// ErrNegativeValue is a sanity error to ensure noone is able to specify a
+	// transaction with a 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 (
@@ -54,16 +79,16 @@ var (
 
 var (
 	// Metrics for the pending pool
-	pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard")
-	pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace")
-	pendingRLCounter      = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
-	pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds")   // Dropped due to out-of-funds
+	pendingDiscardCounter   = metrics.NewCounter("txpool/pending/discard")
+	pendingReplaceCounter   = metrics.NewCounter("txpool/pending/replace")
+	pendingRateLimitCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
+	pendingNofundsCounter   = metrics.NewCounter("txpool/pending/nofunds")   // Dropped due to out-of-funds
 
 	// Metrics for the queued pool
-	queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard")
-	queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace")
-	queuedRLCounter      = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
-	queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds")   // Dropped due to out-of-funds
+	queuedDiscardCounter   = metrics.NewCounter("txpool/queued/discard")
+	queuedReplaceCounter   = metrics.NewCounter("txpool/queued/replace")
+	queuedRateLimitCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
+	queuedNofundsCounter   = metrics.NewCounter("txpool/queued/nofunds")   // Dropped due to out-of-funds
 
 	// General tx metrics
 	invalidTxCounter     = metrics.NewCounter("txpool/invalid")
@@ -301,19 +326,6 @@ func (pool *TxPool) Stats() (int, int) {
 	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
 // number of queued (non-executable) transactions.
 func (pool *TxPool) stats() (int, int) {
@@ -387,7 +399,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
 	}
 	// Last but not least check for nonce errors
 	if currentState.GetNonce(from) > tx.Nonce() {
-		return ErrNonce
+		return ErrNonceTooLow
 	}
 
 	// Check the transaction doesn't exceed the current
@@ -408,12 +420,15 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
 	if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
 		return ErrInsufficientFunds
 	}
-
 	intrGas := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
 	if tx.Gas().Cmp(intrGas) < 0 {
 		return ErrIntrinsicGas
 	}
 
+	// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
+	if tx.Size() > 32*1024 {
+		return ErrOversizedData
+	}
 	return nil
 }
 
@@ -651,8 +666,9 @@ func (pool *TxPool) removeTx(hash common.Hash) {
 			}
 			// Update the account nonce if needed
 			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
@@ -709,10 +725,10 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
 		// Drop all transactions over the allowed limit
 		for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
 			hash := tx.Hash()
-			log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
 			delete(pool.all, hash)
 			pool.priced.Removed()
-			queuedRLCounter.Inc(1)
+			queuedRateLimitCounter.Inc(1)
+			log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
 		}
 		queued += uint64(list.Len())
 
@@ -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 i := 0; i < len(offenders)-1; 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--
 					}
 				}
@@ -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 _, addr := range offenders {
 					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--
 				}
 			}
 		}
-		pendingRLCounter.Inc(int64(pendingBeforeCap - pending))
+		pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
 	}
 	// If we've queued more transactions than the hard limit, drop oldest ones
 	if queued > pool.config.GlobalQueue {
@@ -798,7 +836,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
 					pool.removeTx(tx.Hash())
 				}
 				drop -= size
-				queuedRLCounter.Inc(int64(size))
+				queuedRateLimitCounter.Inc(int64(size))
 				continue
 			}
 			// Otherwise drop only last few transactions
@@ -806,7 +844,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.A
 			for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
 				pool.removeTx(txs[i].Hash())
 				drop--
-				queuedRLCounter.Inc(1)
+				queuedRateLimitCounter.Inc(1)
 			}
 		}
 	}
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index d82a8ef5af..4e28522e9f 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -18,6 +18,7 @@ package core
 
 import (
 	"crypto/ecdsa"
+	"fmt"
 	"math/big"
 	"math/rand"
 	"testing"
@@ -52,6 +53,35 @@ func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
 	return newPool, key
 }
 
+// validateTxPoolInternals checks various consistency invariants within the pool.
+func validateTxPoolInternals(pool *TxPool) error {
+	pool.mu.RLock()
+	defer pool.mu.RUnlock()
+
+	// Ensure the total transaction set is consistent with pending + queued
+	pending, queued := pool.stats()
+	if total := len(pool.all); total != pending+queued {
+		return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
+	}
+	if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
+		return fmt.Errorf("total priced transaction count %d != %d pending + %d queued", priced, pending, queued)
+	}
+	// Ensure the next nonce to assign is the correct one
+	for addr, txs := range pool.pending {
+		// Find the last transaction
+		var last uint64
+		for nonce, _ := range txs.txs.items {
+			if last < nonce {
+				last = nonce
+			}
+		}
+		if nonce := pool.pendingState.GetNonce(addr); nonce != last+1 {
+			return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
+		}
+	}
+	return nil
+}
+
 func deriveSender(tx *types.Transaction) (common.Address, error) {
 	return types.Sender(types.HomesteadSigner{}, tx)
 }
@@ -150,8 +180,8 @@ func TestInvalidTransactions(t *testing.T) {
 	currentState.SetNonce(from, 1)
 	currentState.AddBalance(from, big.NewInt(0xffffffffffffff))
 	tx = transaction(0, big.NewInt(100000), key)
-	if err := pool.Add(tx); err != ErrNonce {
-		t.Error("expected", ErrNonce)
+	if err := pool.Add(tx); err != ErrNonceTooLow {
+		t.Error("expected", ErrNonceTooLow)
 	}
 
 	tx = transaction(1, big.NewInt(100000), key)
@@ -218,20 +248,25 @@ func TestTransactionQueue(t *testing.T) {
 
 func TestRemoveTx(t *testing.T) {
 	pool, key := setupTxPool()
-	tx := transaction(0, big.NewInt(100), key)
-	from, _ := deriveSender(tx)
+	addr := crypto.PubkeyToAddress(key.PublicKey)
 	currentState, _ := pool.currentState()
-	currentState.AddBalance(from, big.NewInt(1))
+	currentState.AddBalance(addr, big.NewInt(1))
+
+	tx1 := transaction(0, big.NewInt(100), key)
+	tx2 := transaction(2, big.NewInt(100), key)
+
+	pool.promoteTx(addr, tx1.Hash(), tx1)
+	pool.enqueueTx(tx2.Hash(), tx2)
 
-	pool.enqueueTx(tx.Hash(), tx)
-	pool.promoteTx(from, tx.Hash(), tx)
 	if len(pool.queue) != 1 {
 		t.Error("expected queue to be 1, got", len(pool.queue))
 	}
 	if len(pool.pending) != 1 {
 		t.Error("expected pending to be 1, got", len(pool.pending))
 	}
-	pool.Remove(tx.Hash())
+	pool.Remove(tx1.Hash())
+	pool.Remove(tx2.Hash())
+
 	if len(pool.queue) > 0 {
 		t.Error("expected queue to be 0, got", len(pool.queue))
 	}
@@ -404,10 +439,10 @@ func TestTransactionDropping(t *testing.T) {
 	)
 	pool.promoteTx(account, tx0.Hash(), tx0)
 	pool.promoteTx(account, tx1.Hash(), tx1)
-	pool.promoteTx(account, tx1.Hash(), tx2)
+	pool.promoteTx(account, tx2.Hash(), tx2)
 	pool.enqueueTx(tx10.Hash(), tx10)
 	pool.enqueueTx(tx11.Hash(), tx11)
-	pool.enqueueTx(tx11.Hash(), tx12)
+	pool.enqueueTx(tx12.Hash(), tx12)
 
 	// Check that pre and post validations leave the pool as is
 	if pool.pending[account].Len() != 3 {
@@ -416,8 +451,8 @@ func TestTransactionDropping(t *testing.T) {
 	if pool.queue[account].Len() != 3 {
 		t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
 	}
-	if len(pool.all) != 4 {
-		t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
+	if len(pool.all) != 6 {
+		t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6)
 	}
 	pool.resetState()
 	if pool.pending[account].Len() != 3 {
@@ -426,8 +461,8 @@ func TestTransactionDropping(t *testing.T) {
 	if pool.queue[account].Len() != 3 {
 		t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
 	}
-	if len(pool.all) != 4 {
-		t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
+	if len(pool.all) != 6 {
+		t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6)
 	}
 	// Reduce the balance of the account, and check that invalidated transactions are dropped
 	state.AddBalance(account, big.NewInt(-650))
@@ -719,12 +754,6 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
 		txns = append(txns, transaction(origin+i, big.NewInt(100000), key2))
 	}
 	pool2.AddBatch(txns)
-	if err := pool2.validateInternals(); err != nil {
-		t.Error(err)
-	}
-	if err := pool1.validateInternals(); err != nil {
-		t.Error(err)
-	}
 
 	// Ensure the batch optimization honors the same pool mechanics
 	if len(pool1.pending) != len(pool2.pending) {
@@ -736,6 +765,12 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
 	if len(pool1.all) != len(pool2.all) {
 		t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", len(pool1.all), len(pool2.all))
 	}
+	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
@@ -775,9 +810,6 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
 	// Import the batch and verify that limits have been enforced
 	pool.AddBatch(txs)
 
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
-	}
 	pending := 0
 	for _, list := range pool.pending {
 		pending += list.Len()
@@ -785,12 +817,18 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
 	if pending > int(DefaultTxPoolConfig.GlobalSlots) {
 		t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, DefaultTxPoolConfig.GlobalSlots)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 }
 
 // Tests that if transactions start being capped, transasctions are also removed from 'all'
 func TestTransactionCapClearsFromAll(t *testing.T) {
 	// Reduce the queue limits to shorten test time
+	defer func(old uint64) { DefaultTxPoolConfig.AccountSlots = old }(DefaultTxPoolConfig.AccountSlots)
+	defer func(old uint64) { DefaultTxPoolConfig.AccountQueue = old }(DefaultTxPoolConfig.AccountQueue)
 	defer func(old uint64) { DefaultTxPoolConfig.GlobalSlots = old }(DefaultTxPoolConfig.GlobalSlots)
+
 	DefaultTxPoolConfig.AccountSlots = 2
 	DefaultTxPoolConfig.AccountQueue = 2
 	DefaultTxPoolConfig.GlobalSlots = 8
@@ -810,16 +848,13 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
 	state.AddBalance(addr, big.NewInt(1000000))
 
 	txs := types.Transactions{}
-	nonce := uint64(0)
 	for j := 0; j < int(DefaultTxPoolConfig.GlobalSlots)*2; j++ {
-		tx := transaction(nonce, big.NewInt(100000), key)
-		txs = append(txs, tx)
-		nonce++
+		txs = append(txs, transaction(uint64(j), big.NewInt(100000), key))
 	}
 	// Import the batch and verify that limits have been enforced
 	pool.AddBatch(txs)
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
 	}
 }
 
@@ -860,15 +895,14 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
 	// Import the batch and verify that limits have been enforced
 	pool.AddBatch(txs)
 
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
-	}
-
 	for addr, list := range pool.pending {
 		if list.Len() != int(DefaultTxPoolConfig.AccountSlots) {
 			t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), DefaultTxPoolConfig.AccountSlots)
 		}
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 }
 
 // Tests that setting the transaction pool gas price to a higher value correctly
@@ -909,10 +943,6 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	// Import the batch and that both pending and queued transactions match up
 	pool.AddBatch(txs)
 
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
-	}
-
 	pending, queued := pool.stats()
 	if pending != 4 {
 		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4)
@@ -920,6 +950,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	if queued != 3 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 	// Reprice the pool and check that underpriced transactions get dropped
 	pool.SetGasPrice(big.NewInt(2))
 
@@ -930,6 +963,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	if queued != 3 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 	// Check that we can't add the old transactions back
 	if err := pool.Add(pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[0])); err != ErrUnderpriced {
 		t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
@@ -937,6 +973,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
 		t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 	// However we can add local underpriced transactions
 	tx := pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[2])
 
@@ -947,10 +986,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
 	if pending, _ = pool.stats(); pending != 3 {
 		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
 	}
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
 	}
-
 }
 
 // Tests that when the pool reaches its global transaction limit, underpriced
@@ -994,9 +1032,6 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 
 	// Import the batch and that both pending and queued transactions match up
 	pool.AddBatch(txs)
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
-	}
 
 	pending, queued := pool.stats()
 	if pending != 3 {
@@ -1005,6 +1040,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 	if queued != 1 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 	// Ensure that adding an underpriced transaction on block limit fails
 	if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
 		t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
@@ -1026,6 +1064,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 	if queued != 2 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
 	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 	// Ensure that adding local transactions can push out even higher priced ones
 	tx := pricedTransaction(1, big.NewInt(100000), big.NewInt(0), keys[2])
 
@@ -1040,8 +1081,8 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
 	if queued != 2 {
 		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
 	}
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
 	}
 }
 
@@ -1104,8 +1145,8 @@ func TestTransactionReplacement(t *testing.T) {
 	if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
 		t.Fatalf("failed to replace original queued transaction: %v", err)
 	}
-	if err := pool.validateInternals(); err != nil {
-		t.Error(err)
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
 	}
 }
 
diff --git a/light/txpool.go b/light/txpool.go
index 4461958065..7276874b8a 100644
--- a/light/txpool.go
+++ b/light/txpool.go
@@ -360,7 +360,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
 	currentState := pool.currentState()
 	if n, err := currentState.GetNonce(ctx, from); err == nil {
 		if n > tx.Nonce() {
-			return core.ErrNonce
+			return core.ErrNonceTooLow
 		}
 	} else {
 		return err