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

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"time"
     8  
     9  	cstypes "github.com/fibonacci-chain/fbc/libs/tendermint/consensus/types"
    10  	"github.com/fibonacci-chain/fbc/libs/tendermint/p2p"
    11  	"github.com/fibonacci-chain/fbc/libs/tendermint/types"
    12  	tmtime "github.com/fibonacci-chain/fbc/libs/tendermint/types/time"
    13  )
    14  
    15  // Attempt to add the vote. if its a duplicate signature, dupeout the validator
    16  func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) {
    17  	added, err := cs.addVote(vote, peerID)
    18  	if err != nil {
    19  		// If the vote height is off, we'll just ignore it,
    20  		// But if it's a conflicting sig, add it to the cs.evpool.
    21  		// If it's otherwise invalid, punish peer.
    22  		// nolint: gocritic
    23  		if err == ErrVoteHeightMismatch {
    24  			return added, err
    25  		} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
    26  			if cs.privValidatorPubKey == nil {
    27  				return false, errPubKeyIsNotSet
    28  			}
    29  
    30  			if bytes.Equal(vote.ValidatorAddress, cs.privValidatorPubKey.Address()) {
    31  				cs.Logger.Error(
    32  					"Found conflicting vote from ourselves. Did you unsafe_reset a validator?",
    33  					"height",
    34  					vote.Height,
    35  					"round",
    36  					vote.Round,
    37  					"type",
    38  					vote.Type)
    39  				return added, err
    40  			}
    41  			if GetActiveVC() && vote.Round == 0 && vote.HasVC {
    42  				return added, err
    43  			}
    44  			cs.evpool.AddEvidence(voteErr.DuplicateVoteEvidence)
    45  			return added, err
    46  		} else if err == types.ErrVoteNonDeterministicSignature {
    47  			cs.Logger.Debug("Vote has non-deterministic signature", "err", err)
    48  		} else {
    49  			// Either
    50  			// 1) bad peer OR
    51  			// 2) not a bad peer? this can also err sometimes with "Unexpected step" OR
    52  			// 3) tmkms use with multiple validators connecting to a single tmkms instance
    53  			// 		(https://github.com/tendermint/tendermint/issues/3839).
    54  			cs.Logger.Info("Error attempting to add vote", "err", err)
    55  			return added, ErrAddingVote
    56  		}
    57  	}
    58  	return added, nil
    59  }
    60  
    61  //-----------------------------------------------------------------------------
    62  
    63  func (cs *State) addVote(
    64  	vote *types.Vote,
    65  	peerID p2p.ID) (added bool, err error) {
    66  	cs.Logger.Debug(
    67  		"addVote",
    68  		"voteHeight",
    69  		vote.Height,
    70  		"voteType",
    71  		vote.Type,
    72  		"valIndex",
    73  		vote.ValidatorIndex,
    74  		"csHeight",
    75  		cs.Height,
    76  	)
    77  
    78  	// A precommit for the previous height?
    79  	// These come in while we wait timeoutCommit
    80  	if vote.Height+1 == cs.Height {
    81  		if !(cs.Step == cstypes.RoundStepNewHeight && vote.Type == types.PrecommitType) {
    82  			// TODO: give the reason ..
    83  			// fmt.Errorf("tryAddVote: Wrong height, not a LastCommit straggler commit.")
    84  			return added, ErrVoteHeightMismatch
    85  		}
    86  		added, err = cs.LastCommit.AddVote(vote)
    87  		if !added {
    88  			return added, err
    89  		}
    90  
    91  		cs.Logger.Info(fmt.Sprintf("Added to lastPrecommits: %v", cs.LastCommit.StringShort()))
    92  		cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote})
    93  		cs.evsw.FireEvent(types.EventVote, vote)
    94  
    95  		// if we can skip timeoutCommit and have all the votes now,
    96  		if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() {
    97  			// go straight to new round (skip timeout commit)
    98  			// cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight)
    99  			cs.enterNewRound(cs.Height, 0)
   100  		}
   101  
   102  		return
   103  	}
   104  
   105  	// Height mismatch is ignored.
   106  	// Not necessarily a bad peer, but not favourable behaviour.
   107  	if vote.Height != cs.Height {
   108  		err = ErrVoteHeightMismatch
   109  		cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "peerID", peerID)
   110  		return
   111  	}
   112  
   113  	height := cs.Height
   114  	added, err = cs.Votes.AddVote(vote, peerID)
   115  	if !added {
   116  		// Either duplicate, or error upon cs.Votes.AddByIndex()
   117  		return
   118  	}
   119  
   120  	cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote})
   121  	cs.evsw.FireEvent(types.EventVote, vote)
   122  
   123  	switch vote.Type {
   124  	case types.PrevoteType:
   125  		prevotes := cs.Votes.Prevotes(vote.Round)
   126  		cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort())
   127  
   128  		// If +2/3 prevotes for a block or nil for *any* round:
   129  		if blockID, ok := prevotes.TwoThirdsMajority(); ok {
   130  
   131  			// There was a polka!
   132  			// If we're locked but this is a recent polka, unlock.
   133  			// If it matches our ProposalBlock, update the ValidBlock
   134  
   135  			// Unlock if `cs.LockedRound < vote.Round <= cs.Round`
   136  			// NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round
   137  			if (cs.LockedBlock != nil) &&
   138  				(cs.LockedRound < vote.Round) &&
   139  				(vote.Round <= cs.Round) &&
   140  				!cs.LockedBlock.HashesTo(blockID.Hash) {
   141  
   142  				cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
   143  				cs.LockedRound = -1
   144  				cs.LockedBlock = nil
   145  				cs.LockedBlockParts = nil
   146  				cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
   147  			}
   148  
   149  			// Update Valid* if we can.
   150  			// NOTE: our proposal block may be nil or not what received a polka..
   151  			if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) {
   152  
   153  				if cs.ProposalBlock.HashesTo(blockID.Hash) {
   154  					cs.Logger.Info(
   155  						"Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round)
   156  					cs.ValidRound = vote.Round
   157  					cs.ValidBlock = cs.ProposalBlock
   158  					cs.ValidBlockParts = cs.ProposalBlockParts
   159  				} else {
   160  					cs.Logger.Info(
   161  						"Valid block we don't know about. Set ProposalBlock=nil",
   162  						"proposal", cs.ProposalBlock.Hash(), "blockID", blockID.Hash)
   163  					// We're getting the wrong block.
   164  					cs.ProposalBlock = nil
   165  				}
   166  				if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
   167  					cs.Logger.Info("addVote proposalBlockPart reset ,because of mismatch hash,",
   168  						"origin", hex.EncodeToString(cs.ProposalBlockParts.Hash()), "after", blockID.Hash)
   169  					cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader)
   170  				}
   171  				cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState)
   172  				cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent())
   173  			}
   174  		}
   175  
   176  		// If +2/3 prevotes for *anything* for future round:
   177  		switch {
   178  		case cs.Round < vote.Round && prevotes.HasTwoThirdsAny():
   179  			// Round-skip if there is any 2/3+ of votes ahead of us
   180  			cs.enterNewRound(height, vote.Round)
   181  		case cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step: // current round
   182  			blockID, ok := prevotes.TwoThirdsMajority()
   183  			if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) {
   184  				cs.enterPrecommit(height, vote.Round)
   185  			} else if prevotes.HasTwoThirdsAny() {
   186  				cs.enterPrevoteWait(height, vote.Round)
   187  			}
   188  		case cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round:
   189  			// If the proposal is now complete, enter prevote of cs.Round.
   190  			if cs.isProposalComplete() {
   191  				cs.enterPrevote(height, cs.Round)
   192  			}
   193  		}
   194  
   195  	case types.PrecommitType:
   196  		precommits := cs.Votes.Precommits(vote.Round)
   197  		cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
   198  
   199  		blockID, ok := precommits.TwoThirdsMajority()
   200  		if ok {
   201  			// Executed as TwoThirdsMajority could be from a higher round
   202  			cs.enterNewRound(height, vote.Round)
   203  			cs.enterPrecommit(height, vote.Round)
   204  			if len(blockID.Hash) != 0 {
   205  				cs.enterCommit(height, vote.Round)
   206  				if cs.config.SkipTimeoutCommit && precommits.HasAll() {
   207  					cs.enterNewRound(cs.Height, 0)
   208  				}
   209  			} else {
   210  				cs.enterPrecommitWait(height, vote.Round)
   211  			}
   212  		} else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
   213  			cs.enterNewRound(height, vote.Round)
   214  			cs.enterPrecommitWait(height, vote.Round)
   215  		}
   216  
   217  	default:
   218  		panic(fmt.Sprintf("Unexpected vote type %X", vote.Type)) // go-amino should prevent this.
   219  	}
   220  
   221  	return added, err
   222  }
   223  
   224  // CONTRACT: cs.privValidator is not nil.
   225  func (cs *State) signVote(
   226  	msgType types.SignedMsgType,
   227  	hash []byte,
   228  	header types.PartSetHeader,
   229  ) (*types.Vote, error) {
   230  	// Flush the WAL. Otherwise, we may not recompute the same vote to sign,
   231  	// and the privValidator will refuse to sign anything.
   232  	cs.wal.FlushAndSync()
   233  
   234  	if cs.privValidatorPubKey == nil {
   235  		return nil, errPubKeyIsNotSet
   236  	}
   237  	addr := cs.privValidatorPubKey.Address()
   238  	valIdx, _ := cs.Validators.GetByAddress(addr)
   239  
   240  	vote := &types.Vote{
   241  		ValidatorAddress: addr,
   242  		ValidatorIndex:   valIdx,
   243  		Height:           cs.Height,
   244  		Round:            cs.Round,
   245  		Timestamp:        cs.voteTime(),
   246  		Type:             msgType,
   247  		BlockID:          types.BlockID{Hash: hash, PartsHeader: header},
   248  		HasVC:            cs.HasVC,
   249  	}
   250  
   251  	err := cs.privValidator.SignVote(cs.state.ChainID, vote)
   252  	return vote, err
   253  }
   254  
   255  func (cs *State) voteTime() time.Time {
   256  	now := tmtime.Now()
   257  	minVoteTime := now
   258  	// TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil,
   259  	// even if cs.LockedBlock != nil. See https://docs.tendermint.com/master/spec/.
   260  	timeIotaMs := time.Duration(cs.state.ConsensusParams.Block.TimeIotaMs) * time.Millisecond
   261  	if cs.LockedBlock != nil {
   262  		// See the BFT time spec https://docs.tendermint.com/master/spec/consensus/bft-time.html
   263  		minVoteTime = cs.LockedBlock.Time.Add(timeIotaMs)
   264  	} else if cs.ProposalBlock != nil {
   265  		minVoteTime = cs.ProposalBlock.Time.Add(timeIotaMs)
   266  	}
   267  
   268  	if now.After(minVoteTime) {
   269  		return now
   270  	}
   271  	return minVoteTime
   272  }
   273  
   274  // sign the vote and publish on internalMsgQueue
   275  func (cs *State) signAddVote(msgType types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
   276  	if cs.privValidator == nil { // the node does not have a key
   277  		return nil
   278  	}
   279  
   280  	if cs.privValidatorPubKey == nil {
   281  		// Vote won't be signed, but it's not critical.
   282  		cs.Logger.Error(fmt.Sprintf("signAddVote: %v", errPubKeyIsNotSet))
   283  		return nil
   284  	}
   285  
   286  	// If the node not in the validator set, do nothing.
   287  	if !cs.Validators.HasAddress(cs.privValidatorPubKey.Address()) {
   288  		return nil
   289  	}
   290  
   291  	// TODO: pass pubKey to signVote
   292  	vote, err := cs.signVote(msgType, hash, header)
   293  	if err == nil {
   294  		//broadcast vote immediately
   295  		cs.evsw.FireEvent(types.EventSignVote, vote)
   296  		cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""})
   297  		cs.Logger.Info("Signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
   298  		return vote
   299  	}
   300  	if !cs.replayMode {
   301  		cs.Logger.Error("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
   302  	}
   303  	return nil
   304  }