github.com/vipernet-xyz/tm@v0.34.24/test/maverick/consensus/misbehavior.go (about)

     1  package consensus
     2  
     3  import (
     4  	"fmt"
     5  
     6  	tmcon "github.com/vipernet-xyz/tm/consensus"
     7  	cstypes "github.com/vipernet-xyz/tm/consensus/types"
     8  	"github.com/vipernet-xyz/tm/libs/log"
     9  	"github.com/vipernet-xyz/tm/p2p"
    10  	tmcons "github.com/vipernet-xyz/tm/proto/tendermint/consensus"
    11  	tmproto "github.com/vipernet-xyz/tm/proto/tendermint/types"
    12  	"github.com/vipernet-xyz/tm/types"
    13  )
    14  
    15  // MisbehaviorList encompasses a list of all possible behaviors
    16  var MisbehaviorList = map[string]Misbehavior{
    17  	"double-prevote": DoublePrevoteMisbehavior(),
    18  }
    19  
    20  type Misbehavior struct {
    21  	Name string
    22  
    23  	EnterPropose func(cs *State, height int64, round int32)
    24  
    25  	EnterPrevote func(cs *State, height int64, round int32)
    26  
    27  	EnterPrecommit func(cs *State, height int64, round int32)
    28  
    29  	ReceivePrevote func(cs *State, prevote *types.Vote)
    30  
    31  	ReceivePrecommit func(cs *State, precommit *types.Vote)
    32  
    33  	ReceiveProposal func(cs *State, proposal *types.Proposal) error
    34  }
    35  
    36  // BEHAVIORS
    37  
    38  func DefaultMisbehavior() Misbehavior {
    39  	return Misbehavior{
    40  		Name:             "default",
    41  		EnterPropose:     defaultEnterPropose,
    42  		EnterPrevote:     defaultEnterPrevote,
    43  		EnterPrecommit:   defaultEnterPrecommit,
    44  		ReceivePrevote:   defaultReceivePrevote,
    45  		ReceivePrecommit: defaultReceivePrecommit,
    46  		ReceiveProposal:  defaultReceiveProposal,
    47  	}
    48  }
    49  
    50  // DoublePrevoteMisbehavior will make a node prevote both nil and a block in the same
    51  // height and round.
    52  func DoublePrevoteMisbehavior() Misbehavior {
    53  	b := DefaultMisbehavior()
    54  	b.Name = "double-prevote"
    55  	b.EnterPrevote = func(cs *State, height int64, round int32) {
    56  
    57  		// If a block is locked, prevote that.
    58  		if cs.LockedBlock != nil {
    59  			cs.Logger.Debug("enterPrevote: already locked on a block, prevoting locked block")
    60  			cs.signAddVote(tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header())
    61  			return
    62  		}
    63  
    64  		// If ProposalBlock is nil, prevote nil.
    65  		if cs.ProposalBlock == nil {
    66  			cs.Logger.Debug("enterPrevote: ProposalBlock is nil")
    67  			cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
    68  			return
    69  		}
    70  
    71  		// Validate proposal block
    72  		err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock)
    73  		if err != nil {
    74  			// ProposalBlock is invalid, prevote nil.
    75  			cs.Logger.Error("enterPrevote: ProposalBlock is invalid", "err", err)
    76  			cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
    77  			return
    78  		}
    79  
    80  		if cs.sw == nil {
    81  			cs.Logger.Error("nil switch")
    82  			return
    83  		}
    84  
    85  		prevote, err := cs.signVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
    86  		if err != nil {
    87  			cs.Logger.Error("enterPrevote: Unable to sign block", "err", err)
    88  		}
    89  
    90  		nilPrevote, err := cs.signVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
    91  		if err != nil {
    92  			cs.Logger.Error("enterPrevote: Unable to sign block", "err", err)
    93  		}
    94  
    95  		// add our own vote
    96  		cs.sendInternalMessage(msgInfo{&tmcon.VoteMessage{Vote: prevote}, ""})
    97  
    98  		cs.Logger.Info("Sending conflicting votes")
    99  		peers := cs.sw.Peers().List()
   100  		// there has to be at least two other peers connected else this behavior works normally
   101  		for idx, peer := range peers {
   102  			if idx%2 == 0 { // sign the proposal block
   103  				p2p.SendEnvelopeShim(peer, p2p.Envelope{ //nolint: staticcheck
   104  					ChannelID: VoteChannel,
   105  					Message: &tmcons.Vote{
   106  						Vote: prevote.ToProto(),
   107  					},
   108  				}, cs.Logger)
   109  			} else { // sign a nil block
   110  				p2p.SendEnvelopeShim(peer, p2p.Envelope{ //nolint: staticcheck
   111  					ChannelID: VoteChannel,
   112  					Message: &tmcons.Vote{
   113  						Vote: nilPrevote.ToProto(),
   114  					},
   115  				}, cs.Logger)
   116  			}
   117  		}
   118  	}
   119  	return b
   120  }
   121  
   122  // DEFAULTS
   123  
   124  func defaultEnterPropose(cs *State, height int64, round int32) {
   125  	logger := cs.Logger.With("height", height, "round", round)
   126  	// If we don't get the proposal and all block parts quick enough, enterPrevote
   127  	cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose)
   128  
   129  	// Nothing more to do if we're not a validator
   130  	if cs.privValidator == nil {
   131  		logger.Debug("This node is not a validator")
   132  		return
   133  	}
   134  	logger.Debug("This node is a validator")
   135  
   136  	pubKey, err := cs.privValidator.GetPubKey()
   137  	if err != nil {
   138  		// If this node is a validator & proposer in the currentx round, it will
   139  		// miss the opportunity to create a block.
   140  		logger.Error("Error on retrival of pubkey", "err", err)
   141  		return
   142  	}
   143  	address := pubKey.Address()
   144  
   145  	// if not a validator, we're done
   146  	if !cs.Validators.HasAddress(address) {
   147  		logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators)
   148  		return
   149  	}
   150  
   151  	if cs.isProposer(address) {
   152  		logger.Debug("enterPropose: our turn to propose",
   153  			"proposer", address,
   154  		)
   155  		cs.decideProposal(height, round)
   156  	} else {
   157  		logger.Debug("enterPropose: not our turn to propose",
   158  			"proposer", cs.Validators.GetProposer().Address,
   159  		)
   160  	}
   161  }
   162  
   163  func defaultEnterPrevote(cs *State, height int64, round int32) {
   164  	logger := cs.Logger.With("height", height, "round", round)
   165  
   166  	// If a block is locked, prevote that.
   167  	if cs.LockedBlock != nil {
   168  		logger.Debug("enterPrevote: already locked on a block, prevoting locked block")
   169  		cs.signAddVote(tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header())
   170  		return
   171  	}
   172  
   173  	// If ProposalBlock is nil, prevote nil.
   174  	if cs.ProposalBlock == nil {
   175  		logger.Debug("enterPrevote: ProposalBlock is nil")
   176  		cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
   177  		return
   178  	}
   179  
   180  	// Validate proposal block
   181  	err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock)
   182  	if err != nil {
   183  		// ProposalBlock is invalid, prevote nil.
   184  		logger.Error("enterPrevote: ProposalBlock is invalid", "err", err)
   185  		cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
   186  		return
   187  	}
   188  
   189  	// Prevote cs.ProposalBlock
   190  	// NOTE: the proposal signature is validated when it is received,
   191  	// and the proposal block parts are validated as they are received (against the merkle hash in the proposal)
   192  	logger.Debug("enterPrevote: ProposalBlock is valid")
   193  	cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
   194  }
   195  
   196  func defaultEnterPrecommit(cs *State, height int64, round int32) {
   197  	logger := cs.Logger.With("height", height, "round", round)
   198  
   199  	// check for a polka
   200  	blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
   201  
   202  	// If we don't have a polka, we must precommit nil.
   203  	if !ok {
   204  		if cs.LockedBlock != nil {
   205  			logger.Debug("enterPrecommit: no +2/3 prevotes during enterPrecommit while we're locked; precommitting nil")
   206  		} else {
   207  			logger.Debug("enterPrecommit: no +2/3 prevotes during enterPrecommit; precommitting nil.")
   208  		}
   209  		cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{})
   210  		return
   211  	}
   212  
   213  	// At this point +2/3 prevoted for a particular block or nil.
   214  	_ = cs.eventBus.PublishEventPolka(cs.RoundStateEvent())
   215  
   216  	// the latest POLRound should be this round.
   217  	polRound, _ := cs.Votes.POLInfo()
   218  	if polRound < round {
   219  		panic(fmt.Sprintf("This POLRound should be %v but got %v", round, polRound))
   220  	}
   221  
   222  	// +2/3 prevoted nil. Unlock and precommit nil.
   223  	if len(blockID.Hash) == 0 {
   224  		if cs.LockedBlock == nil {
   225  			logger.Debug("enterPrecommit: +2/3 prevoted for nil")
   226  		} else {
   227  			logger.Debug("enterPrecommit: +2/3 prevoted for nil; unlocking")
   228  			cs.LockedRound = -1
   229  			cs.LockedBlock = nil
   230  			cs.LockedBlockParts = nil
   231  			_ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
   232  		}
   233  		cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{})
   234  		return
   235  	}
   236  
   237  	// At this point, +2/3 prevoted for a particular block.
   238  
   239  	// If we're already locked on that block, precommit it, and update the LockedRound
   240  	if cs.LockedBlock.HashesTo(blockID.Hash) {
   241  		logger.Debug("enterPrecommit: +2/3 prevoted locked block; relocking")
   242  		cs.LockedRound = round
   243  		_ = cs.eventBus.PublishEventRelock(cs.RoundStateEvent())
   244  		cs.signAddVote(tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader)
   245  		return
   246  	}
   247  
   248  	// If +2/3 prevoted for proposal block, stage and precommit it
   249  	if cs.ProposalBlock.HashesTo(blockID.Hash) {
   250  		logger.Debug("enterPrecommit: +2/3 prevoted proposal block; locking", "hash", blockID.Hash)
   251  		// Validate the block.
   252  		if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil {
   253  			panic(fmt.Sprintf("enterPrecommit: +2/3 prevoted for an invalid block: %v", err))
   254  		}
   255  		cs.LockedRound = round
   256  		cs.LockedBlock = cs.ProposalBlock
   257  		cs.LockedBlockParts = cs.ProposalBlockParts
   258  		_ = cs.eventBus.PublishEventLock(cs.RoundStateEvent())
   259  		cs.signAddVote(tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader)
   260  		return
   261  	}
   262  
   263  	// There was a polka in this round for a block we don't have.
   264  	// Fetch that block, unlock, and precommit nil.
   265  	// The +2/3 prevotes for this round is the POL for our unlock.
   266  	logger.Debug("enterPrecommit: +2/3 prevotes for a block we don't have; voting nil", "blockID", blockID)
   267  	cs.LockedRound = -1
   268  	cs.LockedBlock = nil
   269  	cs.LockedBlockParts = nil
   270  	if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) {
   271  		cs.ProposalBlock = nil
   272  		cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader)
   273  	}
   274  	_ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
   275  	cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{})
   276  }
   277  
   278  func defaultReceivePrevote(cs *State, vote *types.Vote) {
   279  	height := cs.Height
   280  	prevotes := cs.Votes.Prevotes(vote.Round)
   281  
   282  	// If +2/3 prevotes for a block or nil for *any* round:
   283  	if blockID, ok := prevotes.TwoThirdsMajority(); ok {
   284  
   285  		// There was a polka!
   286  		// If we're locked but this is a recent polka, unlock.
   287  		// If it matches our ProposalBlock, update the ValidBlock
   288  
   289  		// Unlock if `cs.LockedRound < vote.Round <= cs.Round`
   290  		// NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round
   291  		if (cs.LockedBlock != nil) &&
   292  			(cs.LockedRound < vote.Round) &&
   293  			(vote.Round <= cs.Round) &&
   294  			!cs.LockedBlock.HashesTo(blockID.Hash) {
   295  
   296  			cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
   297  			cs.LockedRound = -1
   298  			cs.LockedBlock = nil
   299  			cs.LockedBlockParts = nil
   300  			_ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
   301  		}
   302  
   303  		// Update Valid* if we can.
   304  		// NOTE: our proposal block may be nil or not what received a polka..
   305  		if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) {
   306  
   307  			if cs.ProposalBlock.HashesTo(blockID.Hash) {
   308  				cs.Logger.Info(
   309  					"Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round)
   310  				cs.ValidRound = vote.Round
   311  				cs.ValidBlock = cs.ProposalBlock
   312  				cs.ValidBlockParts = cs.ProposalBlockParts
   313  			} else {
   314  				cs.Logger.Info(
   315  					"valid block we do not know about; set ProposalBlock=nil",
   316  					"proposal", log.NewLazyBlockHash(cs.ProposalBlock),
   317  					"blockID", blockID.Hash,
   318  				)
   319  
   320  				// We're getting the wrong block.
   321  				cs.ProposalBlock = nil
   322  			}
   323  			if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) {
   324  				cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader)
   325  			}
   326  			cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState)
   327  			_ = cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent())
   328  		}
   329  	}
   330  
   331  	// If +2/3 prevotes for *anything* for future round:
   332  	switch {
   333  	case cs.Round < vote.Round && prevotes.HasTwoThirdsAny():
   334  		// Round-skip if there is any 2/3+ of votes ahead of us
   335  		cs.enterNewRound(height, vote.Round)
   336  	case cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step: // current round
   337  		blockID, ok := prevotes.TwoThirdsMajority()
   338  		if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) {
   339  			cs.enterPrecommit(height, vote.Round)
   340  		} else if prevotes.HasTwoThirdsAny() {
   341  			cs.enterPrevoteWait(height, vote.Round)
   342  		}
   343  	case cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round:
   344  		// If the proposal is now complete, enter prevote of cs.Round.
   345  		if cs.isProposalComplete() {
   346  			cs.enterPrevote(height, cs.Round)
   347  		}
   348  	}
   349  
   350  }
   351  
   352  func defaultReceivePrecommit(cs *State, vote *types.Vote) {
   353  	height := cs.Height
   354  	precommits := cs.Votes.Precommits(vote.Round)
   355  	cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
   356  
   357  	blockID, ok := precommits.TwoThirdsMajority()
   358  	if ok {
   359  		// Executed as TwoThirdsMajority could be from a higher round
   360  		cs.enterNewRound(height, vote.Round)
   361  		cs.enterPrecommit(height, vote.Round)
   362  		if len(blockID.Hash) != 0 {
   363  			cs.enterCommit(height, vote.Round)
   364  			if cs.config.SkipTimeoutCommit && precommits.HasAll() {
   365  				cs.enterNewRound(cs.Height, 0)
   366  			}
   367  		} else {
   368  			cs.enterPrecommitWait(height, vote.Round)
   369  		}
   370  	} else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
   371  		cs.enterNewRound(height, vote.Round)
   372  		cs.enterPrecommitWait(height, vote.Round)
   373  	}
   374  }
   375  
   376  func defaultReceiveProposal(cs *State, proposal *types.Proposal) error {
   377  	// Already have one
   378  	// TODO: possibly catch double proposals
   379  	if cs.Proposal != nil {
   380  		return nil
   381  	}
   382  
   383  	// Does not apply
   384  	if proposal.Height != cs.Height || proposal.Round != cs.Round {
   385  		return nil
   386  	}
   387  
   388  	// Verify POLRound, which must be -1 or in range [0, proposal.Round).
   389  	if proposal.POLRound < -1 ||
   390  		(proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) {
   391  		return ErrInvalidProposalPOLRound
   392  	}
   393  
   394  	p := proposal.ToProto()
   395  	// Verify signature
   396  	if !cs.Validators.GetProposer().PubKey.VerifySignature(
   397  		types.ProposalSignBytes(cs.state.ChainID, p), proposal.Signature) {
   398  		return ErrInvalidProposalSignature
   399  	}
   400  
   401  	proposal.Signature = p.Signature
   402  	cs.Proposal = proposal
   403  	// We don't update cs.ProposalBlockParts if it is already set.
   404  	// This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round.
   405  	// TODO: We can check if Proposal is for a different block as this is a sign of misbehavior!
   406  	if cs.ProposalBlockParts == nil {
   407  		cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartSetHeader)
   408  	}
   409  
   410  	cs.Logger.Info("received proposal", "proposal", proposal)
   411  	return nil
   412  }