github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/tendermint/consensus/wal.go (about) 1 package consensus 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "hash/crc32" 7 "io" 8 "path/filepath" 9 "time" 10 11 "github.com/pkg/errors" 12 13 amino "github.com/tendermint/go-amino" 14 15 auto "github.com/fibonacci-chain/fbc/libs/tendermint/libs/autofile" 16 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 17 tmos "github.com/fibonacci-chain/fbc/libs/tendermint/libs/os" 18 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/service" 19 "github.com/fibonacci-chain/fbc/libs/tendermint/types" 20 tmtime "github.com/fibonacci-chain/fbc/libs/tendermint/types/time" 21 ) 22 23 const ( 24 // amino overhead + time.Time + max consensus msg size 25 // 26 // q: where 24 bytes are coming from? 27 // a: cdc.MustMarshalBinaryBare(empty consensus part msg) = 14 bytes. +10 28 // bytes just in case amino will require more space in the future. 29 maxMsgSizeBytes = maxMsgSize + 24 30 31 // how often the WAL should be sync'd during period sync'ing 32 walDefaultFlushInterval = 2 * time.Second 33 34 // if write wal time is more than walAlertTime, should log error 35 walAlertTime = 500 * time.Millisecond 36 ) 37 38 //-------------------------------------------------------- 39 // types and functions for savings consensus messages 40 41 // TimedWALMessage wraps WALMessage and adds Time for debugging purposes. 42 type TimedWALMessage struct { 43 Time time.Time `json:"time"` 44 Msg WALMessage `json:"msg"` 45 } 46 47 // EndHeightMessage marks the end of the given height inside WAL. 48 // @internal used by scripts/wal2json util. 49 type EndHeightMessage struct { 50 Height int64 `json:"height"` 51 } 52 53 type WALMessage interface{} 54 55 func RegisterWALMessages(cdc *amino.Codec) { 56 cdc.RegisterInterface((*WALMessage)(nil), nil) 57 cdc.RegisterConcrete(types.EventDataRoundState{}, "tendermint/wal/EventDataRoundState", nil) 58 cdc.RegisterConcrete(msgInfo{}, "tendermint/wal/MsgInfo", nil) 59 cdc.RegisterConcrete(timeoutInfo{}, "tendermint/wal/TimeoutInfo", nil) 60 cdc.RegisterConcrete(EndHeightMessage{}, "tendermint/wal/EndHeightMessage", nil) 61 } 62 63 //-------------------------------------------------------- 64 // Simple write-ahead logger 65 66 // WAL is an interface for any write-ahead logger. 67 type WAL interface { 68 Write(WALMessage) error 69 WriteSync(WALMessage) error 70 FlushAndSync() error 71 72 SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) 73 74 // service methods 75 Start() error 76 Reset() error 77 Stop() error 78 Wait() 79 } 80 81 // Write ahead logger writes msgs to disk before they are processed. 82 // Can be used for crash-recovery and deterministic replay. 83 // TODO: currently the wal is overwritten during replay catchup, give it a mode 84 // so it's either reading or appending - must read to end to start appending 85 // again. 86 type BaseWAL struct { 87 service.BaseService 88 89 group *auto.Group 90 91 enc *WALEncoder 92 93 flushTicker *time.Ticker 94 flushInterval time.Duration 95 } 96 97 var _ WAL = &BaseWAL{} 98 99 // NewWAL returns a new write-ahead logger based on `baseWAL`, which implements 100 // WAL. It's flushed and synced to disk every 2s and once when stopped. 101 func NewWAL(walFile string, groupOptions ...func(*auto.Group)) (*BaseWAL, error) { 102 err := tmos.EnsureDir(filepath.Dir(walFile), 0700) 103 if err != nil { 104 return nil, errors.Wrap(err, "failed to ensure WAL directory is in place") 105 } 106 107 group, err := auto.OpenGroup(walFile, groupOptions...) 108 if err != nil { 109 return nil, err 110 } 111 wal := &BaseWAL{ 112 group: group, 113 enc: NewWALEncoder(group), 114 flushInterval: walDefaultFlushInterval, 115 } 116 wal.BaseService = *service.NewBaseService(nil, "baseWAL", wal) 117 return wal, nil 118 } 119 120 // SetFlushInterval allows us to override the periodic flush interval for the WAL. 121 func (wal *BaseWAL) SetFlushInterval(i time.Duration) { 122 wal.flushInterval = i 123 } 124 125 func (wal *BaseWAL) Group() *auto.Group { 126 return wal.group 127 } 128 129 func (wal *BaseWAL) SetLogger(l log.Logger) { 130 wal.BaseService.Logger = l 131 wal.group.SetLogger(l) 132 } 133 134 func (wal *BaseWAL) OnStart() error { 135 size, err := wal.group.Head.Size() 136 if err != nil { 137 return err 138 } else if size == 0 { 139 wal.WriteSync(EndHeightMessage{types.GetStartBlockHeight()}) 140 } 141 err = wal.group.Start() 142 if err != nil { 143 return err 144 } 145 wal.flushTicker = time.NewTicker(wal.flushInterval) 146 go wal.processFlushTicks() 147 return nil 148 } 149 150 func (wal *BaseWAL) OnReset() error { 151 //size, err := wal.group.Head.Size() 152 //if err != nil { 153 // return err 154 //} else if size == 0 { 155 // wal.WriteSync(EndHeightMessage{types.GetStartBlockHeight()}) 156 //} 157 err := wal.group.Reset() 158 if err != nil { 159 return err 160 } 161 //wal.flushTicker.Reset(wal.flushInterval) 162 //go wal.processFlushTicks() 163 164 return nil 165 } 166 167 func (wal *BaseWAL) processFlushTicks() { 168 for { 169 select { 170 case <-wal.flushTicker.C: 171 if err := wal.FlushAndSync(); err != nil { 172 wal.Logger.Error("Periodic WAL flush failed", "err", err) 173 } 174 case <-wal.Quit(): 175 return 176 } 177 } 178 } 179 180 // FlushAndSync flushes and fsync's the underlying group's data to disk. 181 // See auto#FlushAndSync 182 func (wal *BaseWAL) FlushAndSync() error { 183 return wal.group.FlushAndSync() 184 } 185 186 // Stop the underlying autofile group. 187 // Use Wait() to ensure it's finished shutting down 188 // before cleaning up files. 189 func (wal *BaseWAL) OnStop() { 190 wal.flushTicker.Stop() 191 wal.FlushAndSync() 192 wal.group.Stop() 193 wal.group.Close() 194 } 195 196 // Wait for the underlying autofile group to finish shutting down 197 // so it's safe to cleanup files. 198 func (wal *BaseWAL) Wait() { 199 wal.group.Wait() 200 } 201 202 // Write is called in newStep and for each receive on the 203 // peerMsgQueue and the timeoutTicker. 204 // NOTE: does not call fsync() 205 func (wal *BaseWAL) Write(msg WALMessage) error { 206 t0 := tmtime.Now() 207 if wal == nil { 208 return nil 209 } 210 211 if err := wal.enc.Encode(&TimedWALMessage{tmtime.Now(), msg}); err != nil { 212 wal.Logger.Error("Error writing msg to consensus wal. WARNING: recover may not be possible for the current height", 213 "err", err, "msg", msg) 214 return err 215 } 216 217 if t := tmtime.Now().Sub(t0); t > walAlertTime { 218 wal.Logger.Error("WAL Write Message", "time", t, "msg", msg) 219 } 220 221 return nil 222 } 223 224 // WriteSync is called when we receive a msg from ourselves 225 // so that we write to disk before sending signed messages. 226 // NOTE: calls fsync() 227 func (wal *BaseWAL) WriteSync(msg WALMessage) error { 228 t0 := tmtime.Now() 229 230 if wal == nil { 231 return nil 232 } 233 234 if err := wal.Write(msg); err != nil { 235 return err 236 } 237 238 if err := wal.FlushAndSync(); err != nil { 239 wal.Logger.Error(`WriteSync failed to flush consensus wal. 240 WARNING: may result in creating alternative proposals / votes for the current height iff the node restarted`, 241 "err", err) 242 return err 243 } 244 245 if t := tmtime.Now().Sub(t0); t > walAlertTime { 246 wal.Logger.Error("WriteSync WAL", "time", t, "msg", msg) 247 } 248 249 return nil 250 } 251 252 // WALSearchOptions are optional arguments to SearchForEndHeight. 253 type WALSearchOptions struct { 254 // IgnoreDataCorruptionErrors set to true will result in skipping data corruption errors. 255 IgnoreDataCorruptionErrors bool 256 } 257 258 // SearchForEndHeight searches for the EndHeightMessage with the given height 259 // and returns an auto.GroupReader, whenever it was found or not and an error. 260 // Group reader will be nil if found equals false. 261 // 262 // CONTRACT: caller must close group reader. 263 func (wal *BaseWAL) SearchForEndHeight( 264 height int64, 265 options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { 266 var ( 267 msg *TimedWALMessage 268 gr *auto.GroupReader 269 ) 270 lastHeightFound := int64(-1) 271 272 // NOTE: starting from the last file in the group because we're usually 273 // searching for the last height. See replay.go 274 min, max := wal.group.MinIndex(), wal.group.MaxIndex() 275 wal.Logger.Info("Searching for height", "height", height, "min", min, "max", max) 276 for index := max; index >= min; index-- { 277 gr, err = wal.group.NewReader(index) 278 if err != nil { 279 return nil, false, err 280 } 281 282 dec := NewWALDecoder(gr) 283 for { 284 msg, err = dec.Decode() 285 if err == io.EOF { 286 // OPTIMISATION: no need to look for height in older files if we've seen h < height 287 if lastHeightFound > 0 && lastHeightFound < height { 288 gr.Close() 289 return nil, false, nil 290 } 291 // check next file 292 break 293 } 294 if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) { 295 wal.Logger.Error("Corrupted entry. Skipping...", "err", err) 296 // do nothing 297 continue 298 } else if err != nil { 299 gr.Close() 300 return nil, false, err 301 } 302 303 if m, ok := msg.Msg.(EndHeightMessage); ok { 304 lastHeightFound = m.Height 305 if m.Height == height { // found 306 wal.Logger.Info("Found", "height", height, "index", index) 307 return gr, true, nil 308 } 309 } 310 } 311 gr.Close() 312 } 313 314 return nil, false, nil 315 } 316 317 /////////////////////////////////////////////////////////////////////////////// 318 319 // A WALEncoder writes custom-encoded WAL messages to an output stream. 320 // 321 // Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value (go-amino encoded) 322 type WALEncoder struct { 323 wr io.Writer 324 } 325 326 // NewWALEncoder returns a new encoder that writes to wr. 327 func NewWALEncoder(wr io.Writer) *WALEncoder { 328 return &WALEncoder{wr} 329 } 330 331 // Encode writes the custom encoding of v to the stream. It returns an error if 332 // the amino-encoded size of v is greater than 1MB. Any error encountered 333 // during the write is also returned. 334 func (enc *WALEncoder) Encode(v *TimedWALMessage) error { 335 data := cdc.MustMarshalBinaryBare(v) 336 337 crc := crc32.Checksum(data, crc32c) 338 length := uint32(len(data)) 339 if length > maxMsgSizeBytes { 340 return fmt.Errorf("msg is too big: %d bytes, max: %d bytes", length, maxMsgSizeBytes) 341 } 342 totalLength := 8 + int(length) 343 344 msg := make([]byte, totalLength) 345 binary.BigEndian.PutUint32(msg[0:4], crc) 346 binary.BigEndian.PutUint32(msg[4:8], length) 347 copy(msg[8:], data) 348 349 _, err := enc.wr.Write(msg) 350 return err 351 } 352 353 /////////////////////////////////////////////////////////////////////////////// 354 355 // IsDataCorruptionError returns true if data has been corrupted inside WAL. 356 func IsDataCorruptionError(err error) bool { 357 _, ok := err.(DataCorruptionError) 358 return ok 359 } 360 361 // DataCorruptionError is an error that occures if data on disk was corrupted. 362 type DataCorruptionError struct { 363 cause error 364 } 365 366 func (e DataCorruptionError) Error() string { 367 return fmt.Sprintf("DataCorruptionError[%v]", e.cause) 368 } 369 370 func (e DataCorruptionError) Cause() error { 371 return e.cause 372 } 373 374 // A WALDecoder reads and decodes custom-encoded WAL messages from an input 375 // stream. See WALEncoder for the format used. 376 // 377 // It will also compare the checksums and make sure data size is equal to the 378 // length from the header. If that is not the case, error will be returned. 379 type WALDecoder struct { 380 rd io.Reader 381 } 382 383 // NewWALDecoder returns a new decoder that reads from rd. 384 func NewWALDecoder(rd io.Reader) *WALDecoder { 385 return &WALDecoder{rd} 386 } 387 388 // Decode reads the next custom-encoded value from its reader and returns it. 389 func (dec *WALDecoder) Decode() (*TimedWALMessage, error) { 390 b := make([]byte, 4) 391 392 _, err := dec.rd.Read(b) 393 if err == io.EOF { 394 return nil, err 395 } 396 if err != nil { 397 return nil, DataCorruptionError{fmt.Errorf("failed to read checksum: %v", err)} 398 } 399 crc := binary.BigEndian.Uint32(b) 400 401 b = make([]byte, 4) 402 _, err = dec.rd.Read(b) 403 if err != nil { 404 return nil, DataCorruptionError{fmt.Errorf("failed to read length: %v", err)} 405 } 406 length := binary.BigEndian.Uint32(b) 407 408 if length > maxMsgSizeBytes { 409 return nil, DataCorruptionError{fmt.Errorf( 410 "length %d exceeded maximum possible value of %d bytes", 411 length, 412 maxMsgSizeBytes)} 413 } 414 415 data := make([]byte, length) 416 n, err := dec.rd.Read(data) 417 if err != nil { 418 return nil, DataCorruptionError{fmt.Errorf("failed to read data: %v (read: %d, wanted: %d)", err, n, length)} 419 } 420 421 // check checksum before decoding data 422 actualCRC := crc32.Checksum(data, crc32c) 423 if actualCRC != crc { 424 return nil, DataCorruptionError{fmt.Errorf("checksums do not match: read: %v, actual: %v", crc, actualCRC)} 425 } 426 427 var res = new(TimedWALMessage) // nolint: gosimple 428 err = cdc.UnmarshalBinaryBare(data, res) 429 if err != nil { 430 return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)} 431 } 432 433 return res, err 434 } 435 436 type nilWAL struct{} 437 438 var _ WAL = nilWAL{} 439 440 func (nilWAL) Write(m WALMessage) error { return nil } 441 func (nilWAL) WriteSync(m WALMessage) error { return nil } 442 func (nilWAL) FlushAndSync() error { return nil } 443 func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { 444 return nil, false, nil 445 } 446 func (nilWAL) Start() error { return nil } 447 func (nilWAL) Reset() error { return nil } 448 func (nilWAL) Stop() error { return nil } 449 func (nilWAL) Wait() {}