github.com/okex/exchain@v1.8.0/libs/tendermint/consensus/consensus_commit.go (about)

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"github.com/okex/exchain/libs/iavl"
     8  	iavlcfg "github.com/okex/exchain/libs/iavl/config"
     9  	"github.com/okex/exchain/libs/system/trace"
    10  	cfg "github.com/okex/exchain/libs/tendermint/config"
    11  	cstypes "github.com/okex/exchain/libs/tendermint/consensus/types"
    12  	"github.com/okex/exchain/libs/tendermint/libs/fail"
    13  	tmos "github.com/okex/exchain/libs/tendermint/libs/os"
    14  	sm "github.com/okex/exchain/libs/tendermint/state"
    15  	"github.com/okex/exchain/libs/tendermint/types"
    16  	tmtime "github.com/okex/exchain/libs/tendermint/types/time"
    17  	"time"
    18  )
    19  
    20  func (cs *State) dumpElapsed(trc *trace.Tracer, schema string) {
    21  	trace.GetElapsedInfo().AddInfo(schema, trc.Format())
    22  	trc.Reset()
    23  }
    24  
    25  func (cs *State) initNewHeight() {
    26  	// waiting finished and enterNewHeight by timeoutNewHeight
    27  	if cs.Step == cstypes.RoundStepNewHeight {
    28  		// init StartTime
    29  		cs.StartTime = tmtime.Now()
    30  		cs.dumpElapsed(cs.blockTimeTrc, trace.LastBlockTime)
    31  		cs.traceDump()
    32  	}
    33  }
    34  
    35  func (cs *State) traceDump() {
    36  	if cs.Logger == nil {
    37  		return
    38  	}
    39  
    40  	trace.GetElapsedInfo().AddInfo(trace.CommitRound, fmt.Sprintf("%d", cs.CommitRound))
    41  	trace.GetElapsedInfo().AddInfo(trace.Round, fmt.Sprintf("%d", cs.Round))
    42  	trace.GetElapsedInfo().AddInfo(trace.BlockParts, fmt.Sprintf("%d|%d|%d|%d/%d",
    43  		cs.bt.droppedDue2WrongHeight,
    44  		cs.bt.droppedDue2NotExpected,
    45  		cs.bt.droppedDue2Error,
    46  		cs.bt.droppedDue2NotAdded,
    47  		cs.bt.totalParts,
    48  	))
    49  
    50  	trace.GetElapsedInfo().AddInfo(trace.BlockPartsP2P, fmt.Sprintf("%d|%d|%d",
    51  		cs.bt.bpNOTransByACK, cs.bt.bpNOTransByData, cs.bt.bpSend))
    52  
    53  	trace.GetElapsedInfo().AddInfo(trace.Produce, cs.trc.Format())
    54  	trace.GetElapsedInfo().Dump(cs.Logger.With("module", "main"))
    55  	cs.trc.Reset()
    56  }
    57  
    58  // Enter: +2/3 precommits for block
    59  func (cs *State) enterCommit(height int64, commitRound int) {
    60  	logger := cs.Logger.With("height", height, "commitRound", commitRound)
    61  
    62  	if cs.Height != height || cstypes.RoundStepCommit <= cs.Step {
    63  		logger.Debug(fmt.Sprintf(
    64  			"enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v",
    65  			height,
    66  			commitRound,
    67  			cs.Height,
    68  			cs.Round,
    69  			cs.Step))
    70  		return
    71  	}
    72  
    73  	cs.initNewHeight()
    74  	cs.trc.Pin("%s-%d", "Commit", cs.Round)
    75  
    76  	logger.Info(fmt.Sprintf("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
    77  
    78  	defer func() {
    79  		// Done enterCommit:
    80  		// keep cs.Round the same, commitRound points to the right Precommits set.
    81  		cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit)
    82  		cs.CommitRound = commitRound
    83  		cs.newStep()
    84  
    85  		// Maybe finalize immediately.
    86  		cs.tryFinalizeCommit(height)
    87  	}()
    88  
    89  	blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority()
    90  	if !ok {
    91  		panic("RunActionCommit() expects +2/3 precommits")
    92  	}
    93  
    94  	// The Locked* fields no longer matter.
    95  	// Move them over to ProposalBlock if they match the commit hash,
    96  	// otherwise they'll be cleared in updateToState.
    97  	if cs.LockedBlock.HashesTo(blockID.Hash) {
    98  		logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash)
    99  		cs.ProposalBlock = cs.LockedBlock
   100  		cs.ProposalBlockParts = cs.LockedBlockParts
   101  	}
   102  
   103  	// If we don't have the block being committed, set up to get it.
   104  	if !cs.ProposalBlock.HashesTo(blockID.Hash) {
   105  		if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
   106  			logger.Info(
   107  				"Commit is for a block we don't know about. Set ProposalBlock=nil",
   108  				"proposal",
   109  				cs.ProposalBlock.Hash(),
   110  				"commit",
   111  				blockID.Hash)
   112  			// We're getting the wrong block.
   113  			// Set up ProposalBlockParts and keep waiting.
   114  			cs.ProposalBlock = nil
   115  			cs.Logger.Info("enterCommit proposalBlockPart reset ,because of mismatch hash,",
   116  				"origin", hex.EncodeToString(cs.ProposalBlockParts.Hash()), "after", blockID.Hash)
   117  			cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader)
   118  			cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent())
   119  			cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState)
   120  		}
   121  		// else {
   122  		// We just need to keep waiting.
   123  		// }
   124  	}
   125  }
   126  
   127  // If we have the block AND +2/3 commits for it, finalize.
   128  func (cs *State) tryFinalizeCommit(height int64) {
   129  	logger := cs.Logger.With("height", height)
   130  
   131  	if cs.Height != height {
   132  		panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
   133  	}
   134  
   135  	blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
   136  	if !ok || len(blockID.Hash) == 0 {
   137  		logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.")
   138  		return
   139  	}
   140  	if !cs.ProposalBlock.HashesTo(blockID.Hash) {
   141  		// TODO: this happens every time if we're not a validator (ugly logs)
   142  		// TODO: ^^ wait, why does it matter that we're a validator?
   143  		logger.Info(
   144  			"Attempt to finalize failed. We don't have the commit block.",
   145  			"proposal-block",
   146  			cs.ProposalBlock.Hash(),
   147  			"commit-block",
   148  			blockID.Hash)
   149  		return
   150  	}
   151  
   152  	//	go
   153  	cs.finalizeCommit(height)
   154  }
   155  
   156  // Increment height and goto cstypes.RoundStepNewHeight
   157  func (cs *State) finalizeCommit(height int64) {
   158  	if cs.Height != height || cs.Step != cstypes.RoundStepCommit {
   159  		cs.Logger.Debug(fmt.Sprintf(
   160  			"finalizeCommit(%v): Invalid args. Current step: %v/%v/%v",
   161  			height,
   162  			cs.Height,
   163  			cs.Round,
   164  			cs.Step))
   165  		return
   166  	}
   167  
   168  	blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
   169  	block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts
   170  
   171  	if !ok {
   172  		panic(fmt.Sprintf("Cannot finalizeCommit, commit does not have two thirds majority"))
   173  	}
   174  	if !blockParts.HasHeader(blockID.PartsHeader) {
   175  		panic(fmt.Sprintf("Expected ProposalBlockParts header to be commit header"))
   176  	}
   177  	if !block.HashesTo(blockID.Hash) {
   178  		panic(fmt.Sprintf("Cannot finalizeCommit, ProposalBlock does not hash to commit hash"))
   179  	}
   180  	if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil {
   181  		panic(fmt.Sprintf("+2/3 committed an invalid block: %v", err))
   182  	}
   183  
   184  	cs.Logger.Info("Finalizing commit of block with N txs",
   185  		"height", block.Height,
   186  		"hash", block.Hash(),
   187  		"root", block.AppHash,
   188  		"N", len(block.Txs))
   189  	cs.Logger.Info(fmt.Sprintf("%v", block))
   190  
   191  	fail.Fail() // XXX
   192  
   193  	// Save to blockStore.
   194  	blockTime := block.Time
   195  	if cs.blockStore.Height() < block.Height {
   196  		// NOTE: the seenCommit is local justification to commit this block,
   197  		// but may differ from the LastCommit included in the next block
   198  		precommits := cs.Votes.Precommits(cs.CommitRound)
   199  		seenCommit := precommits.MakeCommit()
   200  		blockTime = sm.MedianTime(seenCommit, cs.Validators)
   201  		cs.blockStore.SaveBlock(block, blockParts, seenCommit)
   202  	} else {
   203  		// Happens during replay if we already saved the block but didn't commit
   204  		cs.Logger.Info("Calling finalizeCommit on already stored block", "height", block.Height)
   205  	}
   206  	trace.GetElapsedInfo().AddInfo(trace.BTInterval, fmt.Sprintf("%dms", blockTime.Sub(block.Time).Milliseconds()))
   207  
   208  	fail.Fail() // XXX
   209  
   210  	// Write EndHeightMessage{} for this height, implying that the blockstore
   211  	// has saved the block.
   212  	//
   213  	// If we crash before writing this EndHeightMessage{}, we will recover by
   214  	// running ApplyBlock during the ABCI handshake when we restart.  If we
   215  	// didn't save the block to the blockstore before writing
   216  	// EndHeightMessage{}, we'd have to change WAL replay -- currently it
   217  	// complains about replaying for heights where an #ENDHEIGHT entry already
   218  	// exists.
   219  	//
   220  	// Either way, the State should not be resumed until we
   221  	// successfully call ApplyBlock (ie. later here, or in Handshake after
   222  	// restart).
   223  	endMsg := EndHeightMessage{height}
   224  	if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync
   225  		panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node",
   226  			endMsg, err))
   227  	}
   228  
   229  	fail.Fail() // XXX
   230  
   231  	// Create a copy of the state for staging and an event cache for txs.
   232  	stateCopy := cs.state.Copy()
   233  
   234  	// Execute and commit the block, update and save the state, and update the mempool.
   235  	// NOTE The block.AppHash wont reflect these txs until the next block.
   236  
   237  	var err error
   238  	var retainHeight int64
   239  
   240  	cs.trc.Pin("%s", trace.ApplyBlock)
   241  
   242  	if iavl.EnableAsyncCommit {
   243  		cs.handleCommitGapOffset(height)
   244  	}
   245  
   246  	stateCopy, retainHeight, err = cs.blockExec.ApplyBlock(
   247  		stateCopy,
   248  		types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()},
   249  		block)
   250  	if err != nil {
   251  		cs.Logger.Error("Error on ApplyBlock. Did the application crash? Please restart tendermint", "err", err)
   252  		err := tmos.Kill()
   253  		if err != nil {
   254  			cs.Logger.Error("Failed to kill this process - please do so manually", "err", err)
   255  		}
   256  		return
   257  	}
   258  
   259  	//reset offset after commitGap
   260  	if iavl.EnableAsyncCommit &&
   261  		height%iavlcfg.DynamicConfig.GetCommitGapHeight() == iavl.GetFinalCommitGapOffset() {
   262  		iavl.SetFinalCommitGapOffset(0)
   263  	}
   264  
   265  	fail.Fail() // XXX
   266  
   267  	cs.trc.Pin("%s", trace.UpdateState)
   268  
   269  	// Prune old heights, if requested by ABCI app.
   270  	if retainHeight > 0 {
   271  		pruned, err := cs.pruneBlocks(retainHeight)
   272  		if err != nil {
   273  			cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err)
   274  		} else {
   275  			cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight)
   276  		}
   277  	}
   278  
   279  	// must be called before we update state
   280  	cs.recordMetrics(height, block)
   281  
   282  	// NewHeightStep!
   283  	cs.stateMtx.Lock()
   284  	cs.updateToState(stateCopy)
   285  	cs.stateMtx.Unlock()
   286  
   287  	fail.Fail() // XXX
   288  
   289  	// Private validator might have changed it's key pair => refetch pubkey.
   290  	if err := cs.updatePrivValidatorPubKey(); err != nil {
   291  		cs.Logger.Error("Can't get private validator pubkey", "err", err)
   292  	}
   293  
   294  	// publish event
   295  	if types.EnableEventBlockTime {
   296  		cs.blockExec.FireBlockTimeEvents(block.Height, len(block.Txs), false)
   297  	}
   298  
   299  	cs.trc.Pin("%s", trace.Waiting)
   300  
   301  	// cs.StartTime is already set.
   302  	// Schedule Round0 to start soon.
   303  	cs.scheduleRound0(&cs.RoundState)
   304  
   305  	// By here,
   306  	// * cs.Height has been increment to height+1
   307  	// * cs.Step is now cstypes.RoundStepNewHeight
   308  	// * cs.StartTime is set to when we will start round0.
   309  }
   310  
   311  // Updates State and increments height to match that of state.
   312  // The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight.
   313  func (cs *State) updateToState(state sm.State) {
   314  	// Do not consider this situation that the consensus machine was stopped
   315  	// when the fast-sync mode opens. So remove it!
   316  	//if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight {
   317  	//	panic(fmt.Sprintf("updateToState() expected state height of %v but found %v",
   318  	//		cs.Height, state.LastBlockHeight))
   319  	//}
   320  	//if !cs.state.IsEmpty() && cs.state.LastBlockHeight+1 != cs.Height {
   321  	//	// This might happen when someone else is mutating cs.state.
   322  	//	// Someone forgot to pass in state.Copy() somewhere?!
   323  	//	panic(fmt.Sprintf("Inconsistent cs.state.LastBlockHeight+1 %v vs cs.Height %v",
   324  	//		cs.state.LastBlockHeight+1, cs.Height))
   325  	//}
   326  
   327  	cs.HasVC = false
   328  	if cs.vcMsg != nil && cs.vcMsg.Height <= cs.Height {
   329  		cs.vcMsg = nil
   330  	}
   331  	for k, _ := range cs.vcHeight {
   332  		if k <= cs.Height {
   333  			delete(cs.vcHeight, k)
   334  		}
   335  	}
   336  	select {
   337  	case <-cs.taskResultChan:
   338  	default:
   339  	}
   340  
   341  	// If state isn't further out than cs.state, just ignore.
   342  	// This happens when SwitchToConsensus() is called in the reactor.
   343  	// We don't want to reset e.g. the Votes, but we still want to
   344  	// signal the new round step, because other services (eg. txNotifier)
   345  	// depend on having an up-to-date peer state!
   346  	if !cs.state.IsEmpty() && (state.LastBlockHeight <= cs.state.LastBlockHeight) {
   347  		cs.Logger.Info(
   348  			"Ignoring updateToState()",
   349  			"newHeight",
   350  			state.LastBlockHeight+1,
   351  			"oldHeight",
   352  			cs.state.LastBlockHeight+1)
   353  		cs.newStep()
   354  		return
   355  	}
   356  
   357  	// Reset fields based on state.
   358  	validators := state.Validators
   359  	switch {
   360  	case state.LastBlockHeight == types.GetStartBlockHeight(): // Very first commit should be empty.
   361  		cs.LastCommit = (*types.VoteSet)(nil)
   362  	case cs.CommitRound > -1 && cs.Votes != nil: // Otherwise, use cs.Votes
   363  		if !cs.Votes.Precommits(cs.CommitRound).HasTwoThirdsMajority() {
   364  			panic(fmt.Sprintf(
   365  				"wanted to form a commit, but precommits (H/R: %d/%d) didn't have 2/3+: %v",
   366  				state.LastBlockHeight, cs.CommitRound, cs.Votes.Precommits(cs.CommitRound),
   367  			))
   368  		}
   369  
   370  		cs.LastCommit = cs.Votes.Precommits(cs.CommitRound)
   371  
   372  	case cs.LastCommit == nil:
   373  		// NOTE: when Tendermint starts, it has no votes. reconstructLastCommit
   374  		// must be called to reconstruct LastCommit from SeenCommit.
   375  		panic(fmt.Sprintf(
   376  			"last commit cannot be empty after initial block (H:%d)",
   377  			state.LastBlockHeight+1,
   378  		))
   379  	}
   380  
   381  	// Next desired block height
   382  	height := state.LastBlockHeight + 1
   383  
   384  	// RoundState fields
   385  	cs.updateHeight(height)
   386  	cs.updateRoundStep(0, cstypes.RoundStepNewHeight)
   387  	cs.bt.reset(height)
   388  
   389  	cs.Validators = validators
   390  	cs.Proposal = nil
   391  	cs.ProposalBlock = nil
   392  	cs.ProposalBlockParts = nil
   393  	cs.LockedRound = -1
   394  	cs.LockedBlock = nil
   395  	cs.LockedBlockParts = nil
   396  	cs.ValidRound = -1
   397  	cs.ValidBlock = nil
   398  	cs.ValidBlockParts = nil
   399  	cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators)
   400  	cs.CommitRound = -1
   401  	cs.LastValidators = state.LastValidators
   402  	cs.TriggeredTimeoutPrecommit = false
   403  	cs.state = state
   404  
   405  	// Finally, broadcast RoundState
   406  	cs.newStep()
   407  }
   408  
   409  func (cs *State) updateHeight(height int64) {
   410  	cs.metrics.Height.Set(float64(height))
   411  	cs.Height = height
   412  }
   413  
   414  func (cs *State) pruneBlocks(retainHeight int64) (uint64, error) {
   415  	base := cs.blockStore.Base()
   416  	if retainHeight <= base {
   417  		return 0, nil
   418  	}
   419  	pruned, err := cs.blockStore.PruneBlocks(retainHeight)
   420  	if err != nil {
   421  		return 0, fmt.Errorf("failed to prune block store: %w", err)
   422  	}
   423  	err = sm.PruneStates(cs.blockExec.DB(), base, retainHeight)
   424  	if err != nil {
   425  		return 0, fmt.Errorf("failed to prune state database: %w", err)
   426  	}
   427  	return pruned, nil
   428  }
   429  
   430  func (cs *State) preMakeBlock(height int64, waiting time.Duration) {
   431  	tNow := tmtime.Now()
   432  	block, blockParts := cs.createProposalBlock()
   433  	cs.taskResultChan <- &preBlockTaskRes{block: block, blockParts: blockParts}
   434  
   435  	propBlockID := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()}
   436  	proposal := types.NewProposal(height, 0, cs.ValidRound, propBlockID)
   437  
   438  	if cs.Height != height {
   439  		return
   440  	}
   441  	isBlockProducer, _ := cs.isBlockProducer()
   442  	if GetActiveVC() && isBlockProducer != "y" {
   443  		// request for proposer of new height
   444  		prMsg := ProposeRequestMessage{Height: height, CurrentProposer: cs.Validators.GetProposer().Address, NewProposer: cs.privValidatorPubKey.Address(), Proposal: proposal}
   445  		go func() {
   446  			time.Sleep(waiting - tmtime.Now().Sub(tNow))
   447  			cs.requestForProposer(prMsg)
   448  		}()
   449  	}
   450  }
   451  
   452  func (cs *State) getPreBlockResult(height int64) *preBlockTaskRes {
   453  	if !GetActiveVC() {
   454  		return nil
   455  	}
   456  	t := time.NewTimer(time.Second)
   457  	for {
   458  		select {
   459  		case res := <-cs.taskResultChan:
   460  			if res.block.Height == height {
   461  				if !t.Stop() {
   462  					<-t.C
   463  				}
   464  				return res
   465  			} else {
   466  				return nil
   467  			}
   468  		case <-t.C:
   469  			return nil
   470  		}
   471  
   472  	}
   473  }
   474  
   475  // handle AC offset to avoid block proposal
   476  func (cs *State) handleCommitGapOffset(height int64) {
   477  	commitGap := iavlcfg.DynamicConfig.GetCommitGapHeight()
   478  	offset := cfg.DynamicConfig.GetCommitGapOffset()
   479  
   480  	// close offset
   481  	if offset <= 0 || (commitGap <= offset) {
   482  		iavl.SetFinalCommitGapOffset(0)
   483  		// only try to offset at commitGap height
   484  	} else if (height % commitGap) == 0 {
   485  		selfAddress := cs.privValidatorPubKey.Address()
   486  		futureValidators := cs.state.Validators.Copy()
   487  
   488  		var i int64
   489  		for ; i < offset; i++ {
   490  			futureBPAddress := futureValidators.GetProposer().Address
   491  
   492  			// self is the validator at the offset height
   493  			if bytes.Equal(futureBPAddress, selfAddress) {
   494  				// trigger ac ahead of the offset
   495  				iavl.SetFinalCommitGapOffset(i + 1)
   496  				//originACHeight|newACHeight|nextProposeHeight|Offset
   497  				trace.GetElapsedInfo().AddInfo(trace.ACOffset, fmt.Sprintf("%d|%d|%d|%d|",
   498  					height, height+i+1, height+i, offset))
   499  				break
   500  			}
   501  			futureValidators.IncrementProposerPriority(1)
   502  		}
   503  	}
   504  }