github.com/noirx94/tendermintmp@v0.0.1/test/maverick/consensus/misbehavior.go (about)

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