github.com/okex/exchain@v1.8.0/libs/tendermint/consensus/consensus_propose.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 cfg "github.com/okex/exchain/libs/tendermint/config" 9 cstypes "github.com/okex/exchain/libs/tendermint/consensus/types" 10 "github.com/okex/exchain/libs/tendermint/libs/automation" 11 "github.com/okex/exchain/libs/tendermint/p2p" 12 "github.com/okex/exchain/libs/tendermint/types" 13 ) 14 15 // SetProposal inputs a proposal. 16 func (cs *State) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { 17 18 if peerID == "" { 19 cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} 20 } else { 21 cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID} 22 } 23 24 // TODO: wait for event?! 25 return nil 26 } 27 28 // AddProposalBlockPart inputs a part of the proposal block. 29 func (cs *State) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error { 30 if peerID == "" { 31 cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} 32 } else { 33 cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID} 34 } 35 36 // TODO: wait for event?! 37 return nil 38 } 39 40 // SetProposalAndBlock inputs the proposal and all block parts. 41 func (cs *State) SetProposalAndBlock( 42 proposal *types.Proposal, 43 block *types.Block, 44 parts *types.PartSet, 45 peerID p2p.ID, 46 ) error { 47 if err := cs.SetProposal(proposal, peerID); err != nil { 48 return err 49 } 50 for i := 0; i < parts.Total(); i++ { 51 part := parts.GetPart(i) 52 if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil { 53 return err 54 } 55 } 56 return nil 57 } 58 59 func (cs *State) isBlockProducer() (string, string) { 60 const len2display int = 6 61 bpAddr := cs.Validators.GetProposer().Address 62 bpStr := bpAddr.String() 63 if len(bpStr) > len2display { 64 bpStr = bpStr[:len2display] 65 } 66 isBlockProducer := "n" 67 if cs.privValidator != nil && cs.privValidatorPubKey != nil { 68 address := cs.privValidatorPubKey.Address() 69 70 if bytes.Equal(bpAddr, address) { 71 isBlockProducer = "y" 72 } 73 } 74 75 return isBlockProducer, strings.ToLower(bpStr) 76 } 77 78 // Enter (CreateEmptyBlocks): from enterNewRound(height,round) 79 // Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): 80 // after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval 81 // Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool 82 func (cs *State) enterPropose(height int64, round int) { 83 logger := cs.Logger.With("height", height, "round", round) 84 if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) { 85 logger.Debug(fmt.Sprintf( 86 "enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", 87 height, 88 round, 89 cs.Height, 90 cs.Round, 91 cs.Step)) 92 return 93 } 94 95 cs.initNewHeight() 96 isBlockProducer, bpAddr := cs.isBlockProducer() 97 98 cs.stateMtx.RLock() 99 cs.updateRoundStep(round, cstypes.RoundStepPropose) 100 newProposer := "" 101 if cs.vcHeight[height] != "" && cs.Round == 0 { 102 newProposer = "-avc-" + cs.vcHeight[height][:6] 103 } 104 cs.stateMtx.RUnlock() 105 cs.trc.Pin("enterPropose-%d-%s-%s%s", round, isBlockProducer, bpAddr, newProposer) 106 107 logger.Info(fmt.Sprintf("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) 108 109 defer func() { 110 // Done enterPropose: 111 cs.newStep() 112 113 // If we have the whole proposal + POL, then goto Prevote now. 114 // else, we'll enterPrevote when the rest of the proposal is received (in AddProposalBlockPart), 115 // or else after timeoutPropose 116 if cs.isProposalComplete() { 117 cs.enterPrevote(height, cs.Round) 118 } 119 }() 120 121 // If we don't get the proposal and all block parts quick enough, enterPrevote 122 cs.timeoutTicker.ScheduleTimeout(timeoutInfo{Duration: cs.config.Propose(round), Height: height, Round: round, Step: cstypes.RoundStepPropose, ActiveViewChange: cs.HasVC}) 123 124 if isBlockProducer == "y" { 125 logger.Info("enterPropose: Our turn to propose", 126 "proposer", 127 bpAddr, 128 "privValidator", 129 cs.privValidator) 130 if newProposer == "" || cs.Round != 0 { 131 cs.decideProposal(height, round) 132 } 133 } else { 134 logger.Info("enterPropose: Not our turn to propose", 135 "proposer", 136 cs.Validators.GetProposer().Address, 137 "privValidator", 138 cs.privValidator) 139 } 140 } 141 142 func (cs *State) isProposer(address []byte) bool { 143 return bytes.Equal(cs.Validators.GetProposer().Address, address) 144 } 145 146 func (cs *State) defaultDecideProposal(height int64, round int) { 147 var block *types.Block 148 var blockParts *types.PartSet 149 150 // Decide on block 151 if cs.ValidBlock != nil { 152 // If there is valid block, choose that. 153 block, blockParts = cs.ValidBlock, cs.ValidBlockParts 154 } else { 155 // Create a new proposal block from state/txs from the mempool. 156 if res := cs.getPreBlockResult(height); res != nil { 157 block, blockParts = res.block, res.blockParts 158 } else { 159 block, blockParts = cs.createProposalBlock() 160 } 161 if block == nil { 162 return 163 } 164 } 165 166 // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, 167 // and the privValidator will refuse to sign anything. 168 cs.wal.FlushAndSync() 169 170 // Make proposal 171 propBlockID := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()} 172 proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID) 173 proposal.HasVC = cs.HasVC 174 if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal); err == nil { 175 176 // send proposal and block parts on internal msg queue 177 cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) 178 for i := 0; i < blockParts.Total(); i++ { 179 part := blockParts.GetPart(i) 180 cs.sendInternalMessage(msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""}) 181 } 182 cs.Logger.Info("Signed proposal", "height", height, "round", round, "proposal", proposal) 183 cs.Logger.Debug(fmt.Sprintf("Signed proposal block: %v", block)) 184 } else if !cs.replayMode { 185 cs.Logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err) 186 } 187 } 188 189 // Returns true if the proposal block is complete && 190 // (if POLRound was proposed, we have +2/3 prevotes from there). 191 func (cs *State) isProposalComplete() bool { 192 if cs.Proposal == nil || cs.ProposalBlock == nil { 193 return false 194 } 195 // we have the proposal. if there's a POLRound, 196 // make sure we have the prevotes from it too 197 if cs.Proposal.POLRound < 0 { 198 return true 199 } 200 // if this is false the proposer is lying or we haven't received the POL yet 201 return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority() 202 203 } 204 205 // Create the next block to propose and return it. Returns nil block upon error. 206 // 207 // We really only need to return the parts, but the block is returned for 208 // convenience so we can log the proposal block. 209 // 210 // NOTE: keep it side-effect free for clarity. 211 // CONTRACT: cs.privValidator is not nil. 212 func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.PartSet) { 213 if cs.privValidator == nil { 214 panic("entered createProposalBlock with privValidator being nil") 215 } 216 217 var commit *types.Commit 218 switch { 219 case cs.Height == types.GetStartBlockHeight()+1: 220 // We're creating a proposal for the first block. 221 // The commit is empty, but not nil. 222 commit = types.NewCommit(0, 0, types.BlockID{}, nil) 223 case cs.LastCommit.HasTwoThirdsMajority(): 224 // Make the commit from LastCommit 225 commit = cs.LastCommit.MakeCommit() 226 default: // This shouldn't happen. 227 cs.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") 228 return 229 } 230 231 if cs.privValidatorPubKey == nil { 232 // If this node is a validator & proposer in the current round, it will 233 // miss the opportunity to create a block. 234 cs.Logger.Error(fmt.Sprintf("enterPropose: %v", errPubKeyIsNotSet)) 235 return 236 } 237 proposerAddr := cs.privValidatorPubKey.Address() 238 239 return cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr) 240 } 241 242 //----------------------------------------------------------------------------- 243 244 func (cs *State) defaultSetProposal(proposal *types.Proposal) (bool, error) { 245 // Already have one 246 // TODO: possibly catch double proposals 247 if cs.Proposal != nil { 248 return false, nil 249 } 250 251 // Does not apply 252 if proposal.Height != cs.Height || proposal.Round != cs.Round { 253 return false, nil 254 } 255 256 // Verify POLRound, which must be -1 or in range [0, proposal.Round). 257 if proposal.POLRound < -1 || 258 (proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) { 259 return false, ErrInvalidProposalPOLRound 260 } 261 262 // Verify signature 263 if !cs.Validators.GetProposer().PubKey.VerifyBytes(proposal.SignBytes(cs.state.ChainID), proposal.Signature) { 264 return false, ErrInvalidProposalSignature 265 } 266 267 cs.Proposal = proposal 268 // We don't update cs.ProposalBlockParts if it is already set. 269 // This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round. 270 // TODO: We can check if Proposal is for a different block as this is a sign of misbehavior! 271 if cs.ProposalBlockParts == nil { 272 cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartsHeader) 273 } 274 cs.Logger.Info("Received proposal", "proposal", proposal) 275 cs.bt.onProposal(proposal.Height) 276 cs.trc.Pin("recvProposal") 277 return true, nil 278 } 279 280 func (cs *State) unmarshalBlock() error { 281 // uncompress blockParts bytes if necessary 282 pbpReader, err := types.UncompressBlockFromReader(cs.ProposalBlockParts.GetReader()) 283 if err != nil { 284 return err 285 } 286 287 // Added and completed! 288 _, err = cdc.UnmarshalBinaryLengthPrefixedReader( 289 pbpReader, 290 &cs.ProposalBlock, 291 cs.state.ConsensusParams.Block.MaxBytes, 292 ) 293 return err 294 } 295 func (cs *State) onBlockPartAdded(height int64, round, index int, added bool, err error) { 296 297 if err != nil { 298 cs.bt.droppedDue2Error++ 299 } 300 301 if added { 302 if cs.ProposalBlockParts.Count() == 1 { 303 cs.trc.Pin("1stPart") 304 cs.bt.on1stPart(height) 305 } 306 // event to decrease blockpart transport 307 if cfg.DynamicConfig.GetEnableHasBlockPartMsg() { 308 cs.evsw.FireEvent(types.EventBlockPart, &HasBlockPartMessage{height, round, index}) 309 } 310 } else { 311 cs.bt.droppedDue2NotAdded++ 312 } 313 314 } 315 316 func (cs *State) addBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) (added bool, err error) { 317 // Blocks might be reused, so round mismatch is OK 318 if cs.Height != height { 319 cs.bt.droppedDue2WrongHeight++ 320 cs.Logger.Debug("Received block part from wrong height", "height", height, "round", round) 321 return 322 } 323 // We're not expecting a block part. 324 if cs.ProposalBlockParts == nil { 325 // NOTE: this can happen when we've gone to a higher round and 326 // then receive parts from the previous round - not necessarily a bad peer. 327 cs.Logger.Info("Received a block part when we're not expecting any", 328 "height", height, "round", round, "index", part.Index, "peer", peerID) 329 cs.bt.droppedDue2NotExpected++ 330 return 331 } 332 added, err = cs.ProposalBlockParts.AddPart(part) 333 cs.onBlockPartAdded(height, round, part.Index, added, err) 334 return 335 } 336 337 // NOTE: block is not necessarily valid. 338 // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, 339 // once we have the full block. 340 func (cs *State) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (added bool, err error) { 341 height, round, part := msg.Height, msg.Round, msg.Part 342 if automation.BlockIsNotCompleted(height, round) { 343 return 344 } 345 automation.AddBlockTimeOut(height, round) 346 added, err = cs.addBlockPart(height, round, part, peerID) 347 348 if added && cs.ProposalBlockParts.IsComplete() { 349 err = cs.unmarshalBlock() 350 if err != nil { 351 return 352 } 353 cs.trc.Pin("lastPart") 354 cs.bt.onRecvBlock(height) 355 cs.bt.totalParts = cs.ProposalBlockParts.Total() 356 if cs.prerunTx { 357 cs.blockExec.NotifyPrerun(cs.ProposalBlock) 358 } 359 // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal 360 cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) 361 cs.eventBus.PublishEventCompleteProposal(cs.CompleteProposalEvent()) 362 } 363 return 364 } 365 366 func (cs *State) handleCompleteProposal(height int64) { 367 cs.Logger.Info("handleCompleteProposal", "height", cs.Height, "round", cs.Round, "step", cs.Step) 368 // Update Valid* if we can. 369 prevotes := cs.Votes.Prevotes(cs.Round) 370 blockID, hasTwoThirds := prevotes.TwoThirdsMajority() 371 prevoteBlockValid := hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) && cs.ProposalBlock.HashesTo(blockID.Hash) 372 if prevoteBlockValid { 373 cs.Logger.Info("Updating valid block to new proposal block", 374 "valid_round", cs.Round, "valid_block_hash", cs.ProposalBlock.Hash()) 375 cs.ValidRound = cs.Round 376 cs.ValidBlock = cs.ProposalBlock 377 cs.ValidBlockParts = cs.ProposalBlockParts 378 // TODO: In case there is +2/3 majority in Prevotes set for some 379 // if !cs.ProposalBlock.HashesTo(blockID.Hash) {} 380 // block and cs.ProposalBlock contains different block, either 381 // proposer is faulty or voting power of faulty processes is more 382 // than 1/3. We should trigger in the future accountability 383 // procedure at this point. 384 } 385 386 if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() { 387 // Move onto the next step 388 cs.enterPrevote(height, cs.Round) 389 if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added 390 cs.enterPrecommit(height, cs.Round) 391 } 392 } 393 if cs.HasVC && cs.Round == 0 { 394 blockID, hasTwoThirds := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() 395 cs.Logger.Info("avc and handleCompleteProposal", "2/3Precommit", hasTwoThirds, "proposal", cs.ProposalBlock.Hash(), "block", blockID.Hash) 396 if hasTwoThirds && !blockID.IsZero() && cs.ProposalBlock.HashesTo(blockID.Hash) { 397 cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) 398 } 399 } 400 if cs.Step == cstypes.RoundStepCommit { 401 // If we're waiting on the proposal block... 402 cs.tryFinalizeCommit(height) 403 } 404 }