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

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