core/txpool/legacypool: update the patch and adjust test coverage

This commit is contained in:
qkrehdus 2026-03-16 17:17:23 +09:00
parent 27b3a6087e
commit 6336d965df
2 changed files with 78 additions and 13 deletions

View file

@ -559,19 +559,68 @@ func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error {
return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts) return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts)
} }
// batchTxValidationState tracks the queue cost baseline for a batch addition so
// later transactions can account for the net queue growth caused by earlier ones.
type batchTxValidationState struct {
queueCostSnapshot map[common.Address]*big.Int
}
func newBatchTxValidationState() *batchTxValidationState {
return &batchTxValidationState{
queueCostSnapshot: make(map[common.Address]*big.Int),
}
}
func (batch *batchTxValidationState) queueSnapshot(pool *LegacyPool, addr common.Address) *big.Int {
if batch == nil {
return new(big.Int)
}
if snap, ok := batch.queueCostSnapshot[addr]; ok {
return snap
}
snap := new(big.Int)
if list, _ := pool.queue.get(addr); list != nil {
snap = list.totalcost.ToBig()
}
batch.queueCostSnapshot[addr] = snap
return snap
}
func (batch *batchTxValidationState) queueCostDelta(pool *LegacyPool, addr common.Address) *big.Int {
if batch == nil {
return new(big.Int)
}
current := new(big.Int)
if list, _ := pool.queue.get(addr); list != nil {
current = list.totalcost.ToBig()
}
snapshot := batch.queueSnapshot(pool, addr)
// Clamp negative deltas so queued cost reductions can't credit new batch additions.
if current.Cmp(snapshot) < 0 {
return new(big.Int)
}
return current.Sub(current, snapshot)
}
// validateTx checks whether a transaction is valid according to the consensus // validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size). // rules and adheres to some heuristic limits of the local node (price and size).
func (pool *LegacyPool) validateTx(tx *types.Transaction) error { func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
return pool.validateTxWithBatch(tx, nil)
}
func (pool *LegacyPool) validateTxWithBatch(tx *types.Transaction, batch *batchTxValidationState) error {
opts := &txpool.ValidationOptionsWithState{ opts := &txpool.ValidationOptionsWithState{
State: pool.currentState, State: pool.currentState,
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
UsedAndLeftSlots: nil, // Pool has own mechanism to limit the number of transactions UsedAndLeftSlots: nil, // Pool has own mechanism to limit the number of transactions
ExistingExpenditure: func(addr common.Address) *big.Int { ExistingExpenditure: func(addr common.Address) *big.Int {
spent := new(big.Int)
if list := pool.pending[addr]; list != nil { if list := pool.pending[addr]; list != nil {
return list.totalcost.ToBig() spent = list.totalcost.ToBig()
} }
return new(big.Int) return spent.Add(spent, batch.queueCostDelta(pool, addr))
}, },
ExistingCost: func(addr common.Address, nonce uint64) *big.Int { ExistingCost: func(addr common.Address, nonce uint64) *big.Int {
if list := pool.pending[addr]; list != nil { if list := pool.pending[addr]; list != nil {
@ -656,6 +705,10 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
// pending promotion and execution. If the transaction is a replacement for an already // pending promotion and execution. If the transaction is a replacement for an already
// pending or queued one, it overwrites the previous transaction if its price is higher. // pending or queued one, it overwrites the previous transaction if its price is higher.
func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
return pool.addWithBatch(tx, nil)
}
func (pool *LegacyPool) addWithBatch(tx *types.Transaction, batch *batchTxValidationState) (replaced bool, err error) {
// If the transaction is already known, discard it // If the transaction is already known, discard it
hash := tx.Hash() hash := tx.Hash()
if pool.all.Get(hash) != nil { if pool.all.Get(hash) != nil {
@ -665,7 +718,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
} }
// If the transaction fails basic validation, discard it // If the transaction fails basic validation, discard it
if err := pool.validateTx(tx); err != nil { if err := pool.validateTxWithBatch(tx, batch); err != nil {
log.Trace("Discarding invalid transaction", "hash", hash, "err", err) log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
invalidTxMeter.Mark(1) invalidTxMeter.Mark(1)
return false, err return false, err
@ -958,10 +1011,11 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accoun
var ( var (
dirty = newAccountSet(pool.signer) dirty = newAccountSet(pool.signer)
errs = make([]error, len(txs)) errs = make([]error, len(txs))
batch = newBatchTxValidationState()
valid int64 valid int64
) )
for i, tx := range txs { for i, tx := range txs {
replaced, err := pool.add(tx) replaced, err := pool.addWithBatch(tx, batch)
errs[i] = err errs[i] = err
if err == nil { if err == nil {
if !replaced { if !replaced {

View file

@ -395,7 +395,11 @@ func TestStateChangeDuringReset(t *testing.T) {
func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) { func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) {
pool.mu.Lock() pool.mu.Lock()
pool.currentState.AddBalance(addr, uint256.MustFromBig(amount), tracing.BalanceChangeUnspecified) if amount.Sign() < 0 {
pool.currentState.SubBalance(addr, uint256.MustFromBig(new(big.Int).Neg(amount)), tracing.BalanceChangeUnspecified)
} else {
pool.currentState.AddBalance(addr, uint256.MustFromBig(amount), tracing.BalanceChangeUnspecified)
}
pool.mu.Unlock() pool.mu.Unlock()
} }
@ -797,12 +801,13 @@ func TestPostponing(t *testing.T) {
for i := 0; i < len(keys); i++ { for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey() keys[i], _ = crypto.GenerateKey()
accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100))
} }
// Add a batch consecutive pending transactions for validation // Build a batch of consecutive pending transactions and fund each account
// for the full batch so strict balance checks still admit the entire set.
txs := []*types.Transaction{} txs := []*types.Transaction{}
initialBalances := make([]*big.Int, len(keys))
for i, key := range keys { for i, key := range keys {
initialBalances[i] = new(big.Int)
for j := 0; j < 100; j++ { for j := 0; j < 100; j++ {
var tx *types.Transaction var tx *types.Transaction
if (i+j)%2 == 0 { if (i+j)%2 == 0 {
@ -811,8 +816,12 @@ func TestPostponing(t *testing.T) {
tx = transaction(uint64(j), 50000, key) tx = transaction(uint64(j), 50000, key)
} }
txs = append(txs, tx) txs = append(txs, tx)
initialBalances[i].Add(initialBalances[i], tx.Cost())
} }
} }
for i, addr := range accs {
testAddBalance(pool, addr, initialBalances[i])
}
for i, err := range pool.addRemotesSync(txs) { for i, err := range pool.addRemotesSync(txs) {
if err != nil { if err != nil {
t.Fatalf("tx %d: failed to add transactions: %v", i, err) t.Fatalf("tx %d: failed to add transactions: %v", i, err)
@ -838,9 +847,11 @@ func TestPostponing(t *testing.T) {
if pool.all.Count() != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
} }
// Reduce the balance of the account, and check that transactions are reorganised // Reduce the balance to just below the expensive tx cost so those txs are
for _, addr := range accs { // dropped while the cheaper ones are postponed into the queue.
testAddBalance(pool, addr, big.NewInt(-1)) reducedBalance := new(big.Int).Sub(txs[1].Cost(), big.NewInt(1))
for i, addr := range accs {
testAddBalance(pool, addr, new(big.Int).Sub(reducedBalance, initialBalances[i]))
} }
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
@ -1352,7 +1363,7 @@ func TestPendingMinimumAllowance(t *testing.T) {
keys := make([]*ecdsa.PrivateKey, 5) keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ { for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey() keys[i], _ = crypto.GenerateKey()
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(10000000))
} }
// Generate and queue a batch of transactions // Generate and queue a batch of transactions
nonces := make(map[common.Address]uint64) nonces := make(map[common.Address]uint64)
@ -1745,7 +1756,7 @@ func TestStableUnderpricing(t *testing.T) {
keys := make([]*ecdsa.PrivateKey, 2) keys := make([]*ecdsa.PrivateKey, 2)
for i := 0; i < len(keys); i++ { for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey() keys[i], _ = crypto.GenerateKey()
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(100000000))
} }
// Fill up the entire queue with the same transaction price points // Fill up the entire queue with the same transaction price points
txs := types.Transactions{} txs := types.Transactions{}