Skip to content

Commit c62c88b

Browse files
committed
Deduplicate transactions on BatchTxPool prior to submission
1 parent 638fe2e commit c62c88b

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

services/requester/batch_tx_pool.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package requester
33
import (
44
"context"
55
"encoding/hex"
6+
"slices"
67
"sort"
78
"sync"
89
"time"
@@ -24,6 +25,7 @@ const eoaActivityCacheSize = 10_000
2425

2526
type pooledEvmTx struct {
2627
txPayload cadence.String
28+
txHash gethCommon.Hash
2729
nonce uint64
2830
}
2931

@@ -143,7 +145,7 @@ func (t *BatchTxPool) Add(
143145
err = t.submitSingleTransaction(ctx, hexEncodedTx)
144146
} else {
145147
// Case 3. EOA activity found AND it was less than [X] seconds ago:
146-
userTx := pooledEvmTx{txPayload: hexEncodedTx, nonce: tx.Nonce()}
148+
userTx := pooledEvmTx{txPayload: hexEncodedTx, txHash: tx.Hash(), nonce: tx.Nonce()}
147149
t.pooledTxs[from] = append(t.pooledTxs[from], userTx)
148150
}
149151

@@ -207,10 +209,22 @@ func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
207209
sort.Slice(pooledTxs, func(i, j int) bool {
208210
return pooledTxs[i].nonce < pooledTxs[j].nonce
209211
})
212+
// Filter out duplicate transactions, based on their tx hash
213+
slices.CompactFunc(pooledTxs, func(a pooledEvmTx, b pooledEvmTx) bool {
214+
if a.txHash == b.txHash {
215+
return true
216+
}
217+
return false
218+
})
210219

211-
hexEncodedTxs := make([]cadence.Value, len(pooledTxs))
212-
for i, txPayload := range pooledTxs {
213-
hexEncodedTxs[i] = txPayload.txPayload
220+
hexEncodedTxs := make([]cadence.Value, 0)
221+
for _, txPayload := range pooledTxs {
222+
// The `slices.CompactFunc` still reserves the duplicate entries
223+
// as zero values in the array, so we need to avoid including those.
224+
if txPayload.txHash == (gethCommon.Hash{}) {
225+
continue
226+
}
227+
hexEncodedTxs = append(hexEncodedTxs, txPayload.txPayload)
214228
}
215229

216230
coinbaseAddress, err := cadence.NewString(t.config.Coinbase.Hex())

tests/tx_batching_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,53 @@ func Test_MultipleTransactionSubmissionsWithinNonRecentInterval(t *testing.T) {
514514
)
515515
}
516516

517+
func Test_MultipleTransactionSubmissionsWithDuplicates(t *testing.T) {
518+
_, cfg, stop := setupGatewayNode(t)
519+
defer stop()
520+
521+
rpcTester := &rpcTest{
522+
url: fmt.Sprintf("%s:%d", cfg.RPCHost, cfg.RPCPort),
523+
}
524+
525+
eoaKey, err := crypto.HexToECDSA(eoaTestPrivateKey)
526+
require.NoError(t, err)
527+
528+
testAddr := common.HexToAddress("55253ed90B70b96C73092D8680915aaF50081194")
529+
nonce := uint64(0)
530+
hashes := make([]common.Hash, 0)
531+
532+
signed, _, err := evmSign(big.NewInt(10), 21000, eoaKey, nonce, &testAddr, nil)
533+
require.NoError(t, err)
534+
nonce += 1
535+
536+
txHash, err := rpcTester.sendRawTx(signed)
537+
require.NoError(t, err)
538+
hashes = append(hashes, txHash)
539+
540+
for range 5 {
541+
// All these transactions are duplicates, since we don't change any
542+
// of the payload data. These will end up having the same tx hash
543+
// as well.
544+
signed, _, err := evmSign(big.NewInt(10), 15_000_000, eoaKey, nonce, &testAddr, nil)
545+
require.NoError(t, err)
546+
547+
txHash, err := rpcTester.sendRawTx(signed)
548+
require.NoError(t, err)
549+
hashes = append(hashes, txHash)
550+
}
551+
552+
assert.Eventually(t, func() bool {
553+
for _, h := range hashes {
554+
rcp, err := rpcTester.getReceipt(h.String())
555+
if err != nil || rcp == nil || rcp.Status != 1 {
556+
return false
557+
}
558+
}
559+
560+
return true
561+
}, time.Second*15, time.Second*1, "all transactions were not executed")
562+
}
563+
517564
func setupGatewayNode(t *testing.T) (emulator.Emulator, config.Config, func()) {
518565
srv, err := startEmulator(true)
519566
require.NoError(t, err)

0 commit comments

Comments
 (0)