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