github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/consensus/replay.go (about)

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"log/slog"
     9  	"reflect"
    10  	"time"
    11  
    12  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    13  	"github.com/gnolang/gno/tm2/pkg/bft/appconn"
    14  	cstypes "github.com/gnolang/gno/tm2/pkg/bft/consensus/types"
    15  	"github.com/gnolang/gno/tm2/pkg/bft/mempool/mock"
    16  	"github.com/gnolang/gno/tm2/pkg/bft/proxy"
    17  	sm "github.com/gnolang/gno/tm2/pkg/bft/state"
    18  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    19  	walm "github.com/gnolang/gno/tm2/pkg/bft/wal"
    20  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    21  	"github.com/gnolang/gno/tm2/pkg/events"
    22  	"github.com/gnolang/gno/tm2/pkg/log"
    23  )
    24  
    25  // Functionality to replay blocks and messages on recovery from a crash.
    26  // There are two general failure scenarios:
    27  //
    28  //  1. failure during consensus
    29  //  2. failure while applying the block
    30  //
    31  // The former is handled by the WAL, the latter by the proxyApp Handshake on
    32  // restart, which ultimately hands off the work to the WAL.
    33  
    34  // -----------------------------------------
    35  // 1. Recover from failure during consensus
    36  // (by replaying messages from the WAL)
    37  // -----------------------------------------
    38  
    39  // Unmarshal and apply a single message to the consensus state as if it were
    40  // received in receiveRoutine.  Lines that start with "#" are ignored.
    41  // NOTE: receiveRoutine should not be running.
    42  func (cs *ConsensusState) readReplayMessage(msg *walm.TimedWALMessage, meta *walm.MetaMessage, newStepSub <-chan events.Event) error {
    43  	// Skip meta messages which exist for demarcating boundaries.
    44  	if meta != nil {
    45  		return nil
    46  	}
    47  
    48  	// for logging
    49  	switch m := msg.Msg.(type) {
    50  	case newRoundStepInfo:
    51  		cs.Logger.Info("Replay: New Step", "height", m.Height, "round", m.Round, "step", m.Step)
    52  		// these are playback checks
    53  		ticker := time.After(time.Second * 2)
    54  		if newStepSub != nil {
    55  			select {
    56  			case stepMsg, ok := <-newStepSub:
    57  				if !ok {
    58  					return fmt.Errorf("failed to read off newStepSub. newStepSub was cancelled")
    59  				}
    60  				m2 := stepMsg.(cstypes.EventNewRoundStep)
    61  				if m.Height != m2.Height || m.Round != m2.Round || m.Step != m2.Step {
    62  					return fmt.Errorf("RoundState mismatch. Got %v; Expected %v", m2, m)
    63  				}
    64  			case <-ticker:
    65  				return fmt.Errorf("failed to read off newStepSub")
    66  			}
    67  		}
    68  	case msgInfo:
    69  		peerID := m.PeerID
    70  		if peerID == "" {
    71  			peerID = "local"
    72  		}
    73  		switch msg := m.Msg.(type) {
    74  		case *ProposalMessage:
    75  			p := msg.Proposal
    76  			cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header",
    77  				p.BlockID.PartsHeader, "pol", p.POLRound, "peer", peerID)
    78  		case *BlockPartMessage:
    79  			cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerID)
    80  		case *VoteMessage:
    81  			v := msg.Vote
    82  			cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type,
    83  				"blockID", v.BlockID, "peer", peerID)
    84  		}
    85  
    86  		cs.handleMsg(m)
    87  	case timeoutInfo:
    88  		cs.Logger.Info("Replay: Timeout", "height", m.Height, "round", m.Round, "step", m.Step, "dur", m.Duration)
    89  		cs.handleTimeout(m, cs.RoundState)
    90  	default:
    91  		return fmt.Errorf("replay: Unknown TimedWALMessage type: %v", reflect.TypeOf(msg.Msg))
    92  	}
    93  	return nil
    94  }
    95  
    96  // Replay only those messages since the last block.  `timeoutRoutine` should
    97  // run concurrently to read off tickChan.
    98  func (cs *ConsensusState) catchupReplay(csHeight int64) error {
    99  	// Set replayMode to true so we don't log signing errors.
   100  	cs.replayMode = true
   101  	defer func() { cs.replayMode = false }()
   102  
   103  	// Ensure that MetaMessage.Height = height+1 doesn't exist.
   104  	// NOTE: This is just a sanity check. As far as we know things work fine
   105  	// without it, and Handshake could reuse ConsensusState if it weren't for
   106  	// this check (since we can crash after writing #{"h"} (meta height).).
   107  	//
   108  	// Ignore data corruption errors since this is a sanity check.
   109  	gr, found, err := cs.wal.SearchForHeight(csHeight+1, &walm.WALSearchOptions{IgnoreDataCorruptionErrors: true})
   110  	if err != nil {
   111  		return err
   112  	}
   113  	if gr != nil {
   114  		if err := gr.Close(); err != nil {
   115  			return err
   116  		}
   117  	}
   118  	if found {
   119  		return fmt.Errorf("WAL should not contain #ENDHEIGHT %d", csHeight)
   120  	}
   121  
   122  	// Search for last height marker.
   123  	//
   124  	// Ignore data corruption errors in previous heights because we only care about last height
   125  	gr, found, err = cs.wal.SearchForHeight(csHeight, &walm.WALSearchOptions{IgnoreDataCorruptionErrors: true})
   126  	if errors.Is(err, io.EOF) {
   127  		cs.Logger.Error("Replay: wal.group.Search returned EOF", "#ENDHEIGHT", csHeight-1)
   128  	} else if err != nil {
   129  		return err
   130  	}
   131  	if !found {
   132  		return fmt.Errorf("cannot replay height %d. WAL does not contain #ENDHEIGHT for %d", csHeight, csHeight-1)
   133  	}
   134  	defer gr.Close() //nolint: errcheck
   135  
   136  	cs.Logger.Info("Catchup by replaying consensus messages", "height", csHeight)
   137  
   138  	dec := walm.NewWALReader(gr, maxMsgSize)
   139  
   140  LOOP:
   141  	for {
   142  		msg, meta, err := dec.ReadMessage()
   143  		switch {
   144  		case errors.Is(err, io.EOF):
   145  			break LOOP
   146  		case walm.IsDataCorruptionError(err):
   147  			cs.Logger.Error("data has been corrupted in last height of consensus WAL", "err", err, "height", csHeight)
   148  			return err
   149  		case err != nil:
   150  			return err
   151  		}
   152  		// NOTE: since the priv key is set when the msgs are received
   153  		// it will attempt to eg double sign but we can just ignore it
   154  		// since the votes will be replayed and we'll get to the next step
   155  		if err := cs.readReplayMessage(msg, meta, nil); err != nil {
   156  			return err
   157  		}
   158  	}
   159  	cs.Logger.Info("Replay: Done")
   160  	return nil
   161  }
   162  
   163  // --------------------------------------------------------------------------------
   164  
   165  // Parses marker lines of the form:
   166  // #ENDHEIGHT: 12345
   167  /*
   168  func makeHeightSearchFunc(height int64) auto.SearchFunc {
   169  	return func(line string) (int, error) {
   170  		line = strings.TrimRight(line, "\n")
   171  		parts := strings.Split(line, " ")
   172  		if len(parts) != 2 {
   173  			return -1, errors.New("Line did not have 2 parts")
   174  		}
   175  		i, err := strconv.Atoi(parts[1])
   176  		if err != nil {
   177  			return -1, errors.New("Failed to parse INFO: " + err.Error())
   178  		}
   179  		if height < i {
   180  			return 1, nil
   181  		} else if height == i {
   182  			return 0, nil
   183  		} else {
   184  			return -1, nil
   185  		}
   186  	}
   187  }*/
   188  
   189  // ---------------------------------------------------
   190  // 2. Recover from failure while applying the block.
   191  // (by handshaking with the app to figure out where
   192  // we were last, and using the WAL to recover there.)
   193  // ---------------------------------------------------
   194  
   195  type Handshaker struct {
   196  	stateDB      dbm.DB
   197  	initialState sm.State
   198  	store        sm.BlockStore
   199  	evsw         events.EventSwitch
   200  	genDoc       *types.GenesisDoc
   201  	logger       *slog.Logger
   202  
   203  	nBlocks int // number of blocks applied to the state
   204  }
   205  
   206  func NewHandshaker(stateDB dbm.DB, state sm.State,
   207  	store sm.BlockStore, genDoc *types.GenesisDoc,
   208  ) *Handshaker {
   209  	return &Handshaker{
   210  		stateDB:      stateDB,
   211  		initialState: state,
   212  		store:        store,
   213  		evsw:         events.NilEventSwitch(),
   214  		genDoc:       genDoc,
   215  		logger:       log.NewNoopLogger(),
   216  		nBlocks:      0,
   217  	}
   218  }
   219  
   220  func (h *Handshaker) SetLogger(l *slog.Logger) {
   221  	h.logger = l
   222  }
   223  
   224  // SetEventSwitch - sets the event bus for publishing block related events.
   225  // If not called, it defaults to types.NopEventSwitch.
   226  func (h *Handshaker) SetEventSwitch(evsw events.EventSwitch) {
   227  	h.evsw = evsw
   228  }
   229  
   230  // NBlocks returns the number of blocks applied to the state.
   231  func (h *Handshaker) NBlocks() int {
   232  	return h.nBlocks
   233  }
   234  
   235  // TODO: retry the handshake/replay if it fails ?
   236  func (h *Handshaker) Handshake(proxyApp appconn.AppConns) error {
   237  	// Handshake is done via ABCI Info on the query conn.
   238  	res, err := proxyApp.Query().InfoSync(abci.RequestInfo{})
   239  	if err != nil {
   240  		return fmt.Errorf("Error calling Info: %w", err)
   241  	}
   242  
   243  	blockHeight := res.LastBlockHeight
   244  	if blockHeight < 0 {
   245  		return fmt.Errorf("got a negative last block height (%d) from the app", blockHeight)
   246  	}
   247  	appHash := res.LastBlockAppHash
   248  
   249  	h.logger.Info("ABCI Handshake App Info",
   250  		"height", blockHeight,
   251  		"hash", fmt.Sprintf("%X", appHash),
   252  		"abci-version", res.ABCIVersion,
   253  		"app-version", res.AppVersion,
   254  	)
   255  
   256  	// Set AppVersion on the state.
   257  	if h.initialState.AppVersion != res.AppVersion {
   258  		h.initialState.AppVersion = res.AppVersion
   259  		sm.SaveState(h.stateDB, h.initialState)
   260  	}
   261  
   262  	// Replay blocks up to the latest in the blockstore.
   263  	_, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp)
   264  	if err != nil {
   265  		return fmt.Errorf("error on replay: %w", err)
   266  	}
   267  
   268  	h.logger.Info("Completed ABCI Handshake - Tendermint and App are synced",
   269  		"appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
   270  
   271  	// TODO: (on restart) replay mempool
   272  
   273  	return nil
   274  }
   275  
   276  // ReplayBlocks replays all blocks since appBlockHeight and ensures the result
   277  // matches the current state.
   278  // Returns the final AppHash or an error.
   279  func (h *Handshaker) ReplayBlocks(
   280  	state sm.State,
   281  	appHash []byte,
   282  	appBlockHeight int64,
   283  	proxyApp appconn.AppConns,
   284  ) ([]byte, error) {
   285  	storeBlockHeight := h.store.Height()
   286  	stateBlockHeight := state.LastBlockHeight
   287  	h.logger.Info("ABCI Replay Blocks", "appHeight", appBlockHeight, "storeHeight", storeBlockHeight, "stateHeight", stateBlockHeight)
   288  
   289  	// If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain.
   290  	if appBlockHeight == 0 {
   291  		validators := make([]*types.Validator, len(h.genDoc.Validators))
   292  		for i, val := range h.genDoc.Validators {
   293  			validators[i] = types.NewValidator(val.PubKey, val.Power)
   294  		}
   295  		validatorSet := types.NewValidatorSet(validators)
   296  		nextVals := validatorSet.ABCIValidatorUpdates()
   297  		csParams := h.genDoc.ConsensusParams
   298  		req := abci.RequestInitChain{
   299  			Time:            h.genDoc.GenesisTime,
   300  			ChainID:         h.genDoc.ChainID,
   301  			ConsensusParams: &csParams,
   302  			Validators:      nextVals,
   303  			AppState:        h.genDoc.AppState,
   304  		}
   305  		res, err := proxyApp.Consensus().InitChainSync(req)
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  
   310  		if stateBlockHeight == 0 { // we only update state when we are in initial state
   311  			// If the app returned validators or consensus params, update the state.
   312  			if len(res.Validators) > 0 {
   313  				vals := types.NewValidatorSetFromABCIValidatorUpdates(res.Validators)
   314  				state.Validators = vals
   315  				state.NextValidators = vals.Copy()
   316  			} else if len(h.genDoc.Validators) == 0 {
   317  				// If validator set is not set in genesis and still empty after InitChain, exit.
   318  				return nil, fmt.Errorf("validator set is nil in genesis and still empty after InitChain")
   319  			}
   320  
   321  			if res.ConsensusParams != nil {
   322  				state.ConsensusParams = state.ConsensusParams.Update(*res.ConsensusParams)
   323  			}
   324  			sm.SaveState(h.stateDB, state)
   325  		}
   326  	}
   327  
   328  	// First handle edge cases and constraints on the storeBlockHeight.
   329  	switch {
   330  	case storeBlockHeight == 0:
   331  		assertAppHashEqualsOneFromState(appHash, state)
   332  		return appHash, nil
   333  
   334  	case storeBlockHeight < appBlockHeight:
   335  		// the app should never be ahead of the store (but this is under app's control)
   336  		return appHash, sm.AppBlockHeightTooHighError{CoreHeight: storeBlockHeight, AppHeight: appBlockHeight}
   337  
   338  	case storeBlockHeight < stateBlockHeight:
   339  		// the state should never be ahead of the store (this is under tendermint's control)
   340  		panic(fmt.Sprintf("StateBlockHeight (%d) > StoreBlockHeight (%d)", stateBlockHeight, storeBlockHeight))
   341  
   342  	case storeBlockHeight > stateBlockHeight+1:
   343  		// store should be at most one ahead of the state (this is under tendermint's control)
   344  		panic(fmt.Sprintf("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1))
   345  	}
   346  
   347  	var err error
   348  	// Now either store is equal to state, or one ahead.
   349  	// For each, consider all cases of where the app could be, given app <= store
   350  	if storeBlockHeight == stateBlockHeight {
   351  		// Tendermint ran Commit and saved the state.
   352  		// Either the app is asking for replay, or we're all synced up.
   353  		if appBlockHeight < storeBlockHeight {
   354  			// the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store)
   355  			return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, false)
   356  		} else if appBlockHeight == storeBlockHeight {
   357  			// We're good!
   358  			assertAppHashEqualsOneFromState(appHash, state)
   359  			return appHash, nil
   360  		}
   361  	} else if storeBlockHeight == stateBlockHeight+1 {
   362  		// We saved the block in the store but haven't updated the state,
   363  		// so we'll need to replay a block using the WAL.
   364  		switch {
   365  		case appBlockHeight < stateBlockHeight:
   366  			// the app is further behind than it should be, so replay blocks
   367  			// but leave the last block to go through the WAL
   368  			return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, true)
   369  
   370  		case appBlockHeight == stateBlockHeight:
   371  			// We haven't run Commit (both the state and app are one block behind),
   372  			// so replayBlock with the real app.
   373  			// NOTE: We could instead use the cs.WAL on cs.Start,
   374  			// but we'd have to allow the WAL to replay a block that wrote it's #ENDHEIGHT
   375  			h.logger.Info("Replay last block using real app")
   376  			state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus())
   377  			return state.AppHash, err
   378  
   379  		case appBlockHeight == storeBlockHeight:
   380  			// We ran Commit, but didn't save the state, so replayBlock with mock app.
   381  			abciResponses, err := sm.LoadABCIResponses(h.stateDB, storeBlockHeight)
   382  			if err != nil {
   383  				return nil, err
   384  			}
   385  			mockApp := newMockProxyApp(appHash, abciResponses)
   386  			h.logger.Info("Replay last block using mock app")
   387  			state, err = h.replayBlock(state, storeBlockHeight, mockApp)
   388  			return state.AppHash, err
   389  		}
   390  	}
   391  
   392  	panic(fmt.Sprintf("uncovered case! appHeight: %d, storeHeight: %d, stateHeight: %d",
   393  		appBlockHeight, storeBlockHeight, stateBlockHeight))
   394  }
   395  
   396  func (h *Handshaker) replayBlocks(state sm.State, proxyApp appconn.AppConns, appBlockHeight, storeBlockHeight int64, mutateState bool) ([]byte, error) {
   397  	// App is further behind than it should be, so we need to replay blocks.
   398  	// We replay all blocks from appBlockHeight+1.
   399  	//
   400  	// Note that we don't have an old version of the state,
   401  	// so we by-pass state validation/mutation using sm.ExecCommitBlock.
   402  	// This also means we won't be saving validator sets if they change during this period.
   403  	// TODO: Load the historical information to fix this and just use state.ApplyBlock
   404  	//
   405  	// If mutateState == true, the final block is replayed with h.replayBlock()
   406  
   407  	var appHash []byte
   408  	var err error
   409  	finalBlock := storeBlockHeight
   410  	if mutateState {
   411  		finalBlock--
   412  	}
   413  	for i := appBlockHeight + 1; i <= finalBlock; i++ {
   414  		h.logger.Info("Applying block", "height", i)
   415  		block := h.store.LoadBlock(i)
   416  		// Extra check to ensure the app was not changed in a way it shouldn't have.
   417  		if len(appHash) > 0 {
   418  			assertAppHashEqualsOneFromBlock(appHash, block)
   419  		}
   420  
   421  		appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.stateDB)
   422  		if err != nil {
   423  			return nil, err
   424  		}
   425  
   426  		h.nBlocks++
   427  	}
   428  
   429  	if mutateState {
   430  		// sync the final block
   431  		state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus())
   432  		if err != nil {
   433  			return nil, err
   434  		}
   435  		appHash = state.AppHash
   436  	}
   437  
   438  	assertAppHashEqualsOneFromState(appHash, state)
   439  	return appHash, nil
   440  }
   441  
   442  // ApplyBlock on the proxyApp with the last block.
   443  func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp appconn.Consensus) (sm.State, error) {
   444  	block := h.store.LoadBlock(height)
   445  	meta := h.store.LoadBlockMeta(height)
   446  
   447  	blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, proxyApp, mock.Mempool{})
   448  	blockExec.SetEventSwitch(h.evsw)
   449  
   450  	var err error
   451  	state, err = blockExec.ApplyBlock(state, meta.BlockID, block)
   452  	if err != nil {
   453  		return sm.State{}, err
   454  	}
   455  
   456  	h.nBlocks++
   457  
   458  	return state, nil
   459  }
   460  
   461  func assertAppHashEqualsOneFromBlock(appHash []byte, block *types.Block) {
   462  	if !bytes.Equal(appHash, block.AppHash) {
   463  		panic(fmt.Sprintf(`block.AppHash does not match AppHash after replay. Got %X, expected %X.
   464  
   465  Block: %v
   466  `,
   467  			appHash, block.AppHash, block))
   468  	}
   469  }
   470  
   471  func assertAppHashEqualsOneFromState(appHash []byte, state sm.State) {
   472  	if !bytes.Equal(appHash, state.AppHash) {
   473  		panic(fmt.Sprintf(`state.AppHash does not match AppHash after replay. Got
   474  %X, expected %X.
   475  
   476  State: %v
   477  
   478  Did you reset Tendermint without resetting your application's data?`,
   479  			appHash, state.AppHash, state))
   480  	}
   481  }
   482  
   483  // --------------------------------------------------------------------------------
   484  // mockProxyApp uses ABCIResponses to give the right results
   485  // Useful because we don't want to call Commit() twice for the same block on the real app.
   486  
   487  func newMockProxyApp(appHash []byte, abciResponses *sm.ABCIResponses) appconn.Consensus {
   488  	clientCreator := proxy.NewLocalClientCreator(&mockProxyApp{
   489  		appHash:       appHash,
   490  		abciResponses: abciResponses,
   491  	})
   492  	cli, _ := clientCreator.NewABCIClient()
   493  	err := cli.Start()
   494  	if err != nil {
   495  		panic(err)
   496  	}
   497  	return appconn.Consensus(cli)
   498  }
   499  
   500  type mockProxyApp struct {
   501  	abci.BaseApplication
   502  
   503  	appHash       []byte
   504  	txCount       int
   505  	abciResponses *sm.ABCIResponses
   506  }
   507  
   508  func (mock *mockProxyApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   509  	r := mock.abciResponses.DeliverTxs[mock.txCount]
   510  	mock.txCount++
   511  	return r
   512  }
   513  
   514  func (mock *mockProxyApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
   515  	mock.txCount = 0
   516  	return mock.abciResponses.EndBlock
   517  }
   518  
   519  func (mock *mockProxyApp) Commit() (res abci.ResponseCommit) {
   520  	res.Data = mock.appHash
   521  	return
   522  }