github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/eventhandler/event_handler.go (about) 1 package eventhandler 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/rs/zerolog" 9 10 "github.com/koko1123/flow-go-1/consensus/hotstuff" 11 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 12 "github.com/koko1123/flow-go-1/model/flow" 13 "github.com/koko1123/flow-go-1/module/mempool" 14 ) 15 16 // EventHandler is the main handler for individual events that trigger state transition. 17 // It exposes API to handle one event at a time synchronously. The caller is 18 // responsible for running the event loop to ensure that. 19 type EventHandler struct { 20 log zerolog.Logger 21 paceMaker hotstuff.PaceMaker 22 blockProducer hotstuff.BlockProducer 23 forks hotstuff.Forks 24 persist hotstuff.Persister 25 communicator hotstuff.Communicator 26 committee hotstuff.Committee 27 voteAggregator hotstuff.VoteAggregator 28 voter hotstuff.Voter 29 validator hotstuff.Validator 30 notifier hotstuff.Consumer 31 ownProposal flow.Identifier 32 } 33 34 var _ hotstuff.EventHandler = (*EventHandler)(nil) 35 36 // NewEventHandler creates an EventHandler instance with initial components. 37 func NewEventHandler( 38 log zerolog.Logger, 39 paceMaker hotstuff.PaceMaker, 40 blockProducer hotstuff.BlockProducer, 41 forks hotstuff.Forks, 42 persist hotstuff.Persister, 43 communicator hotstuff.Communicator, 44 committee hotstuff.Committee, 45 voteAggregator hotstuff.VoteAggregator, 46 voter hotstuff.Voter, 47 validator hotstuff.Validator, 48 notifier hotstuff.Consumer, 49 ) (*EventHandler, error) { 50 e := &EventHandler{ 51 log: log.With().Str("hotstuff", "participant").Logger(), 52 paceMaker: paceMaker, 53 blockProducer: blockProducer, 54 forks: forks, 55 persist: persist, 56 communicator: communicator, 57 voter: voter, 58 validator: validator, 59 committee: committee, 60 voteAggregator: voteAggregator, 61 notifier: notifier, 62 ownProposal: flow.ZeroID, 63 } 64 return e, nil 65 } 66 67 // OnQCConstructed processes constructed QC by our vote aggregator 68 func (e *EventHandler) OnQCConstructed(qc *flow.QuorumCertificate) error { 69 curView := e.paceMaker.CurView() 70 71 log := e.log.With(). 72 Uint64("cur_view", curView). 73 Uint64("qc_view", qc.View). 74 Hex("qc_block_id", qc.BlockID[:]). 75 Logger() 76 77 e.notifier.OnQcConstructedFromVotes(curView, qc) 78 defer e.notifier.OnEventProcessed() 79 80 log.Debug().Msg("received constructed QC") 81 82 // ignore stale qc 83 if qc.View < e.forks.FinalizedView() { 84 log.Debug().Msg("stale qc") 85 return nil 86 } 87 88 return e.processQC(qc) 89 } 90 91 // OnReceiveProposal processes the block when a block proposal is received. 92 // It is assumed that the block proposal is incorporated. (its parent can be found 93 // in the forks) 94 func (e *EventHandler) OnReceiveProposal(proposal *model.Proposal) error { 95 96 block := proposal.Block 97 curView := e.paceMaker.CurView() 98 99 log := e.log.With(). 100 Uint64("cur_view", curView). 101 Uint64("block_view", block.View). 102 Hex("block_id", block.BlockID[:]). 103 Uint64("qc_view", block.QC.View). 104 Hex("proposer_id", block.ProposerID[:]). 105 Logger() 106 107 e.notifier.OnReceiveProposal(curView, proposal) 108 defer e.notifier.OnEventProcessed() 109 log.Debug().Msg("proposal forwarded from compliance engine") 110 111 // ignore stale proposals 112 if block.View < e.forks.FinalizedView() { 113 log.Debug().Msg("stale proposal") 114 return nil 115 } 116 117 // we skip validation for our last own proposal 118 if proposal.Block.BlockID != e.ownProposal { 119 120 // validate the block. exit if the proposal is invalid 121 err := e.validator.ValidateProposal(proposal) 122 if model.IsInvalidBlockError(err) { 123 perr := e.voteAggregator.InvalidBlock(proposal) 124 if mempool.IsDecreasingPruningHeightError(perr) { 125 log.Warn().Err(err).Msgf("invalid block proposal, but vote aggregator has pruned this height: %v", perr) 126 return nil 127 } 128 129 if perr != nil { 130 return fmt.Errorf("vote aggregator could not process invalid block proposal %v, err %v: %w", 131 block.BlockID, err, perr) 132 } 133 134 log.Warn().Err(err).Msg("invalid block proposal") 135 return nil 136 } 137 138 if errors.Is(err, model.ErrUnverifiableBlock) { 139 log.Warn().Err(err).Msg("unverifiable block proposal") 140 141 // even if the block is unverifiable because the QC has been 142 // pruned, it still needs to be added to the forks, otherwise, 143 // a new block with a QC to this block will fail to be added 144 // to forks and crash the event loop. 145 } else if err != nil { 146 return fmt.Errorf("cannot validate proposal (%x): %w", block.BlockID, err) 147 } 148 } 149 150 // notify vote aggregator about a new block, so that it can start verifying 151 // votes for it. 152 err := e.voteAggregator.AddBlock(proposal) 153 if err != nil { 154 if !mempool.IsDecreasingPruningHeightError(err) { 155 return fmt.Errorf("could not add block (%v) to vote aggregator: %w", block.BlockID, err) 156 } 157 } 158 159 // store the block. 160 err = e.forks.AddBlock(block) 161 if err != nil { 162 return fmt.Errorf("cannot add block to fork (%x): %w", block.BlockID, err) 163 } 164 165 // if the block is not for the current view, then process the QC 166 if block.View != curView { 167 return e.processQC(block.QC) 168 } 169 170 // if the block is for the current view, then check whether to vote for this block 171 err = e.processBlockForCurrentView(block) 172 if err != nil { 173 return fmt.Errorf("failed processing current block: %w", err) 174 } 175 176 return nil 177 } 178 179 // TimeoutChannel returns the channel for subscribing the waiting timeout on receiving 180 // block or votes for the current view. 181 func (e *EventHandler) TimeoutChannel() <-chan time.Time { 182 return e.paceMaker.TimeoutChannel() 183 } 184 185 // OnLocalTimeout is called when the timeout event created by pacemaker looped through the 186 // event loop. 187 func (e *EventHandler) OnLocalTimeout() error { 188 189 curView := e.paceMaker.CurView() 190 newView := e.paceMaker.OnTimeout() 191 defer e.notifier.OnEventProcessed() 192 193 log := e.log.With(). 194 Uint64("cur_view", curView). 195 Uint64("new_view", newView.View). 196 Logger() 197 // notifications about time-outs and view-changes are generated by PaceMaker; no need to send a notification here 198 log.Debug().Msg("timeout received from event loop") 199 200 if curView == newView.View { 201 return fmt.Errorf("OnLocalTimeout should guarantee that the pacemaker should go to next view, but didn't: (curView: %v, newView: %v)", curView, newView.View) 202 } 203 204 // current view has changed, go to new view 205 err := e.startNewView() 206 if err != nil { 207 return fmt.Errorf("could not start new view: %w", err) 208 } 209 210 log.Debug().Msg("local timeout processed") 211 212 return nil 213 } 214 215 // Start will start the pacemaker's timer and start the new view 216 func (e *EventHandler) Start() error { 217 e.paceMaker.Start() 218 return e.startNewView() 219 } 220 221 // startNewView will only be called when there is a view change from pacemaker. 222 // It reads the current view, and check if it needs to propose or vote in this view. 223 func (e *EventHandler) startNewView() error { 224 225 // track the start time 226 start := time.Now() 227 228 curView := e.paceMaker.CurView() 229 230 err := e.persist.PutStarted(curView) 231 if err != nil { 232 return fmt.Errorf("could not persist current view: %w", err) 233 } 234 235 currentLeader, err := e.committee.LeaderForView(curView) 236 if err != nil { 237 return fmt.Errorf("failed to determine primary for new view %d: %w", curView, err) 238 } 239 240 log := e.log.With(). 241 Uint64("cur_view", curView). 242 Hex("leader_id", currentLeader[:]).Logger() 243 log.Debug(). 244 Uint64("finalized_view", e.forks.FinalizedView()). 245 Msg("entering new view") 246 e.notifier.OnEnteringView(curView, currentLeader) 247 248 if e.committee.Self() == currentLeader { 249 log.Debug().Msg("generating block proposal as leader") 250 251 // as the leader of the current view, 252 // build the block proposal for the current view 253 qc, _, err := e.forks.MakeForkChoice(curView) 254 if err != nil { 255 return fmt.Errorf("can not make fork choice for view %v: %w", curView, err) 256 } 257 258 proposal, err := e.blockProducer.MakeBlockProposal(qc, curView) 259 if err != nil { 260 return fmt.Errorf("can not make block proposal for curView %v: %w", curView, err) 261 } 262 e.notifier.OnProposingBlock(proposal) 263 264 block := proposal.Block 265 log.Debug(). 266 Uint64("block_view", block.View). 267 Hex("block_id", block.BlockID[:]). 268 Uint64("parent_view", qc.View). 269 Hex("parent_id", qc.BlockID[:]). 270 Hex("signer", block.ProposerID[:]). 271 Msg("forwarding proposal to communicator for broadcasting") 272 273 // broadcast the proposal 274 header := model.ProposalToFlow(proposal) 275 delay := e.paceMaker.BlockRateDelay() 276 elapsed := time.Since(start) 277 if elapsed > delay { 278 delay = 0 279 } else { 280 delay = delay - elapsed 281 } 282 err = e.communicator.BroadcastProposalWithDelay(header, delay) 283 if err != nil { 284 log.Warn().Err(err).Msg("could not forward proposal") 285 } 286 // mark our own proposals to avoid double validation 287 e.ownProposal = proposal.Block.BlockID 288 289 // We return here to correspond to the HotStuff state machine. 290 return nil 291 // Algorithmically, this return statement is optional: 292 // * If this replica is the leader for the current view, there can be no valid proposal from any 293 // other node. This replica's proposal is the only valid proposal. 294 // * This replica's proposal got just sent out above. It will enter the HotStuff logic from the 295 // EventLoop. In other words, the own proposal is not yet stored in Forks. 296 // Hence, Forks cannot contain _any_ valid proposal for the current view. 297 // Therefore, if this replica is the leader, the following code is a no-op. 298 } 299 300 // as a replica of the current view, find and process the block for the current view 301 blocks := e.forks.GetBlocksForView(curView) 302 if len(blocks) == 0 { 303 // if there is no block stored before for the current view, then exit and keep waiting 304 log.Debug().Msg("waiting for proposal from leader") 305 return nil 306 } 307 308 // when there are multiple block proposals, we will just pick the first one. 309 // forks is responsible for slashing double proposal behavior, and 310 // event handler is aware of double proposals, but picking any should work and 311 // won't hurt safety 312 block := blocks[0] 313 314 log.Debug(). 315 Uint64("block_view", block.View). 316 Hex("block_id", block.BlockID[:]). 317 Uint64("parent_view", block.QC.View). 318 Hex("parent_id", block.QC.BlockID[:]). 319 Hex("signer", block.ProposerID[:]). 320 Msg("processing cached proposal from leader") 321 322 return e.processBlockForCurrentView(block) 323 } 324 325 // processBlockForCurrentView processes the block for the current view. 326 // It is called AFTER the block has been stored or found in Forks 327 // It checks whether to vote for this block. 328 // It might trigger a view change to go to a different view, which might re-enter this function. 329 func (e *EventHandler) processBlockForCurrentView(block *model.Block) error { 330 // sanity check that block is really for the current view: 331 curView := e.paceMaker.CurView() 332 if block.View != curView { 333 return fmt.Errorf("sanity check fails: block proposal's view does not match with curView, (blockView: %v, curView: %v)", 334 block.View, curView) 335 } 336 // leader (node ID) for next view 337 nextLeader, err := e.committee.LeaderForView(curView + 1) 338 if err != nil { 339 return fmt.Errorf("failed to determine primary for next view %d: %w", curView+1, err) 340 } 341 342 // voter performs all the checks to decide whether to vote for this block or not. 343 err = e.ownVote(block, curView, nextLeader) 344 if err != nil { 345 return fmt.Errorf("unexpected error in voting logic: %w", err) 346 } 347 348 // Inform PaceMaker that we've processed a block for the current view. We expect 349 // a view change if an only if we are _not_ the next leader. We perform a sanity 350 // check here, because a wrong view change can have disastrous consequences. 351 isSelfNextLeader := e.committee.Self() == nextLeader 352 _, viewChanged := e.paceMaker.UpdateCurViewWithBlock(block, isSelfNextLeader) 353 if viewChanged == isSelfNextLeader { 354 if isSelfNextLeader { 355 return fmt.Errorf("I am primary for next view (%v) and should be collecting votes, but pacemaker triggered already view change", curView+1) 356 } 357 return fmt.Errorf("pacemaker should trigger a view change to net view (%v), but didn't", curView+1) 358 } 359 360 if viewChanged { 361 return e.startNewView() 362 } 363 return nil 364 } 365 366 // ownVote generates and forwards the own vote, if we decide to vote. 367 // Any errors are potential symptoms of uncovered edge cases or corrupted internal state (fatal). 368 func (e *EventHandler) ownVote(block *model.Block, curView uint64, nextLeader flow.Identifier) error { 369 log := e.log.With(). 370 Uint64("block_view", block.View). 371 Hex("block_id", block.BlockID[:]). 372 Uint64("parent_view", block.QC.View). 373 Hex("parent_id", block.QC.BlockID[:]). 374 Hex("signer", block.ProposerID[:]). 375 Logger() 376 377 // voter performs all the checks to decide whether to vote for this block or not. 378 ownVote, err := e.voter.ProduceVoteIfVotable(block, curView) 379 if err != nil { 380 if !model.IsNoVoteError(err) { 381 // unknown error, exit the event loop 382 return fmt.Errorf("could not produce vote: %w", err) 383 } 384 log.Debug().Err(err).Msg("should not vote for this block") 385 return nil 386 } 387 388 // The following code is only reached, if this replica has produced a vote. 389 // Send the vote to the next leader (or directly process it, if I am the next leader). 390 e.notifier.OnVoting(ownVote) 391 log.Debug().Msg("forwarding vote to compliance engine") 392 if e.committee.Self() == nextLeader { // I am the next leader 393 e.voteAggregator.AddVote(ownVote) 394 } else { 395 err = e.communicator.SendVote(ownVote.BlockID, ownVote.View, ownVote.SigData, nextLeader) 396 if err != nil { 397 log.Warn().Err(err).Msg("could not forward vote") 398 } 399 } 400 return nil 401 } 402 403 // processQC stores the QC and check whether the QC will trigger view change. 404 // If triggered, then go to the new view. 405 func (e *EventHandler) processQC(qc *flow.QuorumCertificate) error { 406 407 log := e.log.With(). 408 Uint64("block_view", qc.View). 409 Hex("block_id", qc.BlockID[:]). 410 Logger() 411 412 err := e.forks.AddQC(qc) 413 if err != nil { 414 return fmt.Errorf("cannot add QC to forks: %w", err) 415 } 416 417 _, viewChanged := e.paceMaker.UpdateCurViewWithQC(qc) 418 if !viewChanged { 419 log.Debug().Msg("QC didn't trigger view change, nothing to do") 420 return nil 421 } 422 log.Debug().Msg("QC triggered view change, starting new view now") 423 424 // current view has changed, go to new view 425 return e.startNewView() 426 }