github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/replay.go (about)

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