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

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