master
go 130 lines 4.25 KB
Raw
1 package libp2p
2
3 import (
4 "encoding/binary"
5 "testing"
6
7 "github.com/ipfs/go-datastore"
8 syncds "github.com/ipfs/go-datastore/sync"
9 "github.com/libp2p/go-libp2p/core/peer"
10 "github.com/stretchr/testify/require"
11 )
12
13 // TestSeqnoStore tests the seqnoStore implementation which backs the
14 // BasicSeqnoValidator. The validator prevents message cycles when network
15 // diameter exceeds the timecache span by tracking the maximum sequence number
16 // seen from each peer.
17 func TestSeqnoStore(t *testing.T) {
18 ctx := t.Context()
19 ds := syncds.MutexWrap(datastore.NewMapDatastore())
20 store := &seqnoStore{ds: ds}
21
22 peerA, err := peer.Decode("12D3KooWGC6TvWhfapngX6wvJHMYvKpDMXPb3ZnCZ6dMoaMtimQ5")
23 require.NoError(t, err)
24 peerB, err := peer.Decode("12D3KooWJRqDKTRjvXeGdUEgwkHNsoghYMBUagNYgLPdA4mqdTeo")
25 require.NoError(t, err)
26
27 // BasicSeqnoValidator expects Get to return (nil, nil) for unknown peers,
28 // not an error. This allows the validator to accept the first message from
29 // any peer without special-casing.
30 t.Run("unknown peer returns nil without error", func(t *testing.T) {
31 val, err := store.Get(ctx, peerA)
32 require.NoError(t, err)
33 require.Nil(t, val, "unknown peer should return nil, not empty slice")
34 })
35
36 // Verify basic store/retrieve functionality with a sequence number encoded
37 // as big-endian uint64, matching the format used by BasicSeqnoValidator.
38 t.Run("stores and retrieves seqno", func(t *testing.T) {
39 seqno := uint64(12345)
40 data := make([]byte, 8)
41 binary.BigEndian.PutUint64(data, seqno)
42
43 err := store.Put(ctx, peerA, data)
44 require.NoError(t, err)
45
46 val, err := store.Get(ctx, peerA)
47 require.NoError(t, err)
48 require.Equal(t, seqno, binary.BigEndian.Uint64(val))
49 })
50
51 // Each peer must have isolated storage. If peer data leaked between peers,
52 // the validator would incorrectly reject valid messages or accept replays.
53 t.Run("isolates seqno per peer", func(t *testing.T) {
54 seqnoA := uint64(100)
55 seqnoB := uint64(200)
56 dataA := make([]byte, 8)
57 dataB := make([]byte, 8)
58 binary.BigEndian.PutUint64(dataA, seqnoA)
59 binary.BigEndian.PutUint64(dataB, seqnoB)
60
61 err := store.Put(ctx, peerA, dataA)
62 require.NoError(t, err)
63 err = store.Put(ctx, peerB, dataB)
64 require.NoError(t, err)
65
66 valA, err := store.Get(ctx, peerA)
67 require.NoError(t, err)
68 require.Equal(t, seqnoA, binary.BigEndian.Uint64(valA))
69
70 valB, err := store.Get(ctx, peerB)
71 require.NoError(t, err)
72 require.Equal(t, seqnoB, binary.BigEndian.Uint64(valB))
73 })
74
75 // The validator updates the stored seqno when accepting messages with
76 // higher seqnos. This test verifies that updates work correctly.
77 t.Run("updates seqno to higher value", func(t *testing.T) {
78 seqno1 := uint64(1000)
79 seqno2 := uint64(2000)
80 data1 := make([]byte, 8)
81 data2 := make([]byte, 8)
82 binary.BigEndian.PutUint64(data1, seqno1)
83 binary.BigEndian.PutUint64(data2, seqno2)
84
85 err := store.Put(ctx, peerA, data1)
86 require.NoError(t, err)
87
88 err = store.Put(ctx, peerA, data2)
89 require.NoError(t, err)
90
91 val, err := store.Get(ctx, peerA)
92 require.NoError(t, err)
93 require.Equal(t, seqno2, binary.BigEndian.Uint64(val))
94 })
95
96 // Verify the datastore key format. This is important for:
97 // 1. Debugging: operators can inspect/clear pubsub state
98 // 2. Migrations: future changes need to know the key format
99 t.Run("uses expected datastore key format", func(t *testing.T) {
100 seqno := uint64(42)
101 data := make([]byte, 8)
102 binary.BigEndian.PutUint64(data, seqno)
103
104 err := store.Put(ctx, peerA, data)
105 require.NoError(t, err)
106
107 // Verify we can read directly from datastore with expected key
108 expectedKey := datastore.NewKey("/pubsub/seqno/" + peerA.String())
109 val, err := ds.Get(ctx, expectedKey)
110 require.NoError(t, err)
111 require.Equal(t, seqno, binary.BigEndian.Uint64(val))
112 })
113
114 // Verify data persists when creating a new store instance with the same
115 // underlying datastore. This simulates node restart.
116 t.Run("persists across store instances", func(t *testing.T) {
117 seqno := uint64(99999)
118 data := make([]byte, 8)
119 binary.BigEndian.PutUint64(data, seqno)
120
121 err := store.Put(ctx, peerB, data)
122 require.NoError(t, err)
123
124 // Create new store instance with same datastore
125 store2 := &seqnoStore{ds: ds}
126 val, err := store2.Get(ctx, peerB)
127 require.NoError(t, err)
128 require.Equal(t, seqno, binary.BigEndian.Uint64(val))
129 })
130 }