This commit is contained in:
Delweng 2026-02-25 21:58:01 -08:00 committed by GitHub
commit dac66d6ac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 131 additions and 0 deletions

View file

@ -91,6 +91,8 @@ func (s *Suite) EthTests() []utesting.Test {
{Name: "BlobViolations", Fn: s.TestBlobViolations},
{Name: "TestBlobTxWithoutSidecar", Fn: s.TestBlobTxWithoutSidecar},
{Name: "TestBlobTxWithMismatchedSidecar", Fn: s.TestBlobTxWithMismatchedSidecar},
{Name: "DuplicateTxs", Fn: s.TestDuplicateTxs},
{Name: "DuplicatePooledTxs", Fn: s.TestDuplicatePooledTxs},
}
}
@ -1200,3 +1202,41 @@ func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types
t.Fatalf("%v", err)
}
}
func (s *Suite) testDuplicateTxs(t *utesting.T, sendFunc func(*utesting.T, []*types.Transaction) error) {
// Nudge client out of syncing mode to accept pending txs.
if err := s.engine.sendForkchoiceUpdated(); err != nil {
t.Fatalf("failed to send next block: %v", err)
}
from, nonce := s.chain.GetSender(0)
inner := &types.DynamicFeeTx{
ChainID: s.chain.config.ChainID,
Nonce: nonce,
GasTipCap: common.Big1,
GasFeeCap: s.chain.Head().BaseFee(),
Gas: 30000,
To: &common.Address{0xaa},
Value: common.Big1,
}
tx, err := s.chain.SignTx(from, types.NewTx(inner))
if err != nil {
t.Fatalf("failed to sign tx: %v", err)
}
txs := []*types.Transaction{tx, tx}
if err := sendFunc(t, txs); err != nil {
t.Fatal(err)
}
s.chain.IncNonce(from, 1)
}
func (s *Suite) TestDuplicateTxs(t *utesting.T) {
t.Log(`This test sends a TransactionsMsg containing duplicate transactions and expects the node to disconnect.`)
s.testDuplicateTxs(t, s.sendDuplicateTxsInOneMsg)
}
func (s *Suite) TestDuplicatePooledTxs(t *utesting.T) {
t.Log(`This test announces transaction hashes to the node, then sends a PooledTransactionsMsg containing duplicate transactions and expects the node to disconnect.`)
s.testDuplicateTxs(t, s.sendDuplicatePooledTxsInOneMsg)
}

View file

@ -181,3 +181,94 @@ func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error {
}
}
}
// sendDuplicateTxsInOneMsg sends a transaction slice containing duplicates
// in a single TransactionsMsg and expects the node to disconnect.
func (s *Suite) sendDuplicateTxsInOneMsg(t *utesting.T, txs []*types.Transaction) error {
conn, err := s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
defer conn.Close()
if err = conn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
if err = conn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil {
return fmt.Errorf("failed to write message to connection: %v", err)
}
// Expect disconnect.
code, _, err := conn.Read()
if err != nil {
return fmt.Errorf("error reading from connection: %v", err)
}
if code != discMsg {
return fmt.Errorf("expected disconnect, got msg code %d", code)
}
return nil
}
// sendDuplicatePooledTxsInOneMsg sends a PooledTransactionsMsg containing duplicates
// and expects the node to disconnect.
func (s *Suite) sendDuplicatePooledTxsInOneMsg(t *utesting.T, txs []*types.Transaction) error {
// First, announce the transactions so the node will request them.
announceConn, err := s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
defer announceConn.Close()
if err = announceConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
// Create announcement for the transactions.
var (
hashes = make([]common.Hash, len(txs))
types = make([]byte, len(txs))
sizes = make([]uint32, len(txs))
)
for i, tx := range txs {
hashes[i] = tx.Hash()
types[i] = tx.Type()
sizes[i] = uint32(tx.Size())
}
ann := eth.NewPooledTransactionHashesPacket{
Types: types,
Sizes: sizes,
Hashes: hashes,
}
if err := announceConn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann); err != nil {
return fmt.Errorf("failed to announce transactions: %v", err)
}
// Wait for GetPooledTransactions request.
req := new(eth.GetPooledTransactionsPacket)
if err := announceConn.ReadMsg(ethProto, eth.GetPooledTransactionsMsg, req); err != nil {
return fmt.Errorf("failed to read GetPooledTransactions: %v", err)
}
// Send response with duplicate transactions.
resp := &eth.PooledTransactionsPacket{
RequestId: req.RequestId,
PooledTransactionsResponse: eth.PooledTransactionsResponse(txs),
}
if err := announceConn.Write(ethProto, eth.PooledTransactionsMsg, resp); err != nil {
return fmt.Errorf("failed to send PooledTransactions: %v", err)
}
// Expect disconnect.
code, _, err := announceConn.Read()
if err != nil {
return fmt.Errorf("error reading from connection: %v", err)
}
if code != discMsg {
return fmt.Errorf("expected disconnect, got msg code %d", code)
}
return nil
}