github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/consensus/wal_test.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "os" 7 "path/filepath" 8 9 // "sync" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/badrootd/nibiru-cometbft/consensus/types" 17 "github.com/badrootd/nibiru-cometbft/crypto/merkle" 18 "github.com/badrootd/nibiru-cometbft/libs/autofile" 19 "github.com/badrootd/nibiru-cometbft/libs/log" 20 cmttypes "github.com/badrootd/nibiru-cometbft/types" 21 cmttime "github.com/badrootd/nibiru-cometbft/types/time" 22 ) 23 24 const ( 25 walTestFlushInterval = time.Duration(100) * time.Millisecond 26 ) 27 28 func TestWALTruncate(t *testing.T) { 29 walDir, err := os.MkdirTemp("", "wal") 30 require.NoError(t, err) 31 defer os.RemoveAll(walDir) 32 33 walFile := filepath.Join(walDir, "wal") 34 35 // this magic number 4K can truncate the content when RotateFile. 36 // defaultHeadSizeLimit(10M) is hard to simulate. 37 // this magic number 1 * time.Millisecond make RotateFile check frequently. 38 // defaultGroupCheckDuration(5s) is hard to simulate. 39 wal, err := NewWAL(walFile, 40 autofile.GroupHeadSizeLimit(4096), 41 autofile.GroupCheckDuration(1*time.Millisecond), 42 ) 43 require.NoError(t, err) 44 wal.SetLogger(log.TestingLogger()) 45 err = wal.Start() 46 require.NoError(t, err) 47 defer func() { 48 if err := wal.Stop(); err != nil { 49 t.Error(err) 50 } 51 // wait for the wal to finish shutting down so we 52 // can safely remove the directory 53 wal.Wait() 54 }() 55 56 // 60 block's size nearly 70K, greater than group's headBuf size(4096 * 10), 57 // when headBuf is full, truncate content will Flush to the file. at this 58 // time, RotateFile is called, truncate content exist in each file. 59 err = WALGenerateNBlocks(t, wal.Group(), 60) 60 require.NoError(t, err) 61 62 time.Sleep(1 * time.Millisecond) // wait groupCheckDuration, make sure RotateFile run 63 64 if err := wal.FlushAndSync(); err != nil { 65 t.Error(err) 66 } 67 68 h := int64(50) 69 gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{}) 70 assert.NoError(t, err, "expected not to err on height %d", h) 71 assert.True(t, found, "expected to find end height for %d", h) 72 assert.NotNil(t, gr) 73 defer gr.Close() 74 75 dec := NewWALDecoder(gr) 76 msg, err := dec.Decode() 77 assert.NoError(t, err, "expected to decode a message") 78 rs, ok := msg.Msg.(cmttypes.EventDataRoundState) 79 assert.True(t, ok, "expected message of type EventDataRoundState") 80 assert.Equal(t, rs.Height, h+1, "wrong height") 81 } 82 83 func TestWALEncoderDecoder(t *testing.T) { 84 now := cmttime.Now() 85 msgs := []TimedWALMessage{ 86 {Time: now, Msg: EndHeightMessage{0}}, 87 {Time: now, Msg: timeoutInfo{Duration: time.Second, Height: 1, Round: 1, Step: types.RoundStepPropose}}, 88 {Time: now, Msg: cmttypes.EventDataRoundState{Height: 1, Round: 1, Step: ""}}, 89 } 90 91 b := new(bytes.Buffer) 92 93 for _, msg := range msgs { 94 msg := msg 95 96 b.Reset() 97 98 enc := NewWALEncoder(b) 99 err := enc.Encode(&msg) 100 require.NoError(t, err) 101 102 dec := NewWALDecoder(b) 103 decoded, err := dec.Decode() 104 require.NoError(t, err) 105 assert.Equal(t, msg.Time.UTC(), decoded.Time) 106 assert.Equal(t, msg.Msg, decoded.Msg) 107 } 108 } 109 110 func TestWALWrite(t *testing.T) { 111 walDir, err := os.MkdirTemp("", "wal") 112 require.NoError(t, err) 113 defer os.RemoveAll(walDir) 114 walFile := filepath.Join(walDir, "wal") 115 116 wal, err := NewWAL(walFile) 117 require.NoError(t, err) 118 err = wal.Start() 119 require.NoError(t, err) 120 defer func() { 121 if err := wal.Stop(); err != nil { 122 t.Error(err) 123 } 124 // wait for the wal to finish shutting down so we 125 // can safely remove the directory 126 wal.Wait() 127 }() 128 129 // 1) Write returns an error if msg is too big 130 msg := &BlockPartMessage{ 131 Height: 1, 132 Round: 1, 133 Part: &cmttypes.Part{ 134 Index: 1, 135 Bytes: make([]byte, 1), 136 Proof: merkle.Proof{ 137 Total: 1, 138 Index: 1, 139 LeafHash: make([]byte, maxMsgSizeBytes-30), 140 }, 141 }, 142 } 143 144 err = wal.Write(msgInfo{ 145 Msg: msg, 146 }) 147 if assert.Error(t, err) { 148 assert.Contains(t, err.Error(), "msg is too big") 149 } 150 } 151 152 func TestWALSearchForEndHeight(t *testing.T) { 153 walBody, err := WALWithNBlocks(t, 6) 154 if err != nil { 155 t.Fatal(err) 156 } 157 walFile := tempWALWithData(walBody) 158 159 wal, err := NewWAL(walFile) 160 require.NoError(t, err) 161 wal.SetLogger(log.TestingLogger()) 162 163 h := int64(3) 164 gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{}) 165 assert.NoError(t, err, "expected not to err on height %d", h) 166 assert.True(t, found, "expected to find end height for %d", h) 167 assert.NotNil(t, gr) 168 defer gr.Close() 169 170 dec := NewWALDecoder(gr) 171 msg, err := dec.Decode() 172 assert.NoError(t, err, "expected to decode a message") 173 rs, ok := msg.Msg.(cmttypes.EventDataRoundState) 174 assert.True(t, ok, "expected message of type EventDataRoundState") 175 assert.Equal(t, rs.Height, h+1, "wrong height") 176 } 177 178 func TestWALPeriodicSync(t *testing.T) { 179 walDir, err := os.MkdirTemp("", "wal") 180 require.NoError(t, err) 181 defer os.RemoveAll(walDir) 182 183 walFile := filepath.Join(walDir, "wal") 184 wal, err := NewWAL(walFile, autofile.GroupCheckDuration(1*time.Millisecond)) 185 require.NoError(t, err) 186 187 wal.SetFlushInterval(walTestFlushInterval) 188 wal.SetLogger(log.TestingLogger()) 189 190 // Generate some data 191 err = WALGenerateNBlocks(t, wal.Group(), 5) 192 require.NoError(t, err) 193 194 // We should have data in the buffer now 195 assert.NotZero(t, wal.Group().Buffered()) 196 197 require.NoError(t, wal.Start()) 198 defer func() { 199 if err := wal.Stop(); err != nil { 200 t.Error(err) 201 } 202 wal.Wait() 203 }() 204 205 time.Sleep(walTestFlushInterval + (10 * time.Millisecond)) 206 207 // The data should have been flushed by the periodic sync 208 assert.Zero(t, wal.Group().Buffered()) 209 210 h := int64(4) 211 gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{}) 212 assert.NoError(t, err, "expected not to err on height %d", h) 213 assert.True(t, found, "expected to find end height for %d", h) 214 assert.NotNil(t, gr) 215 if gr != nil { 216 gr.Close() 217 } 218 } 219 220 /* 221 var initOnce sync.Once 222 223 func registerInterfacesOnce() { 224 initOnce.Do(func() { 225 var _ = wire.RegisterInterface( 226 struct{ WALMessage }{}, 227 wire.ConcreteType{[]byte{}, 0x10}, 228 ) 229 }) 230 } 231 */ 232 233 func nBytes(n int) []byte { 234 buf := make([]byte, n) 235 n, _ = rand.Read(buf) 236 return buf[:n] 237 } 238 239 func benchmarkWalDecode(b *testing.B, n int) { 240 // registerInterfacesOnce() 241 242 buf := new(bytes.Buffer) 243 enc := NewWALEncoder(buf) 244 245 data := nBytes(n) 246 if err := enc.Encode(&TimedWALMessage{Msg: data, Time: time.Now().Round(time.Second).UTC()}); err != nil { 247 b.Error(err) 248 } 249 250 encoded := buf.Bytes() 251 252 b.ResetTimer() 253 for i := 0; i < b.N; i++ { 254 buf.Reset() 255 buf.Write(encoded) 256 dec := NewWALDecoder(buf) 257 if _, err := dec.Decode(); err != nil { 258 b.Fatal(err) 259 } 260 } 261 b.ReportAllocs() 262 } 263 264 func BenchmarkWalDecode512B(b *testing.B) { 265 benchmarkWalDecode(b, 512) 266 } 267 268 func BenchmarkWalDecode10KB(b *testing.B) { 269 benchmarkWalDecode(b, 10*1024) 270 } 271 func BenchmarkWalDecode100KB(b *testing.B) { 272 benchmarkWalDecode(b, 100*1024) 273 } 274 func BenchmarkWalDecode1MB(b *testing.B) { 275 benchmarkWalDecode(b, 1024*1024) 276 } 277 func BenchmarkWalDecode10MB(b *testing.B) { 278 benchmarkWalDecode(b, 10*1024*1024) 279 } 280 func BenchmarkWalDecode100MB(b *testing.B) { 281 benchmarkWalDecode(b, 100*1024*1024) 282 } 283 func BenchmarkWalDecode1GB(b *testing.B) { 284 benchmarkWalDecode(b, 1024*1024*1024) 285 }