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()        {}