github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/eventhandler/event_handler_test.go (about) 1 package eventhandler 2 3 import ( 4 "fmt" 5 "os" 6 "testing" 7 "time" 8 9 "github.com/rs/zerolog" 10 "github.com/rs/zerolog/log" 11 "github.com/stretchr/testify/mock" 12 "github.com/stretchr/testify/require" 13 "github.com/stretchr/testify/suite" 14 15 "github.com/koko1123/flow-go-1/consensus/hotstuff" 16 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 17 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 18 "github.com/koko1123/flow-go-1/consensus/hotstuff/notifications" 19 "github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker" 20 "github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker/timeout" 21 "github.com/koko1123/flow-go-1/model/flow" 22 ) 23 24 const ( 25 startRepTimeout float64 = 400.0 // Milliseconds 26 minRepTimeout float64 = 100.0 // Milliseconds 27 voteTimeoutFraction float64 = 0.5 // multiplicative factor 28 multiplicativeIncrease float64 = 1.5 // multiplicative factor 29 multiplicativeDecrease float64 = 0.85 // multiplicative factor 30 ) 31 32 // TestPaceMaker is a real pacemaker module with logging for view changes 33 type TestPaceMaker struct { 34 hotstuff.PaceMaker 35 t require.TestingT 36 } 37 38 func NewTestPaceMaker(t require.TestingT, startView uint64, timeoutController *timeout.Controller, notifier hotstuff.Consumer) *TestPaceMaker { 39 p, err := pacemaker.New(startView, timeoutController, notifier) 40 if err != nil { 41 panic(err) 42 } 43 return &TestPaceMaker{p, t} 44 } 45 46 func (p *TestPaceMaker) UpdateCurViewWithQC(qc *flow.QuorumCertificate) (*model.NewViewEvent, bool) { 47 oldView := p.CurView() 48 newView, changed := p.PaceMaker.UpdateCurViewWithQC(qc) 49 log.Info().Msgf("pacemaker.UpdateCurViewWithQC old view: %v, new view: %v\n", oldView, p.CurView()) 50 return newView, changed 51 } 52 53 func (p *TestPaceMaker) UpdateCurViewWithBlock(block *model.Block, isLeaderForNextView bool) (*model.NewViewEvent, bool) { 54 oldView := p.CurView() 55 newView, changed := p.PaceMaker.UpdateCurViewWithBlock(block, isLeaderForNextView) 56 log.Info().Msgf("pacemaker.UpdateCurViewWithBlock old view: %v, new view: %v\n", oldView, p.CurView()) 57 return newView, changed 58 } 59 60 func (p *TestPaceMaker) OnTimeout() *model.NewViewEvent { 61 oldView := p.CurView() 62 newView := p.PaceMaker.OnTimeout() 63 log.Info().Msgf("pacemaker.OnTimeout old view: %v, new view: %v\n", oldView, p.CurView()) 64 return newView 65 } 66 67 // using a real pacemaker for testing event handler 68 func initPaceMaker(t require.TestingT, view uint64) hotstuff.PaceMaker { 69 notifier := &mocks.Consumer{} 70 tc, err := timeout.NewConfig( 71 time.Duration(startRepTimeout*1e6), 72 time.Duration(minRepTimeout*1e6), 73 voteTimeoutFraction, 74 multiplicativeIncrease, 75 multiplicativeDecrease, 76 0) 77 if err != nil { 78 t.FailNow() 79 } 80 pm := NewTestPaceMaker(t, view, timeout.NewController(tc), notifier) 81 notifier.On("OnStartingTimeout", mock.Anything).Return() 82 notifier.On("OnQcTriggeredViewChange", mock.Anything, mock.Anything).Return() 83 notifier.On("OnReachedTimeout", mock.Anything).Return() 84 pm.Start() 85 return pm 86 } 87 88 // VoteAggregator is a mock for testing eventhandler 89 type VoteAggregator struct { 90 // if a blockID exists in qcs field, then a vote can be made into a QC 91 qcs map[flow.Identifier]*flow.QuorumCertificate 92 t require.TestingT 93 } 94 95 func NewVoteAggregator(t require.TestingT) *VoteAggregator { 96 return &VoteAggregator{ 97 qcs: make(map[flow.Identifier]*flow.QuorumCertificate), 98 t: t, 99 } 100 } 101 102 func (v *VoteAggregator) StoreVoteAndBuildQC(vote *model.Vote, block *model.Block) (*flow.QuorumCertificate, bool, error) { 103 qc, ok := v.qcs[block.BlockID] 104 log.Info().Msgf("voteaggregator.StoreVoteAndBuildQC, qc built: %v, for view: %x, blockID: %v\n", ok, block.View, block.BlockID) 105 106 return qc, ok, nil 107 } 108 109 func (v *VoteAggregator) StorePendingVote(vote *model.Vote) (bool, error) { 110 return false, nil 111 } 112 113 func (v *VoteAggregator) StoreProposerVote(vote *model.Vote) bool { 114 return true 115 } 116 117 func (v *VoteAggregator) BuildQCOnReceivedBlock(block *model.Block) (*flow.QuorumCertificate, bool, error) { 118 qc, ok := v.qcs[block.BlockID] 119 log.Info().Msgf("voteaggregator.BuildQCOnReceivedBlock, qc built: %v, for view: %x, blockID: %v\n", ok, block.View, block.BlockID) 120 121 return qc, ok, nil 122 } 123 124 func (v *VoteAggregator) PruneByView(view uint64) { 125 log.Info().Msgf("pruned at view:%v\n", view) 126 } 127 128 type Committee struct { 129 mocks.Committee 130 // to mock I'm the leader of a certain view, add the view into the keys of leaders field 131 leaders map[uint64]struct{} 132 } 133 134 func NewCommittee() *Committee { 135 return &Committee{ 136 leaders: make(map[uint64]struct{}), 137 } 138 } 139 140 func (c *Committee) LeaderForView(view uint64) (flow.Identifier, error) { 141 _, isLeader := c.leaders[view] 142 if isLeader { 143 return flow.Identifier{0x01}, nil 144 } 145 return flow.Identifier{0x00}, nil 146 } 147 148 func (c *Committee) Self() flow.Identifier { 149 return flow.Identifier{0x01} 150 } 151 152 // The Voter mock will not vote for any block unless the block's ID exists in votable field's key 153 type Voter struct { 154 votable map[flow.Identifier]struct{} 155 lastVotedView uint64 156 t require.TestingT 157 } 158 159 func NewVoter(t require.TestingT, lastVotedView uint64) *Voter { 160 return &Voter{ 161 votable: make(map[flow.Identifier]struct{}), 162 lastVotedView: lastVotedView, 163 t: t, 164 } 165 } 166 167 // voter will not vote for any block, unless the blockID exists in votable map 168 func (v *Voter) ProduceVoteIfVotable(block *model.Block, curView uint64) (*model.Vote, error) { 169 _, ok := v.votable[block.BlockID] 170 if !ok { 171 return nil, model.NoVoteError{Msg: "block not found"} 172 } 173 return createVote(block), nil 174 } 175 176 // Forks mock allows to customize the Add QC and AddBlock function by specifying the addQC and addBlock callbacks 177 type Forks struct { 178 mocks.Forks 179 // blocks stores all the blocks that have been added to the forks 180 blocks map[flow.Identifier]*model.Block 181 finalized uint64 182 t require.TestingT 183 qc *flow.QuorumCertificate 184 // addQC is to customize the logic to change finalized view 185 addQC func(qc *flow.QuorumCertificate) error 186 // addBlock is to customize the logic to change finalized view 187 addBlock func(block *model.Block) error 188 } 189 190 func NewForks(t require.TestingT, finalized uint64) *Forks { 191 f := &Forks{ 192 blocks: make(map[flow.Identifier]*model.Block), 193 finalized: finalized, 194 t: t, 195 } 196 197 f.addQC = func(qc *flow.QuorumCertificate) error { 198 if f.qc == nil || qc.View > f.qc.View { 199 f.qc = qc 200 } 201 return nil 202 } 203 f.addBlock = func(block *model.Block) error { 204 f.blocks[block.BlockID] = block 205 if block.QC == nil { 206 panic(fmt.Sprintf("block has no QC: %v", block.View)) 207 } 208 _ = f.addQC(block.QC) 209 return nil 210 } 211 212 qc := createQC(createBlock(finalized)) 213 _ = f.addQC(qc) 214 215 return f 216 } 217 218 func (f *Forks) AddBlock(block *model.Block) error { 219 log.Info().Msgf("forks.AddBlock received Block for view: %v, qc: %v\n", block.View, block.QC.View) 220 return f.addBlock(block) 221 } 222 223 func (f *Forks) AddQC(qc *flow.QuorumCertificate) error { 224 log.Info().Msgf("forks.AddQC received QC for view: %v\n", qc.View) 225 return f.addQC(qc) 226 } 227 228 func (f *Forks) FinalizedView() uint64 { 229 return f.finalized 230 } 231 232 func (f *Forks) GetBlock(blockID flow.Identifier) (*model.Block, bool) { 233 b, ok := f.blocks[blockID] 234 var view uint64 235 if ok { 236 view = b.View 237 } 238 log.Info().Msgf("forks.GetBlock found: %v, view: %v\n", ok, view) 239 return b, ok 240 } 241 242 func (f *Forks) GetBlocksForView(view uint64) []*model.Block { 243 blocks := make([]*model.Block, 0) 244 for _, b := range f.blocks { 245 if b.View == view { 246 blocks = append(blocks, b) 247 } 248 } 249 log.Info().Msgf("forks.GetBlocksForView found %v block(s) for view %v\n", len(blocks), view) 250 return blocks 251 } 252 253 func (f *Forks) MakeForkChoice(curView uint64) (*flow.QuorumCertificate, *model.Block, error) { 254 if f.qc == nil { 255 log.Fatal().Msgf("cannot make fork choice for curview: %v", curView) 256 } 257 258 block, ok := f.blocks[f.qc.BlockID] 259 if !ok { 260 return nil, nil, fmt.Errorf("cannot block %V for fork choice qc", f.qc.BlockID) 261 } 262 log.Info().Msgf("forks.MakeForkChoice for view: %v, qc view: %v\n", curView, f.qc.View) 263 return f.qc, block, nil 264 } 265 266 // BlockProducer mock will always make a valid block 267 type BlockProducer struct{} 268 269 func (b *BlockProducer) MakeBlockProposal(qc *flow.QuorumCertificate, view uint64) (*model.Proposal, error) { 270 return createProposal(view, qc.View), nil 271 } 272 273 // BlacklistValidator is Validator mock that consider all proposals are valid unless the proposal's BlockID exists 274 // in the invalidProposals key or unverifiable key 275 type BlacklistValidator struct { 276 mocks.Validator 277 invalidProposals map[flow.Identifier]struct{} 278 unverifiable map[flow.Identifier]struct{} 279 t require.TestingT 280 } 281 282 func NewBlacklistValidator(t require.TestingT) *BlacklistValidator { 283 return &BlacklistValidator{ 284 invalidProposals: make(map[flow.Identifier]struct{}), 285 unverifiable: make(map[flow.Identifier]struct{}), 286 t: t, 287 } 288 } 289 290 func (v *BlacklistValidator) ValidateProposal(proposal *model.Proposal) error { 291 // check if is invalid 292 _, ok := v.invalidProposals[proposal.Block.BlockID] 293 if ok { 294 log.Info().Msgf("invalid proposal: %v\n", proposal.Block.View) 295 return model.InvalidBlockError{ 296 BlockID: proposal.Block.BlockID, 297 View: proposal.Block.View, 298 Err: fmt.Errorf("some error"), 299 } 300 } 301 302 // check if is unverifiable 303 _, ok = v.unverifiable[proposal.Block.BlockID] 304 if ok { 305 log.Info().Msgf("unverifiable proposal: %v\n", proposal.Block.View) 306 return model.ErrUnverifiableBlock 307 } 308 309 return nil 310 } 311 312 func TestEventHandler(t *testing.T) { 313 suite.Run(t, new(EventHandlerSuite)) 314 } 315 316 // EventHandlerSuite contains mocked state for testing event handler under different scenarios. 317 type EventHandlerSuite struct { 318 suite.Suite 319 320 eventhandler *EventHandler 321 322 paceMaker hotstuff.PaceMaker 323 forks *Forks 324 persist *mocks.Persister 325 blockProducer *BlockProducer 326 communicator *mocks.Communicator 327 committee *Committee 328 voteAggregator *mocks.VoteAggregator 329 voter *Voter 330 validator *BlacklistValidator 331 notifier hotstuff.Consumer 332 333 initView uint64 334 endView uint64 335 votingBlock *model.Block 336 qc *flow.QuorumCertificate 337 newview *model.NewViewEvent 338 } 339 340 func (es *EventHandlerSuite) SetupTest() { 341 finalized, curView := uint64(3), uint64(6) 342 343 es.paceMaker = initPaceMaker(es.T(), curView) 344 es.forks = NewForks(es.T(), finalized) 345 es.persist = &mocks.Persister{} 346 es.persist.On("PutStarted", mock.Anything).Return(nil) 347 es.blockProducer = &BlockProducer{} 348 es.communicator = &mocks.Communicator{} 349 es.communicator.On("BroadcastProposalWithDelay", mock.Anything, mock.Anything).Return(nil) 350 es.communicator.On("SendVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 351 es.committee = NewCommittee() 352 es.voteAggregator = &mocks.VoteAggregator{} 353 es.voter = NewVoter(es.T(), finalized) 354 es.validator = NewBlacklistValidator(es.T()) 355 es.notifier = ¬ifications.NoopConsumer{} 356 357 eventhandler, err := NewEventHandler( 358 zerolog.New(os.Stderr), 359 es.paceMaker, 360 es.blockProducer, 361 es.forks, 362 es.persist, 363 es.communicator, 364 es.committee, 365 es.voteAggregator, 366 es.voter, 367 es.validator, 368 es.notifier) 369 require.NoError(es.T(), err) 370 371 es.eventhandler = eventhandler 372 373 es.initView = curView 374 es.endView = curView 375 // voting block is a block for the current view, which will trigger view change 376 es.votingBlock = createBlockWithQC(es.paceMaker.CurView(), es.paceMaker.CurView()-1) 377 es.qc = &flow.QuorumCertificate{ 378 BlockID: es.votingBlock.BlockID, 379 View: es.votingBlock.View, 380 SignerIndices: nil, 381 SigData: nil, 382 } 383 es.newview = &model.NewViewEvent{ 384 View: es.votingBlock.View + 1, // the vote for the voting blocks will trigger a view change to the next view 385 } 386 } 387 388 func (es *EventHandlerSuite) markInvalidProposal(blockID flow.Identifier) { 389 es.validator.invalidProposals[blockID] = struct{}{} 390 } 391 392 // a QC for current view triggered view change 393 func (es *EventHandlerSuite) TestQCBuiltViewChanged() { 394 // voting block exists 395 es.forks.blocks[es.votingBlock.BlockID] = es.votingBlock 396 397 // a qc is built 398 qc := createQC(es.votingBlock) 399 400 // new qc is added to forks 401 // view changed 402 // I'm not the next leader 403 // haven't received block for next view 404 // goes to the new view 405 es.endView++ 406 // not the leader of the newview 407 // don't have block for the newview 408 // over 409 410 err := es.eventhandler.OnQCConstructed(qc) 411 require.NoError(es.T(), err, "if a vote can trigger a QC to be built,"+ 412 "and the QC triggered a view change, then start new view") 413 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 414 } 415 416 // a QC for future view triggered view change 417 func (es *EventHandlerSuite) TestQCBuiltFutureViewChanged() { 418 // voting block exists 419 curView := es.paceMaker.CurView() 420 421 // b1 is for current view 422 // b2 and b3 is for future view, but branched out from the same parent as b1 423 b1 := createBlockWithQC(curView, curView-1) 424 b2 := createBlockWithQC(curView+1, curView-1) 425 b3 := createBlockWithQC(curView+2, curView-1) 426 427 // a qc is built 428 // qc3 is for future view 429 // qc2 is an older than qc3 430 // since vote aggregator can concurrently process votes and build qcs, 431 // we prepare qcs at different view to be processed, and verify the view change. 432 qc1 := createQC(b1) 433 qc2 := createQC(b2) 434 qc3 := createQC(b3) 435 436 // all three blocks are known 437 es.forks.blocks[b1.BlockID] = b1 438 es.forks.blocks[b2.BlockID] = b2 439 es.forks.blocks[b3.BlockID] = b3 440 441 // test that qc for future view should trigger view change 442 err := es.eventhandler.OnQCConstructed(qc3) 443 endView := b3.View + 1 // next view 444 require.NoError(es.T(), err, "if a vote can trigger a QC to be built,"+ 445 "and the QC triggered a view change, then start new view") 446 require.Equal(es.T(), endView, es.paceMaker.CurView(), "incorrect view change") 447 448 // the same qc would not trigger view change 449 err = es.eventhandler.OnQCConstructed(qc3) 450 endView = b3.View + 1 // next view 451 require.NoError(es.T(), err, "same qc should not trigger view change") 452 require.Equal(es.T(), endView, es.paceMaker.CurView(), "incorrect view change") 453 454 // old QCs won't trigger view change 455 err = es.eventhandler.OnQCConstructed(qc2) 456 require.NoError(es.T(), err) 457 require.Equal(es.T(), endView, es.paceMaker.CurView(), "incorrect view change") 458 459 err = es.eventhandler.OnQCConstructed(qc1) 460 require.NoError(es.T(), err) 461 require.Equal(es.T(), endView, es.paceMaker.CurView(), "incorrect view change") 462 } 463 464 // in the newview, I'm not the leader, and I have the cur block, 465 // and the block is not a safe node, and I'm the next leader, and no qc built for this block. 466 func (es *EventHandlerSuite) TestInNewView_NotLeader_HasBlock_NoVote_IsNextLeader_NoQC() { 467 // voting block exists 468 es.forks.blocks[es.votingBlock.BlockID] = es.votingBlock 469 // a qc is built 470 qc := createQC(es.votingBlock) 471 // viewchanged 472 es.endView++ 473 // not leader for newview 474 475 // has block for newview 476 newviewblock := createBlockWithQC(es.newview.View, es.newview.View-1) 477 es.forks.blocks[newviewblock.BlockID] = newviewblock 478 479 // I'm the next leader 480 es.committee.leaders[es.newview.View+1] = struct{}{} 481 482 // no QC for the new view 483 err := es.eventhandler.OnQCConstructed(qc) 484 require.NoError(es.T(), err) 485 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 486 } 487 488 // TestInNewView_NotLeader_HasBlock_NoVote_IsNextLeader_QCBuilt_NoViewChange doesn't exist 489 // in the newview, I'm not the leader, and I have the cur block, 490 // and the block is not a safe node, and I'm the next leader, and a qc is built for this block, 491 // and the qc triggered view change. 492 func (es *EventHandlerSuite) TestInNewView_NotLeader_HasBlock_NoVote_IsNextLeader_QCBuilt_ViewChanged() { 493 // voting block exists 494 es.forks.blocks[es.votingBlock.BlockID] = es.votingBlock 495 // a qc is built 496 qc := createQC(es.votingBlock) 497 // viewchanged 498 es.endView++ 499 // not leader for newview 500 501 // has block for newview 502 newviewblock := createBlockWithQC(es.newview.View, es.newview.View-1) 503 es.forks.blocks[newviewblock.BlockID] = newviewblock 504 505 // not to vote for the new view block 506 507 // I'm the next leader 508 es.committee.leaders[es.newview.View+1] = struct{}{} 509 510 // qc built for the new view block 511 nextQC := createQC(newviewblock) 512 // view change by this qc 513 es.endView++ 514 515 err := es.eventhandler.OnQCConstructed(qc) 516 require.NoError(es.T(), err) 517 518 // no broadcast shouldn't be made with first qc because we are not a leader 519 es.communicator.AssertNotCalled(es.T(), "BroadcastProposalWithDelay") 520 521 err = es.eventhandler.OnQCConstructed(nextQC) 522 require.NoError(es.T(), err) 523 524 lastCall := es.communicator.Calls[len(es.communicator.Calls)-1] 525 // the last call is BroadcastProposal 526 require.Equal(es.T(), "BroadcastProposalWithDelay", lastCall.Method) 527 header, ok := lastCall.Arguments[0].(*flow.Header) 528 require.True(es.T(), ok) 529 // it should broadcast a header as the same as endView 530 require.Equal(es.T(), es.endView, header.View) 531 } 532 533 // in the newview, I'm not the leader, and I have the cur block, 534 // and the block is a safe node to vote, and I'm the next leader, and no qc is built for this block. 535 func (es *EventHandlerSuite) TestInNewView_NotLeader_HasBlock_NotSafeNode_IsNextLeader_Voted_NoQC() { 536 // voting block exists 537 es.forks.blocks[es.votingBlock.BlockID] = es.votingBlock 538 // a qc is built 539 qc := createQC(es.votingBlock) 540 // viewchanged by new qc 541 es.endView++ 542 // not leader for newview 543 544 // has block for newview 545 newviewblock := createBlockWithQC(es.newview.View, es.newview.View-1) 546 es.forks.blocks[newviewblock.BlockID] = newviewblock 547 548 // not to vote for the new view block 549 550 // I'm the next leader 551 es.committee.leaders[es.newview.View+1] = struct{}{} 552 553 // no qc for the newview block 554 555 // should not trigger view change 556 err := es.eventhandler.OnQCConstructed(qc) 557 require.NoError(es.T(), err) 558 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 559 } 560 561 // in the newview, I'm not the leader, and I have the cur block, 562 // and the block is not a safe node to vote, and I'm not the next leader 563 func (es *EventHandlerSuite) TestInNewView_NotLeader_HasBlock_NotSafeNode_NotNextLeader() { 564 // voting block exists 565 es.forks.blocks[es.votingBlock.BlockID] = es.votingBlock 566 // a qc is built 567 qc := createQC(es.votingBlock) 568 // viewchanged by new qc 569 es.endView++ 570 571 // view changed to newview 572 // I'm not the leader for newview 573 574 // have received block for cur view 575 newviewblock := createBlockWithQC(es.newview.View, es.newview.View-1) 576 es.forks.blocks[newviewblock.BlockID] = newviewblock 577 578 // I'm not the next leader 579 // no vote for this block 580 // goes to the next view 581 es.endView++ 582 // not leader for next view 583 584 err := es.eventhandler.OnQCConstructed(qc) 585 require.NoError(es.T(), err, "if a vote can trigger a QC to be built,"+ 586 "and the QC triggered a view change, then start new view") 587 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 588 } 589 590 // receiving an invalid proposal should not trigger view change 591 func (es *EventHandlerSuite) TestOnReceiveProposal_InvalidProposal_NoViewChange() { 592 proposal := createProposal(es.initView, es.initView-1) 593 // invalid proposal 594 es.markInvalidProposal(proposal.Block.BlockID) 595 es.voteAggregator.On("InvalidBlock", proposal).Return(nil).Once() 596 597 err := es.eventhandler.OnReceiveProposal(proposal) 598 require.NoError(es.T(), err) 599 require.Equal(es.T(), es.initView, es.paceMaker.CurView(), "incorrect view change") 600 } 601 602 // received a valid proposal that has older view, and cannot build qc from votes for this block, 603 // the proposal's QC didn't trigger view change 604 func (es *EventHandlerSuite) TestOnReceiveProposal_OlderThanCurView_CannotBuildQCFromVotes_NoViewChange() { 605 proposal := createProposal(es.initView-1, es.initView-2) 606 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 607 608 // can not build qc from votes for block 609 // should not trigger view change 610 err := es.eventhandler.OnReceiveProposal(proposal) 611 require.NoError(es.T(), err) 612 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 613 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 614 } 615 616 // received a valid proposal that has older view, and can built a qc from votes for this block, 617 // the proposal's QC didn't trigger view change 618 func (es *EventHandlerSuite) TestOnReceiveProposal_OlderThanCurView_CanBuildQCFromVotes_NoViewChange() { 619 proposal := createProposal(es.initView-1, es.initView-2) 620 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 621 622 //should not trigger view change 623 err := es.eventhandler.OnReceiveProposal(proposal) 624 require.NoError(es.T(), err) 625 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 626 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 627 } 628 629 // received a valid proposal that has newer view, and cannot build qc from votes for this block, 630 // the proposal's QC triggered view change 631 func (es *EventHandlerSuite) TestOnReceiveProposal_NewerThanCurView_CannotBuildQCFromVotes_ViewChange() { 632 proposal := createProposal(es.initView+1, es.initView) 633 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 634 635 // can not build qc from votes for block 636 // block 7 triggered view change 637 es.endView++ 638 639 // not leader of view 7, go to view 8 640 es.endView++ 641 err := es.eventhandler.OnReceiveProposal(proposal) 642 require.NoError(es.T(), err) 643 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 644 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 645 } 646 647 // received a valid proposal that has newer view, and can build qc from votes for this block, 648 // the proposal's QC triggered view change 649 func (es *EventHandlerSuite) TestOnReceiveProposal_NewerThanCurView_CanBuildQCFromVotes_ViewChange() { 650 proposal := createProposal(es.initView+1, es.initView) 651 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 652 653 es.forks.blocks[proposal.Block.BlockID] = proposal.Block 654 655 // trigged view change 656 es.endView++ 657 // the proposal is for next view, has block for next view, no vote, trigger view change 658 es.endView++ 659 660 err := es.eventhandler.OnReceiveProposal(proposal) 661 require.NoError(es.T(), err) 662 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 663 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 664 } 665 666 // received a valid proposal whose QC that has newer view, and cannot build qc from votes for this block, 667 // the proposal's QC triggered view change 668 func (es *EventHandlerSuite) TestOnReceiveProposal_QCNewerThanCurView_CannotBuildQCFromVotes_ViewChanged() { 669 proposal := createProposal(es.initView+2, es.initView+1) 670 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 671 672 // can not build qc from votes for block 673 // block 8 triggered view change 674 es.endView = es.endView + 2 675 676 // not leader of view 8, go to view 9 677 es.endView++ 678 err := es.eventhandler.OnReceiveProposal(proposal) 679 require.NoError(es.T(), err) 680 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 681 require.Contains(es.T(), es.forks.blocks, proposal.Block.BlockID, "proposal block should be stored") 682 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 683 } 684 685 // received a valid proposal for cur view, but not a safe node to vote, and I'm the next leader, 686 // no qc for the block 687 func (es *EventHandlerSuite) TestOnReceiveProposal_ForCurView_NoVote_IsNextLeader_NoQC() { 688 proposal := createProposal(es.initView, es.initView-1) 689 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 690 691 // I'm the next leader 692 es.committee.leaders[es.initView+1] = struct{}{} 693 // no qc can be built for this block 694 err := es.eventhandler.OnReceiveProposal(proposal) 695 require.NoError(es.T(), err) 696 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 697 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 698 } 699 700 // received a valid proposal for cur view, but not a safe node to vote, and I'm the next leader, 701 // a qc can be built for the block, triggered view change 702 func (es *EventHandlerSuite) TestOnReceiveProposal_ForCurView_NoVote_IsNextLeader_QCBuilt_ViewChange() { 703 proposal := createProposal(es.initView, es.initView-1) 704 qc := createQC(proposal.Block) 705 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 706 // I'm the next leader 707 es.committee.leaders[es.initView+1] = struct{}{} 708 // qc triggered view change 709 es.endView++ 710 // I'm the leader of cur view (7) 711 // I'm not the leader of next view (8), trigger view change 712 713 err := es.eventhandler.OnReceiveProposal(proposal) 714 require.NoError(es.T(), err) 715 716 // after receiving proposal build QC and deliver it to event handler 717 err = es.eventhandler.OnQCConstructed(qc) 718 require.NoError(es.T(), err) 719 720 lastCall := es.communicator.Calls[len(es.communicator.Calls)-1] 721 // the last call is BroadcastProposal 722 require.Equal(es.T(), "BroadcastProposalWithDelay", lastCall.Method) 723 header, ok := lastCall.Arguments[0].(*flow.Header) 724 require.True(es.T(), ok) 725 // it should broadcast a header as the same as endView 726 require.Equal(es.T(), es.endView, header.View) 727 728 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 729 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 730 } 731 732 // received a unverifiable proposal for future view, no view change 733 func (es *EventHandlerSuite) TestOnReceiveProposal_Unverifiable() { 734 // qc.View is below the finalized view 735 proposal := createProposal(es.forks.finalized+2, es.forks.finalized-1) 736 737 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 738 739 // proposal is unverifiable 740 es.validator.unverifiable[proposal.Block.BlockID] = struct{}{} 741 742 err := es.eventhandler.OnReceiveProposal(proposal) 743 require.NoError(es.T(), err) 744 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 745 es.voteAggregator.AssertCalled(es.T(), "AddBlock", proposal) 746 } 747 748 func (es *EventHandlerSuite) TestOnTimeout() { 749 err := es.eventhandler.OnLocalTimeout() 750 // timeout will trigger viewchange 751 es.endView++ 752 require.NoError(es.T(), err) 753 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 754 } 755 756 func (es *EventHandlerSuite) Test100Timeout() { 757 for i := 0; i < 100; i++ { 758 err := es.eventhandler.OnLocalTimeout() 759 es.endView++ 760 require.NoError(es.T(), err) 761 } 762 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 763 } 764 765 // a leader builds 100 blocks one after another 766 func (es *EventHandlerSuite) TestLeaderBuild100Blocks() { 767 // I'm the leader for the first view 768 es.committee.leaders[es.initView] = struct{}{} 769 770 totalView := 100 771 for i := 0; i < totalView; i++ { 772 // I'm the leader for 100 views 773 // I'm the next leader 774 es.committee.leaders[es.initView+uint64(i+1)] = struct{}{} 775 // I can build qc for all 100 views 776 proposal := createProposal(es.initView+uint64(i), es.initView+uint64(i)-1) 777 qc := createQC(proposal.Block) 778 779 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 780 es.voteAggregator.On("AddVote", proposal.ProposerVote()).Return(nil).Once() 781 782 es.voter.votable[proposal.Block.BlockID] = struct{}{} 783 // should trigger 100 view change 784 es.endView++ 785 786 err := es.eventhandler.OnReceiveProposal(proposal) 787 require.NoError(es.T(), err) 788 err = es.eventhandler.OnQCConstructed(qc) 789 require.NoError(es.T(), err) 790 791 lastCall := es.communicator.Calls[len(es.communicator.Calls)-1] 792 require.Equal(es.T(), "BroadcastProposalWithDelay", lastCall.Method) 793 header, ok := lastCall.Arguments[0].(*flow.Header) 794 require.True(es.T(), ok) 795 require.Equal(es.T(), proposal.Block.View+1, header.View) 796 } 797 798 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 799 require.Equal(es.T(), totalView, len(es.forks.blocks)) 800 es.voteAggregator.AssertExpectations(es.T()) 801 } 802 803 // a follower receives 100 blocks 804 func (es *EventHandlerSuite) TestFollowerFollows100Blocks() { 805 for i := 0; i < 100; i++ { 806 // create each proposal as if they are created by some leader 807 proposal := createProposal(es.initView+uint64(i), es.initView+uint64(i)-1) 808 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 809 // as a follower, I receive these propsals 810 err := es.eventhandler.OnReceiveProposal(proposal) 811 require.NoError(es.T(), err) 812 es.endView++ 813 } 814 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 815 require.Equal(es.T(), 100, len(es.forks.blocks)) 816 es.voteAggregator.AssertExpectations(es.T()) 817 } 818 819 // a follower receives 100 forks built on top of the same block 820 func (es *EventHandlerSuite) TestFollowerReceives100Forks() { 821 for i := 0; i < 100; i++ { 822 // create each proposal as if they are created by some leader 823 proposal := createProposal(es.initView+uint64(i)+1, es.initView-1) 824 es.voteAggregator.On("AddBlock", proposal).Return(nil).Once() 825 // as a follower, I receive these propsals 826 err := es.eventhandler.OnReceiveProposal(proposal) 827 require.NoError(es.T(), err) 828 } 829 require.Equal(es.T(), es.endView, es.paceMaker.CurView(), "incorrect view change") 830 require.Equal(es.T(), 100, len(es.forks.blocks)) 831 es.voteAggregator.AssertExpectations(es.T()) 832 } 833 834 func createBlock(view uint64) *model.Block { 835 blockID := flow.MakeID(struct { 836 BlockID uint64 837 }{ 838 BlockID: view, 839 }) 840 return &model.Block{ 841 BlockID: blockID, 842 View: uint64(view), 843 } 844 } 845 846 func createBlockWithQC(view uint64, qcview uint64) *model.Block { 847 block := createBlock(view) 848 parent := createBlock(qcview) 849 block.QC = createQC(parent) 850 return block 851 } 852 853 func createQC(parent *model.Block) *flow.QuorumCertificate { 854 qc := &flow.QuorumCertificate{ 855 BlockID: parent.BlockID, 856 View: parent.View, 857 SignerIndices: nil, 858 SigData: nil, 859 } 860 return qc 861 } 862 863 func createVote(block *model.Block) *model.Vote { 864 return &model.Vote{ 865 View: block.View, 866 BlockID: block.BlockID, 867 SignerID: flow.ZeroID, 868 SigData: nil, 869 } 870 } 871 872 func createProposal(view uint64, qcview uint64) *model.Proposal { 873 block := createBlockWithQC(view, qcview) 874 return &model.Proposal{ 875 Block: block, 876 SigData: nil, 877 } 878 }