Skip to content

Commit 29ad7e9

Browse files
mergify[bot]rach-idclauderootulpmcrakhman
authored
fix(mempool): cap SeenTxSet size to prevent OOM from SeenTx flooding (backport #2887) (#2927)
## Summary - Cap `SeenTxSet` to 10M entries (~5 GB worst-case) to prevent unbounded memory growth from malicious peers flooding `SeenTx` messages with random tx keys on channel `0x31`. - When the cap is reached and a new key arrives, one random existing entry is evicted to make room. Updates to existing keys are unaffected. - Added `TestSeenTxSetMaxSize` to verify the cap, eviction, and existing-key update behavior. Closes PROTOCO-1325 ## Test plan - [x] `TestSeenTxSet` passes (existing behavior preserved) - [x] `TestSeenTxSetMaxSize` passes (cap enforced, eviction works, existing-key updates work at capacity) - [ ] Run full mempool/cat test suite: `go test ./mempool/cat/...` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <a href="https://app.devin.ai/review/celestiaorg/celestia-core/pull/2887" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <hr>This is an automatic backport of pull request #2887 done by [Mergify](https://mergify.com). <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/celestiaorg/celestia-core/pull/2927" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: CHAMI Rachid <chamirachid1@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Rootul P <rootulp@gmail.com> Co-authored-by: Mikhail Rakhmanov <rakhmanov.m@gmail.com>
1 parent 6f09eba commit 29ad7e9

3 files changed

Lines changed: 42 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- Cap `SeenTxSet` to 10,000,000 entries (~5 GB worst-case) to prevent
2+
unbounded memory growth from malicious peers flooding SeenTx messages
3+
with random tx keys.
4+
([\#CELESTIA-256](https://github.com/celestiaorg/celestia-core/issues/CELESTIA-256))

mempool/cat/cache.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,26 @@ func NewSeenTxSet() *SeenTxSet {
2525
}
2626
}
2727

28+
// maxSeenTxSetSize limits the number of unique tx keys tracked in the SeenTxSet
29+
// to prevent unbounded memory growth from malicious peers flooding SeenTx messages.
30+
// Each entry costs ~500 bytes (including Go map overhead and GC pressure),
31+
// so 10M entries ≈ 5 GB worst-case.
32+
const maxSeenTxSetSize = 10_000_000
33+
2834
func (s *SeenTxSet) Add(txKey types.TxKey, peer uint16) {
2935
if peer == 0 {
3036
return
3137
}
3238
s.mtx.Lock()
3339
defer s.mtx.Unlock()
3440
seenSet, exists := s.set[txKey]
41+
if !exists && len(s.set) >= maxSeenTxSetSize {
42+
// Evict one random entry to make room.
43+
for k := range s.set {
44+
delete(s.set, k)
45+
break
46+
}
47+
}
3548
if !exists {
3649
s.set[txKey] = timestampedPeerSet{
3750
peers: map[uint16]struct{}{peer: {}},

mempool/cat/cache_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cat
22

33
import (
4+
"encoding/binary"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -35,3 +36,27 @@ func TestSeenTxSet(t *testing.T) {
3536
require.Nil(t, seenSet.Get(tx2Key))
3637
require.True(t, seenSet.Has(tx3Key, peer1))
3738
}
39+
40+
func TestSeenTxSetMaxSize(t *testing.T) {
41+
seenSet := NewSeenTxSet()
42+
43+
// Pre-fill the map to exactly maxSeenTxSetSize by inserting dummy entries directly.
44+
for i := 0; i < maxSeenTxSetSize; i++ {
45+
var key types.TxKey
46+
binary.BigEndian.PutUint64(key[:8], uint64(i))
47+
seenSet.set[key] = timestampedPeerSet{peers: map[uint16]struct{}{1: {}}}
48+
}
49+
require.Equal(t, maxSeenTxSetSize, seenSet.Len())
50+
51+
// New key should evict one entry and be added (size stays at cap).
52+
var extraKey types.TxKey
53+
binary.BigEndian.PutUint64(extraKey[:8], uint64(maxSeenTxSetSize+1))
54+
seenSet.Add(extraKey, 1)
55+
require.Equal(t, maxSeenTxSetSize, seenSet.Len())
56+
require.True(t, seenSet.Has(extraKey, 1))
57+
58+
// Adding a new peer to an existing key should still work at capacity.
59+
seenSet.Add(extraKey, 2)
60+
require.True(t, seenSet.Has(extraKey, 2))
61+
require.Equal(t, maxSeenTxSetSize, seenSet.Len())
62+
}

0 commit comments

Comments
 (0)