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 }