Skip to content

Commit 4200fca

Browse files
authored
Merge pull request #3 from osmosis-labs/fix/default-mempool-dedup
Deduplicate tx in default mempool insert
2 parents 3684c82 + 616d761 commit 4200fca

File tree

3 files changed

+177
-422
lines changed

3 files changed

+177
-422
lines changed

block/base/default.go

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,158 @@
11
package base
22

33
import (
4+
"container/list"
45
"context"
6+
"fmt"
57

68
sdk "github.com/cosmos/cosmos-sdk/types"
79
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
10+
11+
signer_extraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter"
812
)
913

1014
var (
1115
_ MempoolInterface = (*DefaultMempool[int64])(nil)
1216
_ sdkmempool.Iterator = (*DefaultIterator)(nil)
1317
)
1418

15-
// DefaultMempool implements a simple mempool that stores all transactions
19+
// DefaultMempool implements a FIFO mempool with duplicate detection based on sender:nonce.
20+
// It uses a linked list for FIFO ordering and a map for O(1) duplicate detection.
1621
type DefaultMempool[C comparable] struct {
17-
txs []sdk.Tx
18-
MaxTx int
22+
txs *list.List // Linked list for FIFO ordering
23+
seen map[string]*list.Element // Map from sender:nonce to list element
24+
MaxTx int // Maximum number of transactions
25+
signerExtractor signer_extraction.Adapter // For extracting signer info
1926
}
2027

21-
// NewDefaultMempool creates a new DefaultMempool
22-
func NewDefaultMempool[C comparable](maxTxs int) *DefaultMempool[C] {
28+
// NewDefaultMempool creates a new FIFO mempool with duplicate detection
29+
func NewDefaultMempool[C comparable](maxTxs int, signerExtractor signer_extraction.Adapter) *DefaultMempool[C] {
2330
return &DefaultMempool[C]{
24-
txs: make([]sdk.Tx, 0),
25-
MaxTx: maxTxs,
31+
txs: list.New(),
32+
seen: make(map[string]*list.Element),
33+
MaxTx: maxTxs,
34+
signerExtractor: signerExtractor,
35+
}
36+
}
37+
38+
// getTxKey creates a unique key from sender:nonce combination
39+
func (mp *DefaultMempool[C]) getTxKey(tx sdk.Tx) (string, error) {
40+
signers, err := mp.signerExtractor.GetSigners(tx)
41+
if err != nil {
42+
return "", err
2643
}
44+
if len(signers) == 0 {
45+
return "", fmt.Errorf("tx must have at least one signer")
46+
}
47+
48+
sig := signers[0]
49+
nonce := sig.Sequence
50+
sender := sig.Signer.String()
51+
52+
return fmt.Sprintf("%s:%d", sender, nonce), nil
2753
}
2854

2955
// Insert implements MempoolInterface.
3056
func (mp *DefaultMempool[C]) Insert(_ context.Context, tx sdk.Tx) error {
57+
if mp.MaxTx < 0 {
58+
return nil // No-op if MaxTx is negative
59+
}
60+
61+
key, err := mp.getTxKey(tx)
62+
if err != nil {
63+
return fmt.Errorf("failed to get tx key for insertion: %w", err)
64+
}
65+
66+
// Check if this tx already exists
67+
if _, exists := mp.seen[key]; exists {
68+
// No-op: transaction with same sender:nonce already exists
69+
return nil
70+
}
71+
72+
// Check capacity before adding new transaction
3173
if mp.MaxTx > 0 && mp.CountTx() >= mp.MaxTx {
3274
return sdkmempool.ErrMempoolTxMaxCapacity
33-
} else if mp.MaxTx < 0 {
34-
return nil
3575
}
36-
mp.txs = append(mp.txs, tx)
76+
77+
// Add new transaction
78+
element := mp.txs.PushBack(tx)
79+
mp.seen[key] = element
80+
3781
return nil
3882
}
3983

4084
// Remove implements MempoolInterface.
4185
func (mp *DefaultMempool[C]) Remove(tx sdk.Tx) error {
42-
for i, t := range mp.txs {
43-
if t == tx {
44-
mp.txs = append(mp.txs[:i], mp.txs[i+1:]...)
45-
return nil
46-
}
86+
key, err := mp.getTxKey(tx)
87+
if err != nil {
88+
return fmt.Errorf("failed to get tx key for removal: %w", err)
89+
}
90+
91+
// Remove by key
92+
if element, exists := mp.seen[key]; exists {
93+
mp.txs.Remove(element)
94+
delete(mp.seen, key)
4795
}
96+
4897
return nil
4998
}
5099

51100
// Select implements MempoolInterface.
52101
func (mp *DefaultMempool[C]) Select(_ context.Context, _ [][]byte) sdkmempool.Iterator {
53-
if len(mp.txs) == 0 {
102+
if mp.txs.Len() == 0 {
54103
return nil
55104
}
56105

57106
return &DefaultIterator{
58-
txs: mp.txs,
59-
curr: 0,
107+
current: mp.txs.Front(),
60108
}
61109
}
62110

63111
// CountTx implements MempoolInterface.
64112
func (mp *DefaultMempool[C]) CountTx() int {
65-
return len(mp.txs)
113+
return mp.txs.Len()
66114
}
67115

68116
// Contains implements MempoolInterface.
69117
func (mp *DefaultMempool[C]) Contains(tx sdk.Tx) bool {
70-
for _, t := range mp.txs {
71-
if t == tx {
72-
return true
73-
}
118+
key, err := mp.getTxKey(tx)
119+
if err != nil {
120+
return false
121+
}
122+
123+
// Check if we have this sender:nonce combination
124+
if element, exists := mp.seen[key]; exists {
125+
// Return true only if it's the exact same transaction object
126+
return element.Value.(sdk.Tx) == tx
74127
}
128+
75129
return false
76130
}
77131

78-
// DefaultIterator implements sdkmempool.Iterator
132+
// DefaultIterator implements sdkmempool.Iterator for FIFO mempool
79133
type DefaultIterator struct {
80-
txs []sdk.Tx
81-
curr int
134+
current *list.Element
82135
}
83136

84137
// Next implements sdkmempool.Iterator
85138
func (i *DefaultIterator) Next() sdkmempool.Iterator {
86-
if i.curr >= len(i.txs)-1 {
139+
if i.current == nil {
87140
return nil
88141
}
89-
i.curr++
142+
143+
i.current = i.current.Next()
144+
if i.current == nil {
145+
return nil
146+
}
147+
90148
return i
91149
}
92150

93151
// Tx implements sdkmempool.Iterator
94152
func (i *DefaultIterator) Tx() sdk.Tx {
95-
return i.txs[i.curr]
153+
if i.current == nil {
154+
return nil
155+
}
156+
157+
return i.current.Value.(sdk.Tx)
96158
}

0 commit comments

Comments
 (0)