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

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	cfg "github.com/okex/exchain/libs/tendermint/config"
     9  	cstypes "github.com/okex/exchain/libs/tendermint/consensus/types"
    10  	"github.com/okex/exchain/libs/tendermint/libs/automation"
    11  	"github.com/okex/exchain/libs/tendermint/p2p"
    12  	"github.com/okex/exchain/libs/tendermint/types"
    13  )
    14  
    15  // SetProposal inputs a proposal.
    16  func (cs *State) SetProposal(proposal *types.Proposal, peerID p2p.ID) error {
    17  
    18  	if peerID == "" {
    19  		cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""}
    20  	} else {
    21  		cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID}
    22  	}
    23  
    24  	// TODO: wait for event?!
    25  	return nil
    26  }
    27  
    28  // AddProposalBlockPart inputs a part of the proposal block.
    29  func (cs *State) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error {
    30  	if peerID == "" {
    31  		cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""}
    32  	} else {
    33  		cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID}
    34  	}
    35  
    36  	// TODO: wait for event?!
    37  	return nil
    38  }
    39  
    40  // SetProposalAndBlock inputs the proposal and all block parts.
    41  func (cs *State) SetProposalAndBlock(
    42  	proposal *types.Proposal,
    43  	block *types.Block,
    44  	parts *types.PartSet,
    45  	peerID p2p.ID,
    46  ) error {
    47  	if err := cs.SetProposal(proposal, peerID); err != nil {
    48  		return err
    49  	}
    50  	for i := 0; i < parts.Total(); i++ {
    51  		part := parts.GetPart(i)
    52  		if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil {
    53  			return err
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  func (cs *State) isBlockProducer() (string, string) {
    60  	const len2display int = 6
    61  	bpAddr := cs.Validators.GetProposer().Address
    62  	bpStr := bpAddr.String()
    63  	if len(bpStr) > len2display {
    64  		bpStr = bpStr[:len2display]
    65  	}
    66  	isBlockProducer := "n"
    67  	if cs.privValidator != nil && cs.privValidatorPubKey != nil {
    68  		address := cs.privValidatorPubKey.Address()
    69  
    70  		if bytes.Equal(bpAddr, address) {
    71  			isBlockProducer = "y"
    72  		}
    73  	}
    74  
    75  	return isBlockProducer, strings.ToLower(bpStr)
    76  }
    77  
    78  // Enter (CreateEmptyBlocks): from enterNewRound(height,round)
    79  // Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ):
    80  // 		after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval
    81  // Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool
    82  func (cs *State) enterPropose(height int64, round int) {
    83  	logger := cs.Logger.With("height", height, "round", round)
    84  	if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) {
    85  		logger.Debug(fmt.Sprintf(
    86  			"enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v",
    87  			height,
    88  			round,
    89  			cs.Height,
    90  			cs.Round,
    91  			cs.Step))
    92  		return
    93  	}
    94  
    95  	cs.initNewHeight()
    96  	isBlockProducer, bpAddr := cs.isBlockProducer()
    97  
    98  	cs.stateMtx.RLock()
    99  	cs.updateRoundStep(round, cstypes.RoundStepPropose)
   100  	newProposer := ""
   101  	if cs.vcHeight[height] != "" && cs.Round == 0 {
   102  		newProposer = "-avc-" + cs.vcHeight[height][:6]
   103  	}
   104  	cs.stateMtx.RUnlock()
   105  	cs.trc.Pin("enterPropose-%d-%s-%s%s", round, isBlockProducer, bpAddr, newProposer)
   106  
   107  	logger.Info(fmt.Sprintf("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
   108  
   109  	defer func() {
   110  		// Done enterPropose:
   111  		cs.newStep()
   112  
   113  		// If we have the whole proposal + POL, then goto Prevote now.
   114  		// else, we'll enterPrevote when the rest of the proposal is received (in AddProposalBlockPart),
   115  		// or else after timeoutPropose
   116  		if cs.isProposalComplete() {
   117  			cs.enterPrevote(height, cs.Round)
   118  		}
   119  	}()
   120  
   121  	// If we don't get the proposal and all block parts quick enough, enterPrevote
   122  	cs.timeoutTicker.ScheduleTimeout(timeoutInfo{Duration: cs.config.Propose(round), Height: height, Round: round, Step: cstypes.RoundStepPropose, ActiveViewChange: cs.HasVC})
   123  
   124  	if isBlockProducer == "y" {
   125  		logger.Info("enterPropose: Our turn to propose",
   126  			"proposer",
   127  			bpAddr,
   128  			"privValidator",
   129  			cs.privValidator)
   130  		if newProposer == "" || cs.Round != 0 {
   131  			cs.decideProposal(height, round)
   132  		}
   133  	} else {
   134  		logger.Info("enterPropose: Not our turn to propose",
   135  			"proposer",
   136  			cs.Validators.GetProposer().Address,
   137  			"privValidator",
   138  			cs.privValidator)
   139  	}
   140  }
   141  
   142  func (cs *State) isProposer(address []byte) bool {
   143  	return bytes.Equal(cs.Validators.GetProposer().Address, address)
   144  }
   145  
   146  func (cs *State) defaultDecideProposal(height int64, round int) {
   147  	var block *types.Block
   148  	var blockParts *types.PartSet
   149  
   150  	// Decide on block
   151  	if cs.ValidBlock != nil {
   152  		// If there is valid block, choose that.
   153  		block, blockParts = cs.ValidBlock, cs.ValidBlockParts
   154  	} else {
   155  		// Create a new proposal block from state/txs from the mempool.
   156  		if res := cs.getPreBlockResult(height); res != nil {
   157  			block, blockParts = res.block, res.blockParts
   158  		} else {
   159  			block, blockParts = cs.createProposalBlock()
   160  		}
   161  		if block == nil {
   162  			return
   163  		}
   164  	}
   165  
   166  	// Flush the WAL. Otherwise, we may not recompute the same proposal to sign,
   167  	// and the privValidator will refuse to sign anything.
   168  	cs.wal.FlushAndSync()
   169  
   170  	// Make proposal
   171  	propBlockID := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()}
   172  	proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID)
   173  	proposal.HasVC = cs.HasVC
   174  	if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal); err == nil {
   175  
   176  		// send proposal and block parts on internal msg queue
   177  		cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""})
   178  		for i := 0; i < blockParts.Total(); i++ {
   179  			part := blockParts.GetPart(i)
   180  			cs.sendInternalMessage(msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""})
   181  		}
   182  		cs.Logger.Info("Signed proposal", "height", height, "round", round, "proposal", proposal)
   183  		cs.Logger.Debug(fmt.Sprintf("Signed proposal block: %v", block))
   184  	} else if !cs.replayMode {
   185  		cs.Logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err)
   186  	}
   187  }
   188  
   189  // Returns true if the proposal block is complete &&
   190  // (if POLRound was proposed, we have +2/3 prevotes from there).
   191  func (cs *State) isProposalComplete() bool {
   192  	if cs.Proposal == nil || cs.ProposalBlock == nil {
   193  		return false
   194  	}
   195  	// we have the proposal. if there's a POLRound,
   196  	// make sure we have the prevotes from it too
   197  	if cs.Proposal.POLRound < 0 {
   198  		return true
   199  	}
   200  	// if this is false the proposer is lying or we haven't received the POL yet
   201  	return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority()
   202  
   203  }
   204  
   205  // Create the next block to propose and return it. Returns nil block upon error.
   206  //
   207  // We really only need to return the parts, but the block is returned for
   208  // convenience so we can log the proposal block.
   209  //
   210  // NOTE: keep it side-effect free for clarity.
   211  // CONTRACT: cs.privValidator is not nil.
   212  func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.PartSet) {
   213  	if cs.privValidator == nil {
   214  		panic("entered createProposalBlock with privValidator being nil")
   215  	}
   216  
   217  	var commit *types.Commit
   218  	switch {
   219  	case cs.Height == types.GetStartBlockHeight()+1:
   220  		// We're creating a proposal for the first block.
   221  		// The commit is empty, but not nil.
   222  		commit = types.NewCommit(0, 0, types.BlockID{}, nil)
   223  	case cs.LastCommit.HasTwoThirdsMajority():
   224  		// Make the commit from LastCommit
   225  		commit = cs.LastCommit.MakeCommit()
   226  	default: // This shouldn't happen.
   227  		cs.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block")
   228  		return
   229  	}
   230  
   231  	if cs.privValidatorPubKey == nil {
   232  		// If this node is a validator & proposer in the current round, it will
   233  		// miss the opportunity to create a block.
   234  		cs.Logger.Error(fmt.Sprintf("enterPropose: %v", errPubKeyIsNotSet))
   235  		return
   236  	}
   237  	proposerAddr := cs.privValidatorPubKey.Address()
   238  
   239  	return cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr)
   240  }
   241  
   242  //-----------------------------------------------------------------------------
   243  
   244  func (cs *State) defaultSetProposal(proposal *types.Proposal) (bool, error) {
   245  	// Already have one
   246  	// TODO: possibly catch double proposals
   247  	if cs.Proposal != nil {
   248  		return false, nil
   249  	}
   250  
   251  	// Does not apply
   252  	if proposal.Height != cs.Height || proposal.Round != cs.Round {
   253  		return false, nil
   254  	}
   255  
   256  	// Verify POLRound, which must be -1 or in range [0, proposal.Round).
   257  	if proposal.POLRound < -1 ||
   258  		(proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) {
   259  		return false, ErrInvalidProposalPOLRound
   260  	}
   261  
   262  	// Verify signature
   263  	if !cs.Validators.GetProposer().PubKey.VerifyBytes(proposal.SignBytes(cs.state.ChainID), proposal.Signature) {
   264  		return false, ErrInvalidProposalSignature
   265  	}
   266  
   267  	cs.Proposal = proposal
   268  	// We don't update cs.ProposalBlockParts if it is already set.
   269  	// This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round.
   270  	// TODO: We can check if Proposal is for a different block as this is a sign of misbehavior!
   271  	if cs.ProposalBlockParts == nil {
   272  		cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartsHeader)
   273  	}
   274  	cs.Logger.Info("Received proposal", "proposal", proposal)
   275  	cs.bt.onProposal(proposal.Height)
   276  	cs.trc.Pin("recvProposal")
   277  	return true, nil
   278  }
   279  
   280  func (cs *State) unmarshalBlock() error {
   281  	// uncompress blockParts bytes if necessary
   282  	pbpReader, err := types.UncompressBlockFromReader(cs.ProposalBlockParts.GetReader())
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	// Added and completed!
   288  	_, err = cdc.UnmarshalBinaryLengthPrefixedReader(
   289  		pbpReader,
   290  		&cs.ProposalBlock,
   291  		cs.state.ConsensusParams.Block.MaxBytes,
   292  	)
   293  	return err
   294  }
   295  func (cs *State) onBlockPartAdded(height int64, round, index int, added bool, err error) {
   296  
   297  	if err != nil {
   298  		cs.bt.droppedDue2Error++
   299  	}
   300  
   301  	if added {
   302  		if cs.ProposalBlockParts.Count() == 1 {
   303  			cs.trc.Pin("1stPart")
   304  			cs.bt.on1stPart(height)
   305  		}
   306  		// event to decrease blockpart transport
   307  		if cfg.DynamicConfig.GetEnableHasBlockPartMsg() {
   308  			cs.evsw.FireEvent(types.EventBlockPart, &HasBlockPartMessage{height, round, index})
   309  		}
   310  	} else {
   311  		cs.bt.droppedDue2NotAdded++
   312  	}
   313  
   314  }
   315  
   316  func (cs *State) addBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) (added bool, err error) {
   317  	// Blocks might be reused, so round mismatch is OK
   318  	if cs.Height != height {
   319  		cs.bt.droppedDue2WrongHeight++
   320  		cs.Logger.Debug("Received block part from wrong height", "height", height, "round", round)
   321  		return
   322  	}
   323  	// We're not expecting a block part.
   324  	if cs.ProposalBlockParts == nil {
   325  		// NOTE: this can happen when we've gone to a higher round and
   326  		// then receive parts from the previous round - not necessarily a bad peer.
   327  		cs.Logger.Info("Received a block part when we're not expecting any",
   328  			"height", height, "round", round, "index", part.Index, "peer", peerID)
   329  		cs.bt.droppedDue2NotExpected++
   330  		return
   331  	}
   332  	added, err = cs.ProposalBlockParts.AddPart(part)
   333  	cs.onBlockPartAdded(height, round, part.Index, added, err)
   334  	return
   335  }
   336  
   337  // NOTE: block is not necessarily valid.
   338  // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit,
   339  // once we have the full block.
   340  func (cs *State) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (added bool, err error) {
   341  	height, round, part := msg.Height, msg.Round, msg.Part
   342  	if automation.BlockIsNotCompleted(height, round) {
   343  		return
   344  	}
   345  	automation.AddBlockTimeOut(height, round)
   346  	added, err = cs.addBlockPart(height, round, part, peerID)
   347  
   348  	if added && cs.ProposalBlockParts.IsComplete() {
   349  		err = cs.unmarshalBlock()
   350  		if err != nil {
   351  			return
   352  		}
   353  		cs.trc.Pin("lastPart")
   354  		cs.bt.onRecvBlock(height)
   355  		cs.bt.totalParts = cs.ProposalBlockParts.Total()
   356  		if cs.prerunTx {
   357  			cs.blockExec.NotifyPrerun(cs.ProposalBlock)
   358  		}
   359  		// NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal
   360  		cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash())
   361  		cs.eventBus.PublishEventCompleteProposal(cs.CompleteProposalEvent())
   362  	}
   363  	return
   364  }
   365  
   366  func (cs *State) handleCompleteProposal(height int64) {
   367  	cs.Logger.Info("handleCompleteProposal", "height", cs.Height, "round", cs.Round, "step", cs.Step)
   368  	// Update Valid* if we can.
   369  	prevotes := cs.Votes.Prevotes(cs.Round)
   370  	blockID, hasTwoThirds := prevotes.TwoThirdsMajority()
   371  	prevoteBlockValid := hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) && cs.ProposalBlock.HashesTo(blockID.Hash)
   372  	if prevoteBlockValid {
   373  		cs.Logger.Info("Updating valid block to new proposal block",
   374  			"valid_round", cs.Round, "valid_block_hash", cs.ProposalBlock.Hash())
   375  		cs.ValidRound = cs.Round
   376  		cs.ValidBlock = cs.ProposalBlock
   377  		cs.ValidBlockParts = cs.ProposalBlockParts
   378  		// TODO: In case there is +2/3 majority in Prevotes set for some
   379  		// if !cs.ProposalBlock.HashesTo(blockID.Hash) {}
   380  		// block and cs.ProposalBlock contains different block, either
   381  		// proposer is faulty or voting power of faulty processes is more
   382  		// than 1/3. We should trigger in the future accountability
   383  		// procedure at this point.
   384  	}
   385  
   386  	if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() {
   387  		// Move onto the next step
   388  		cs.enterPrevote(height, cs.Round)
   389  		if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added
   390  			cs.enterPrecommit(height, cs.Round)
   391  		}
   392  	}
   393  	if cs.HasVC && cs.Round == 0 {
   394  		blockID, hasTwoThirds := cs.Votes.Precommits(cs.Round).TwoThirdsMajority()
   395  		cs.Logger.Info("avc and handleCompleteProposal", "2/3Precommit", hasTwoThirds, "proposal", cs.ProposalBlock.Hash(), "block", blockID.Hash)
   396  		if hasTwoThirds && !blockID.IsZero() && cs.ProposalBlock.HashesTo(blockID.Hash) {
   397  			cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit)
   398  		}
   399  	}
   400  	if cs.Step == cstypes.RoundStepCommit {
   401  		// If we're waiting on the proposal block...
   402  		cs.tryFinalizeCommit(height)
   403  	}
   404  }