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 }