core/types: separate special transactions correctly (#1893)

- Refactor NewTransactionsByPriceAndNonce to properly filter and separate special transactions (IsSpecialTransaction)
- Remove account from txs map if it has no normal transactions left
- Update comments for clarity
This commit is contained in:
Daniel Liu 2026-01-05 14:45:53 +08:00 committed by GitHub
parent adcc0d3652
commit fb89c95640
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 8 deletions

View file

@ -785,19 +785,20 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa
for _, accTxs := range txs {
from, _ := Sender(signer, accTxs[0])
var normalTxs Transactions
lastSpecialTx := -1
if lastSpecialTx >= 0 {
for i := 0; i <= lastSpecialTx; i++ {
specialTxs = append(specialTxs, accTxs[i])
for _, tx := range accTxs {
if tx.IsSpecialTransaction() {
specialTxs = append(specialTxs, tx)
} else {
normalTxs = append(normalTxs, tx)
}
normalTxs = accTxs[lastSpecialTx+1:]
} else {
normalTxs = accTxs
}
if len(normalTxs) > 0 {
heads.txs = append(heads.txs, normalTxs[0])
// Ensure the sender address is from the signer
// Remove the first normal transaction for this sender
txs[from] = normalTxs[1:]
} else {
// Remove the account if there are no normal transactions
delete(txs, from)
}
}
heap.Init(&heads)

View file

@ -718,3 +718,78 @@ func TestIsNonEVMTx(t *testing.T) {
})
}
}
// TestNewTransactionsByPriceAndNonce_SpecialSeparation uses table-driven tests to verify separation of special and normal transactions.
func TestNewTransactionsByPriceAndNonce_SpecialSeparation(t *testing.T) {
signer := HomesteadSigner{}
genNormalTx := func(nonce uint64, key *ecdsa.PrivateKey) *Transaction {
tx, _ := SignTx(NewTransaction(nonce, common.HexToAddress("0x1234567890123456789012345678901234567890"), big.NewInt(1), 21000, big.NewInt(1), nil), signer, key)
return tx
}
genSpecialTx := func(nonce uint64, key *ecdsa.PrivateKey) *Transaction {
tx, _ := SignTx(NewTransaction(nonce, common.BlockSignersBinary, big.NewInt(1), 21000, big.NewInt(1), nil), signer, key)
return tx
}
testCases := []struct {
name string
normalCount int
specialCount int
expectNormal int
expectSpecial int
}{
{"no transactions", 0, 0, 0, 0},
{"only 1 normal", 1, 0, 1, 0},
{"only 2 normal", 2, 0, 2, 0},
{"only 3 normal", 3, 0, 3, 0},
{"only 1 special", 0, 1, 0, 1},
{"only 2 special", 0, 2, 0, 2},
{"only 3 special", 0, 3, 0, 3},
{"1 normal, 1 special", 1, 1, 1, 1},
{"2 normal, 2 special", 2, 2, 2, 2},
{"3 normal, 3 special", 3, 3, 3, 3},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
txs := make(Transactions, 0, tc.normalCount+tc.specialCount)
for i := 0; i < tc.normalCount; i++ {
txs = append(txs, genNormalTx(uint64(i), key))
}
for i := 0; i < tc.specialCount; i++ {
txs = append(txs, genSpecialTx(uint64(tc.normalCount+i), key))
}
group := map[common.Address]Transactions{}
if len(txs) > 0 {
group[addr] = txs
}
txset, specialTxs := NewTransactionsByPriceAndNonce(signer, group, map[common.Address]*big.Int{})
// Check special transactions
if len(specialTxs) != tc.expectSpecial {
t.Errorf("expected %d special txs, got %d", tc.expectSpecial, len(specialTxs))
}
for _, tx := range specialTxs {
if tx.To() == nil || *tx.To() != common.BlockSignersBinary {
t.Errorf("specialTxs contains non-special tx: %v", tx)
}
}
// Check normal transactions
normalCount := 0
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
if tx.To() == nil || *tx.To() == common.BlockSignersBinary {
t.Errorf("txset contains special or nil-to tx: %v", tx)
}
normalCount++
txset.Shift()
}
if normalCount != tc.expectNormal {
t.Errorf("expected %d normal txs, got %d", tc.expectNormal, normalCount)
}
})
}
}