Skip to content

Commit 7b32d2a

Browse files
authored
Merge pull request #20085 from karalabe/txpool-api-fix
core: fix tx dedup return error count
2 parents 0ac9bbb + f40ff23 commit 7b32d2a

File tree

2 files changed

+90
-6
lines changed

2 files changed

+90
-6
lines changed

core/tx_pool.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -766,21 +766,40 @@ func (pool *TxPool) AddRemote(tx *types.Transaction) error {
766766
// addTxs attempts to queue a batch of transactions if they are valid.
767767
func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
768768
// Filter out known ones without obtaining the pool lock or recovering signatures
769-
for i := 0; i < len(txs); i++ {
770-
if pool.all.Get(txs[i].Hash()) != nil {
769+
var (
770+
errs = make([]error, len(txs))
771+
news = make([]*types.Transaction, 0, len(txs))
772+
)
773+
for i, tx := range txs {
774+
// If the transaction is known, pre-set the error slot
775+
if pool.all.Get(tx.Hash()) != nil {
776+
errs[i] = fmt.Errorf("known transaction: %x", tx.Hash())
771777
knownTxMeter.Mark(1)
772-
txs = append(txs[:i], txs[i+1:]...)
773-
i--
778+
continue
774779
}
780+
// Accumulate all unknown transactions for deeper processing
781+
news = append(news, tx)
782+
}
783+
if len(news) == 0 {
784+
return errs
775785
}
776786
// Cache senders in transactions before obtaining lock (pool.signer is immutable)
777-
for _, tx := range txs {
787+
for _, tx := range news {
778788
types.Sender(pool.signer, tx)
779789
}
790+
// Process all the new transaction and merge any errors into the original slice
780791
pool.mu.Lock()
781-
errs, dirtyAddrs := pool.addTxsLocked(txs, local)
792+
newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
782793
pool.mu.Unlock()
783794

795+
var nilSlot = 0
796+
for _, err := range newErrs {
797+
for errs[nilSlot] != nil {
798+
nilSlot++
799+
}
800+
errs[nilSlot] = err
801+
}
802+
// Reorg the pool internals if needed and return
784803
done := pool.requestPromoteExecutables(dirtyAddrs)
785804
if sync {
786805
<-done

core/tx_pool_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,71 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
14381438
}
14391439
}
14401440

1441+
// Tests that the pool rejects duplicate transactions.
1442+
func TestTransactionDeduplication(t *testing.T) {
1443+
t.Parallel()
1444+
1445+
// Create the pool to test the pricing enforcement with
1446+
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
1447+
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
1448+
1449+
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
1450+
defer pool.Stop()
1451+
1452+
// Create a test account to add transactions with
1453+
key, _ := crypto.GenerateKey()
1454+
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
1455+
1456+
// Create a batch of transactions and add a few of them
1457+
txs := make([]*types.Transaction, 16)
1458+
for i := 0; i < len(txs); i++ {
1459+
txs[i] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key)
1460+
}
1461+
var firsts []*types.Transaction
1462+
for i := 0; i < len(txs); i += 2 {
1463+
firsts = append(firsts, txs[i])
1464+
}
1465+
errs := pool.AddRemotesSync(firsts)
1466+
if len(errs) != len(firsts) {
1467+
t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts))
1468+
}
1469+
for i, err := range errs {
1470+
if err != nil {
1471+
t.Errorf("add %d failed: %v", i, err)
1472+
}
1473+
}
1474+
pending, queued := pool.Stats()
1475+
if pending != 1 {
1476+
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
1477+
}
1478+
if queued != len(txs)/2-1 {
1479+
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1)
1480+
}
1481+
// Try to add all of them now and ensure previous ones error out as knowns
1482+
errs = pool.AddRemotesSync(txs)
1483+
if len(errs) != len(txs) {
1484+
t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs))
1485+
}
1486+
for i, err := range errs {
1487+
if i%2 == 0 && err == nil {
1488+
t.Errorf("add %d succeeded, should have failed as known", i)
1489+
}
1490+
if i%2 == 1 && err != nil {
1491+
t.Errorf("add %d failed: %v", i, err)
1492+
}
1493+
}
1494+
pending, queued = pool.Stats()
1495+
if pending != len(txs) {
1496+
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs))
1497+
}
1498+
if queued != 0 {
1499+
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
1500+
}
1501+
if err := validateTxPoolInternals(pool); err != nil {
1502+
t.Fatalf("pool internal state corrupted: %v", err)
1503+
}
1504+
}
1505+
14411506
// Tests that the pool rejects replacement transactions that don't meet the minimum
14421507
// price bump required.
14431508
func TestTransactionReplacement(t *testing.T) {

0 commit comments

Comments
 (0)