Skip to content

core/txpool/legacypool: fix incorrect error in authority tracker when removing tx #31263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1814,8 +1814,6 @@ func (t *lookup) removeAuthorities(hash common.Hash) {
// Remove tx from tracker.
if i := slices.Index(list, hash); i >= 0 {
list = append(list[:i], list[i+1:]...)
} else {
Copy link
Member

@rjl493456442 rjl493456442 Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are essentially iterate the entire auth maps for every transaction, not sure if it's too expensive.

diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index e959bdb55f..c194411c70 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -637,7 +637,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
 		}
 	}
 	// Authorities cannot conflict with any pending or queued transactions.
-	if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
+	if auths := tx.SetCodeAuthorities(false); len(auths) > 0 {
 		for _, auth := range auths {
 			if pool.pending[auth] != nil || pool.queue[auth] != nil {
 				return ErrAuthorityReserved
@@ -1765,12 +1765,12 @@ func (t *lookup) Remove(hash common.Hash) {
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
-	t.removeAuthorities(hash)
 	tx, ok := t.txs[hash]
 	if !ok {
 		log.Error("No transaction found to be deleted", "hash", hash)
 		return
 	}
+	t.removeAuthorities(hash, tx.SetCodeAuthorities(false))
 	t.slots -= numSlots(tx)
 	slotsGauge.Update(int64(t.slots))
 
@@ -1792,7 +1792,7 @@ func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
 // addAuthorities tracks the supplied tx in relation to each authority it
 // specifies.
 func (t *lookup) addAuthorities(tx *types.Transaction) {
-	for _, addr := range tx.SetCodeAuthorities() {
+	for _, addr := range tx.SetCodeAuthorities(false) {
 		list, ok := t.auths[addr]
 		if !ok {
 			list = []common.Hash{}
@@ -1808,12 +1808,14 @@ func (t *lookup) addAuthorities(tx *types.Transaction) {
 
 // removeAuthorities stops tracking the supplied tx in relation to its
 // authorities.
-func (t *lookup) removeAuthorities(hash common.Hash) {
-	for addr := range t.auths {
+func (t *lookup) removeAuthorities(hash common.Hash, auths []common.Address) {
+	for _, addr := range auths {
 		list := t.auths[addr]
 		// Remove tx from tracker.
 		if i := slices.Index(list, hash); i >= 0 {
 			list = append(list[:i], list[i+1:]...)
+		} else {
+			log.Error("Authority with untracked tx", "addr", addr, "hash", hash)
 		}
 		if len(list) == 0 {
 			// If list is newly empty, delete it entirely.
diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go
index 5cc00785a4..0db18dbf0f 100644
--- a/core/txpool/legacypool/legacypool_test.go
+++ b/core/txpool/legacypool/legacypool_test.go
@@ -241,7 +241,7 @@ func validatePoolInternals(pool *LegacyPool) error {
 	}
 	// Ensure all auths in pool are tracked
 	for _, tx := range pool.all.txs {
-		for _, addr := range tx.SetCodeAuthorities() {
+		for _, addr := range tx.SetCodeAuthorities(false) {
 			list := pool.all.auths[addr]
 			if i := slices.Index(list, tx.Hash()); i < 0 {
 				return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 7df13e04bb..e7f53a33b4 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -484,14 +484,21 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
 }
 
 // SetCodeAuthorities returns a list of each authorization's corresponding authority.
-func (tx *Transaction) SetCodeAuthorities() []common.Address {
+func (tx *Transaction) SetCodeAuthorities(allowDuplicates bool) []common.Address {
 	setcodetx, ok := tx.inner.(*SetCodeTx)
 	if !ok {
 		return nil
 	}
-	auths := make([]common.Address, 0, len(setcodetx.AuthList))
+	var (
+		marks = make(map[common.Address]bool)
+		auths = make([]common.Address, 0, len(setcodetx.AuthList))
+	)
 	for _, auth := range setcodetx.AuthList {
 		if addr, err := auth.Authority(); err == nil {
+			if !allowDuplicates && marks[addr] {
+				continue
+			}
+			marks[addr] = true
 			auths = append(auths, addr)
 		}
 	}

Alternatively we can obtain the auth list from the cached tx.

The question is what if the tx is somehow not founded? I guess the txpool will be very wrong at that point

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a good idea - I modified it slightly to always return a unique list in bcb9836

log.Error("Authority with untracked tx", "addr", addr, "hash", hash)
}
if len(list) == 0 {
// If list is newly empty, delete it entirely.
Expand Down
43 changes: 43 additions & 0 deletions core/txpool/legacypool/legacypool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"
"math/big"
"math/rand"
"slices"
"sync"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -238,6 +239,23 @@ func validatePoolInternals(pool *LegacyPool) error {
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
}
}
// Ensure all auths in pool are tracked
for _, tx := range pool.all.txs {
for _, addr := range tx.SetCodeAuthorities() {
list := pool.all.auths[addr]
if i := slices.Index(list, tx.Hash()); i < 0 {
return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())
}
}
}
// Ensure all auths in pool have an associated tx.
for addr, hashes := range pool.all.auths {
for _, hash := range hashes {
if _, ok := pool.all.txs[hash]; !ok {
return fmt.Errorf("dangling authority, missing originating tx: addr %s, hash %s", addr, hash.Hex())
}
}
}
return nil
}

Expand Down Expand Up @@ -2381,6 +2399,31 @@ func TestSetCodeTransactions(t *testing.T) {
}
},
},
{
name: "remove-hash-from-authority-tracker",
pending: 10,
run: func(name string) {
var keys []*ecdsa.PrivateKey
for i := 0; i < 30; i++ {
key, _ := crypto.GenerateKey()
keys = append(keys, key)
addr := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, addr, big.NewInt(params.Ether))
}
// Create a transactions with 3 unique auths so the lookup's auth map is
// filled with addresses.
for i := 0; i < 30; i += 3 {
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keys[i], []unsignedAuth{{0, keys[i]}, {0, keys[i+1]}, {0, keys[i+2]}})); err != nil {
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
}
}
// Replace one of the transactions with a normal transaction so that the
// original hash is removed from the tracker. The hash should be associated with 3 different authorities.
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keys[0])); err != nil {
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
}
},
},
} {
tt.run(tt.name)
pending, queued := pool.Stats()
Expand Down