Skip to content

Commit

Permalink
core: fix tx dedup return error count (ethereum#20085)
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe authored and gzliudan committed May 13, 2024
1 parent bef2c34 commit d141583
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 6 deletions.
31 changes: 25 additions & 6 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,21 +902,40 @@ func (pool *TxPool) AddRemote(tx *types.Transaction) error {
// addTxs attempts to queue a batch of transactions if they are valid.
func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
// Filter out known ones without obtaining the pool lock or recovering signatures
for i := 0; i < len(txs); i++ {
if pool.all.Get(txs[i].Hash()) != nil {
var (
errs = make([]error, len(txs))
news = make([]*types.Transaction, 0, len(txs))
)
for i, tx := range txs {
// If the transaction is known, pre-set the error slot
if pool.all.Get(tx.Hash()) != nil {
errs[i] = fmt.Errorf("known transaction: %x", tx.Hash())
knownTxMeter.Mark(1)
txs = append(txs[:i], txs[i+1:]...)
i--
continue
}
// Accumulate all unknown transactions for deeper processing
news = append(news, tx)
}
if len(news) == 0 {
return errs
}
// Cache senders in transactions before obtaining lock (pool.signer is immutable)
for _, tx := range txs {
for _, tx := range news {
types.Sender(pool.signer, tx)
}
// Process all the new transaction and merge any errors into the original slice
pool.mu.Lock()
errs, dirtyAddrs := pool.addTxsLocked(txs, local)
newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
pool.mu.Unlock()

var nilSlot = 0
for _, err := range newErrs {
for errs[nilSlot] != nil {
nilSlot++
}
errs[nilSlot] = err
}
// Reorg the pool internals if needed and return
done := pool.requestPromoteExecutables(dirtyAddrs)
if sync {
<-done
Expand Down
65 changes: 65 additions & 0 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,71 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
}
}

// Tests that the pool rejects duplicate transactions.
func TestTransactionDeduplication(t *testing.T) {
t.Parallel()

// Create the pool to test the pricing enforcement with
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}

pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
defer pool.Stop()

// Create a test account to add transactions with
key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))

// Create a batch of transactions and add a few of them
txs := make([]*types.Transaction, common.LimitThresholdNonceInQueue)
for i := 0; i < len(txs); i++ {
txs[i] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key)
}
var firsts []*types.Transaction
for i := 0; i < len(txs); i += 2 {
firsts = append(firsts, txs[i])
}
errs := pool.AddRemotesSync(firsts)
if len(errs) != len(firsts) {
t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts))
}
for i, err := range errs {
if err != nil {
t.Errorf("add %d failed: %v", i, err)
}
}
pending, queued := pool.Stats()
if pending != 1 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
}
if queued != len(txs)/2-1 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1)
}
// Try to add all of them now and ensure previous ones error out as knowns
errs = pool.AddRemotesSync(txs)
if len(errs) != len(txs) {
t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs))
}
for i, err := range errs {
if i%2 == 0 && err == nil {
t.Errorf("add %d succeeded, should have failed as known", i)
}
if i%2 == 1 && err != nil {
t.Errorf("add %d failed: %v", i, err)
}
}
pending, queued = pool.Stats()
if pending != len(txs) {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs))
}
if queued != 0 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
}
if err := validateTxPoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}
}

// Tests that the pool rejects replacement transactions that don't meet the minimum
// price bump required.
func TestTransactionReplacement(t *testing.T) {
Expand Down

0 comments on commit d141583

Please sign in to comment.