github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/tendermint/consensus/consensus_commit.go (about)

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/fibonacci-chain/fbc/libs/iavl"
    10  	iavlcfg "github.com/fibonacci-chain/fbc/libs/iavl/config"
    11  	"github.com/fibonacci-chain/fbc/libs/system/trace"
    12  	cfg "github.com/fibonacci-chain/fbc/libs/tendermint/config"
    13  	cstypes "github.com/fibonacci-chain/fbc/libs/tendermint/consensus/types"
    14  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/fail"
    15  	tmos "github.com/fibonacci-chain/fbc/libs/tendermint/libs/os"
    16  	sm "github.com/fibonacci-chain/fbc/libs/tendermint/state"
    17  	"github.com/fibonacci-chain/fbc/libs/tendermint/types"
    18  	tmtime "github.com/fibonacci-chain/fbc/libs/tendermint/types/time"
    19  )
    20  
    21  func (cs *State) dumpElapsed(trc *trace.Tracer, schema string) {
    22  	trace.GetElapsedInfo().AddInfo(schema, trc.Format())
    23  	trc.Reset()
    24  }
    25  
    26  func (cs *State) initNewHeight() {
    27  	// waiting finished and enterNewHeight by timeoutNewHeight
    28  	if cs.Step == cstypes.RoundStepNewHeight {
    29  		// init StartTime
    30  		cs.StartTime = tmtime.Now()
    31  		cs.dumpElapsed(cs.blockTimeTrc, trace.LastBlockTime)
    32  		cs.traceDump()
    33  	}
    34  }
    35  
    36  func (cs *State) traceDump() {
    37  	if cs.Logger == nil {
    38  		return
    39  	}
    40  
    41  	trace.GetElapsedInfo().AddInfo(trace.CommitRound, fmt.Sprintf("%d", cs.CommitRound))
    42  	trace.GetElapsedInfo().AddInfo(trace.Round, fmt.Sprintf("%d", cs.Round))
    43  	trace.GetElapsedInfo().AddInfo(trace.BlockParts, fmt.Sprintf("%d|%d|%d|%d/%d",
    44  		cs.bt.droppedDue2WrongHeight,
    45  		cs.bt.droppedDue2NotExpected,
    46  		cs.bt.droppedDue2Error,
    47  		cs.bt.droppedDue2NotAdded,
    48  		cs.bt.totalParts,
    49  	))
    50  
    51  	trace.GetElapsedInfo().AddInfo(trace.BlockPartsP2P, fmt.Sprintf("%d|%d|%d",
    52  		cs.bt.bpNOTransByACK, cs.bt.bpNOTransByData, cs.bt.bpSend))
    53  
    54  	trace.GetElapsedInfo().AddInfo(trace.Produce, cs.trc.Format())
    55  	trace.GetElapsedInfo().Dump(cs.Logger.With("module", "main"))
    56  	cs.trc.Reset()
    57  }
    58  
    59  // Enter: +2/3 precommits for block
    60  func (cs *State) enterCommit(height int64, commitRound int) {
    61  	logger := cs.Logger.With("height", height, "commitRound", commitRound)
    62  
    63  	if cs.Height != height || cstypes.RoundStepCommit <= cs.Step {
    64  		logger.Debug(fmt.Sprintf(
    65  			"enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v",
    66  			height,
    67  			commitRound,
    68  			cs.Height,
    69  			cs.Round,
    70  			cs.Step))
    71  		return
    72  	}
    73  
    74  	cs.initNewHeight()
    75  	cs.trc.Pin("%s-%d", "Commit", cs.Round)
    76  
    77  	logger.Info(fmt.Sprintf("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
    78  
    79  	defer func() {
    80  		// Done enterCommit:
    81  		// keep cs.Round the same, commitRound points to the right Precommits set.
    82  		cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit)
    83  		cs.CommitRound = commitRound
    84  		cs.newStep()
    85  
    86  		// Maybe finalize immediately.
    87  		cs.tryFinalizeCommit(height)
    88  	}()
    89  
    90  	blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority()
    91  	if !ok {
    92  		panic("RunActionCommit() expects +2/3 precommits")
    93  	}
    94  
    95  	// The Locked* fields no longer matter.
    96  	// Move them over to ProposalBlock if they match the commit hash,
    97  	// otherwise they'll be cleared in updateToState.
    98  	if cs.LockedBlock.HashesTo(blockID.Hash) {
    99  		logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash)
   100  		cs.ProposalBlock = cs.LockedBlock
   101  		cs.ProposalBlockParts = cs.LockedBlockParts
   102  	}
   103  
   104  	// If we don't have the block being committed, set up to get it.
   105  	if !cs.ProposalBlock.HashesTo(blockID.Hash) {
   106  		if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
   107  			logger.Info(
   108  				"Commit is for a block we don't know about. Set ProposalBlock=nil",
   109  				"proposal",
   110  				cs.ProposalBlock.Hash(),
   111  				"commit",
   112  				blockID.Hash)
   113  			// We're getting the wrong block.
   114  			// Set up ProposalBlockParts and keep waiting.
   115  			cs.ProposalBlock = nil
   116  			cs.Logger.Info("enterCommit proposalBlockPart reset ,because of mismatch hash,",
   117  				"origin", hex.EncodeToString(cs.ProposalBlockParts.Hash()), "after", blockID.Hash)
   118  			cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader)
   119  			cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent())
   120  			cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState)
   121  		}
   122  		// else {
   123  		// We just need to keep waiting.
   124  		// }
   125  	}
   126  }
   127  
   128  // If we have the block AND +2/3 commits for it, finalize.
   129  func (cs *State) tryFinalizeCommit(height int64) {
   130  	logger := cs.Logger.With("height", height)
   131  
   132  	if cs.Height != height {
   133  		panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
   134  	}
   135  
   136  	blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
   137  	if !ok || len(blockID.Hash) == 0 {
   138  		logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.")
   139  		return
   140  	}
   141  	if !cs.ProposalBlock.HashesTo(blockID.Hash) {
   142  		// TODO: this happens every time if we're not a validator (ugly logs)
   143  		// TODO: ^^ wait, why does it matter that we're a validator?
   144  		logger.Info(
   145  			"Attempt to finalize failed. We don't have the commit block.",
   146  			"proposal-block",
   147  			cs.ProposalBlock.Hash(),
   148  			"commit-block",
   149  			blockID.Hash)
   150  		return
   151  	}
   152  
   153  	//	go
   154  	cs.finalizeCommit(height)
   155  }
   156  
   157  // Increment height and goto cstypes.RoundStepNewHeight
   158  func (cs *State) finalizeCommit(height int64) {
   159  	if cs.Height != height || cs.Step != cstypes.RoundStepCommit {
   160  		cs.Logger.Debug(fmt.Sprintf(
   161  			"finalizeCommit(%v): Invalid args. Current step: %v/%v/%v",
   162  			height,
   163  			cs.Height,
   164  			cs.Round,
   165  			cs.Step))
   166  		return
   167  	}
   168  
   169  	blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
   170  	block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts
   171  
   172  	if !ok {
   173  		panic(fmt.Sprintf("Cannot finalizeCommit, commit does not have two thirds majority"))
   174  	}
   175  	if !blockParts.HasHeader(blockID.PartsHeader) {
   176  		panic(fmt.Sprintf("Expected ProposalBlockParts header to be commit header"))
   177  	}
   178  	if !block.HashesTo(blockID.Hash) {
   179  		panic(fmt.Sprintf("Cannot finalizeCommit, ProposalBlock does not hash to commit hash"))
   180  	}
   181  	if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil {
   182  		panic(fmt.Sprintf("+2/3 committed an invalid block: %v", err))
   183  	}
   184  
   185  	cs.Logger.Info("Finalizing commit of block with N txs",
   186  		"height", block.Height,
   187  		"hash", block.Hash(),
   188  		"root", block.AppHash,
   189  		"N", len(block.Txs))
   190  	cs.Logger.Info(fmt.Sprintf("%v", block))
   191  
   192  	fail.Fail() // XXX
   193  
   194  	// Save to blockStore.
   195  	blockTime := block.Time
   196  	if cs.blockStore.Height() < block.Height {
   197  		// NOTE: the seenCommit is local justification to commit this block,
   198  		// but may differ from the LastCommit included in the next block
   199  		precommits := cs.Votes.Precommits(cs.CommitRound)
   200  		seenCommit := precommits.MakeCommit()
   201  		blockTime = sm.MedianTime(seenCommit, cs.Validators)
   202  		cs.blockStore.SaveBlock(block, blockParts, seenCommit)
   203  	} else {
   204  		// Happens during replay if we already saved the block but didn't commit
   205  		cs.Logger.Info("Calling finalizeCommit on already stored block", "height", block.Height)
   206  	}
   207  	trace.GetElapsedInfo().AddInfo(trace.BTInterval, fmt.Sprintf("%dms", blockTime.Sub(block.Time).Milliseconds()))
   208  
   209  	fail.Fail() // XXX
   210  
   211  	// Write EndHeightMessage{} for this height, implying that the blockstore
   212  	// has saved the block.
   213  	//
   214  	// If we crash before writing this EndHeightMessage{}, we will recover by
   215  	// running ApplyBlock during the ABCI handshake when we restart.  If we
   216  	// didn't save the block to the blockstore before writing
   217  	// EndHeightMessage{}, we'd have to change WAL replay -- currently it
   218  	// complains about replaying for heights where an #ENDHEIGHT entry already
   219  	// exists.
   220  	//
   221  	// Either way, the State should not be resumed until we
   222  	// successfully call ApplyBlock (ie. later here, or in Handshake after
   223  	// restart).
   224  	endMsg := EndHeightMessage{height}
   225  	if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync
   226  		panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node",
   227  			endMsg, err))
   228  	}
   229  
   230  	fail.Fail() // XXX
   231  
   232  	// Create a copy of the state for staging and an event cache for txs.
   233  	stateCopy := cs.state.Copy()
   234  
   235  	// Execute and commit the block, update and save the state, and update the mempool.
   236  	// NOTE The block.AppHash wont reflect these txs until the next block.
   237  
   238  	var err error
   239  	var retainHeight int64
   240  
   241  	cs.trc.Pin("%s", trace.ApplyBlock)
   242  
   243  	if iavl.EnableAsyncCommit {
   244  		cs.handleCommitGapOffset(height)
   245  	}
   246  
   247  	stateCopy, retainHeight, err = cs.blockExec.ApplyBlock(
   248  		stateCopy,
   249  		types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()},
   250  		block)
   251  	if err != nil {
   252  		cs.Logger.Error("Error on ApplyBlock. Did the application crash? Please restart tendermint", "err", err)
   253  		err := tmos.Kill()
   254  		if err != nil {
   255  			cs.Logger.Error("Failed to kill this process - please do so manually", "err", err)
   256  		}
   257  		return
   258  	}
   259  
   260  	//reset offset after commitGap
   261  	if iavl.EnableAsyncCommit &&
   262  		height%iavlcfg.DynamicConfig.GetCommitGapHeight() == iavl.GetFinalCommitGapOffset() {
   263  		iavl.SetFinalCommitGapOffset(0)
   264  	}
   265  
   266  	fail.Fail() // XXX
   267  
   268  	cs.trc.Pin("%s", trace.UpdateState)
   269  
   270  	// Prune old heights, if requested by ABCI app.
   271  	if retainHeight > 0 {
   272  		pruned, err := cs.pruneBlocks(retainHeight)
   273  		if err != nil {
   274  			cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err)
   275  		} else {
   276  			cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight)
   277  		}
   278  	}
   279  
   280  	// must be called before we update state
   281  	cs.recordMetrics(height, block)
   282  
   283  	// NewHeightStep!
   284  	cs.stateMtx.Lock()
   285  	cs.updateToState(stateCopy)
   286  	cs.stateMtx.Unlock()
   287  
   288  	fail.Fail() // XXX
   289  
   290  	// Private validator might have changed it's key pair => refetch pubkey.
   291  	if err := cs.updatePrivValidatorPubKey(); err != nil {
   292  		cs.Logger.Error("Can't get private validator pubkey", "err", err)
   293  	}
   294  
   295  	// publish event
   296  	if types.EnableEventBlockTime {
   297  		cs.blockExec.FireBlockTimeEvents(block.Height, len(block.Txs), false)
   298  	}
   299  
   300  	cs.trc.Pin("%s", trace.Waiting)
   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  }