github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/integration/instance_test.go (about) 1 package integration 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/gammazero/workerpool" 11 "github.com/rs/zerolog" 12 "github.com/stretchr/testify/mock" 13 "github.com/stretchr/testify/require" 14 "go.uber.org/atomic" 15 16 "github.com/onflow/flow-go/consensus/hotstuff" 17 "github.com/onflow/flow-go/consensus/hotstuff/blockproducer" 18 "github.com/onflow/flow-go/consensus/hotstuff/committees" 19 "github.com/onflow/flow-go/consensus/hotstuff/eventhandler" 20 "github.com/onflow/flow-go/consensus/hotstuff/forks" 21 "github.com/onflow/flow-go/consensus/hotstuff/helper" 22 "github.com/onflow/flow-go/consensus/hotstuff/mocks" 23 "github.com/onflow/flow-go/consensus/hotstuff/model" 24 "github.com/onflow/flow-go/consensus/hotstuff/notifications" 25 "github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub" 26 "github.com/onflow/flow-go/consensus/hotstuff/pacemaker" 27 "github.com/onflow/flow-go/consensus/hotstuff/pacemaker/timeout" 28 "github.com/onflow/flow-go/consensus/hotstuff/safetyrules" 29 "github.com/onflow/flow-go/consensus/hotstuff/timeoutaggregator" 30 "github.com/onflow/flow-go/consensus/hotstuff/timeoutcollector" 31 "github.com/onflow/flow-go/consensus/hotstuff/validator" 32 "github.com/onflow/flow-go/consensus/hotstuff/voteaggregator" 33 "github.com/onflow/flow-go/consensus/hotstuff/votecollector" 34 "github.com/onflow/flow-go/crypto" 35 "github.com/onflow/flow-go/model/flow" 36 "github.com/onflow/flow-go/module/counters" 37 "github.com/onflow/flow-go/module/irrecoverable" 38 "github.com/onflow/flow-go/module/metrics" 39 module "github.com/onflow/flow-go/module/mock" 40 msig "github.com/onflow/flow-go/module/signature" 41 "github.com/onflow/flow-go/module/util" 42 "github.com/onflow/flow-go/utils/unittest" 43 ) 44 45 type Instance struct { 46 47 // instance parameters 48 participants flow.IdentityList 49 localID flow.Identifier 50 blockVoteIn VoteFilter 51 blockVoteOut VoteFilter 52 blockPropIn ProposalFilter 53 blockPropOut ProposalFilter 54 blockTimeoutObjectIn TimeoutObjectFilter 55 blockTimeoutObjectOut TimeoutObjectFilter 56 stop Condition 57 58 // instance data 59 queue chan interface{} 60 updatingBlocks sync.RWMutex 61 headers map[flow.Identifier]*flow.Header 62 pendings map[flow.Identifier]*model.Proposal // indexed by parent ID 63 64 // mocked dependencies 65 committee *mocks.DynamicCommittee 66 builder *module.Builder 67 finalizer *module.Finalizer 68 persist *mocks.Persister 69 signer *mocks.Signer 70 verifier *mocks.Verifier 71 notifier *MockedCommunicatorConsumer 72 73 // real dependencies 74 pacemaker hotstuff.PaceMaker 75 producer *blockproducer.BlockProducer 76 forks *forks.Forks 77 voteAggregator *voteaggregator.VoteAggregator 78 timeoutAggregator *timeoutaggregator.TimeoutAggregator 79 safetyRules *safetyrules.SafetyRules 80 validator *validator.Validator 81 82 // main logic 83 handler *eventhandler.EventHandler 84 } 85 86 type MockedCommunicatorConsumer struct { 87 notifications.NoopProposalViolationConsumer 88 notifications.NoopParticipantConsumer 89 notifications.NoopFinalizationConsumer 90 *mocks.CommunicatorConsumer 91 } 92 93 func NewMockedCommunicatorConsumer() *MockedCommunicatorConsumer { 94 return &MockedCommunicatorConsumer{ 95 CommunicatorConsumer: &mocks.CommunicatorConsumer{}, 96 } 97 } 98 99 var _ hotstuff.Consumer = (*MockedCommunicatorConsumer)(nil) 100 var _ hotstuff.TimeoutCollectorConsumer = (*Instance)(nil) 101 102 func NewInstance(t *testing.T, options ...Option) *Instance { 103 104 // generate random default identity 105 identity := unittest.IdentityFixture() 106 107 // initialize the default configuration 108 cfg := Config{ 109 Root: DefaultRoot(), 110 Participants: flow.IdentityList{identity}, 111 LocalID: identity.NodeID, 112 Timeouts: timeout.DefaultConfig, 113 IncomingVotes: BlockNoVotes, 114 OutgoingVotes: BlockNoVotes, 115 IncomingProposals: BlockNoProposals, 116 OutgoingProposals: BlockNoProposals, 117 IncomingTimeoutObjects: BlockNoTimeoutObjects, 118 OutgoingTimeoutObjects: BlockNoTimeoutObjects, 119 StopCondition: RightAway, 120 } 121 122 // apply the custom options 123 for _, option := range options { 124 option(&cfg) 125 } 126 127 // check the local ID is a participant 128 var index uint 129 takesPart := false 130 for i, participant := range cfg.Participants { 131 if participant.NodeID == cfg.LocalID { 132 index = uint(i) 133 takesPart = true 134 break 135 } 136 } 137 require.True(t, takesPart) 138 139 // initialize the instance 140 in := Instance{ 141 142 // instance parameters 143 participants: cfg.Participants, 144 localID: cfg.LocalID, 145 blockVoteIn: cfg.IncomingVotes, 146 blockVoteOut: cfg.OutgoingVotes, 147 blockPropIn: cfg.IncomingProposals, 148 blockPropOut: cfg.OutgoingProposals, 149 blockTimeoutObjectIn: cfg.IncomingTimeoutObjects, 150 blockTimeoutObjectOut: cfg.OutgoingTimeoutObjects, 151 stop: cfg.StopCondition, 152 153 // instance data 154 pendings: make(map[flow.Identifier]*model.Proposal), 155 headers: make(map[flow.Identifier]*flow.Header), 156 queue: make(chan interface{}, 1024), 157 158 // instance mocks 159 committee: &mocks.DynamicCommittee{}, 160 builder: &module.Builder{}, 161 persist: &mocks.Persister{}, 162 signer: &mocks.Signer{}, 163 verifier: &mocks.Verifier{}, 164 notifier: NewMockedCommunicatorConsumer(), 165 finalizer: &module.Finalizer{}, 166 } 167 168 // insert root block into headers register 169 in.headers[cfg.Root.ID()] = cfg.Root 170 171 // program the hotstuff committee state 172 in.committee.On("IdentitiesByEpoch", mock.Anything).Return( 173 func(_ uint64) flow.IdentityList { 174 return in.participants 175 }, 176 nil, 177 ) 178 for _, participant := range in.participants { 179 in.committee.On("IdentityByBlock", mock.Anything, participant.NodeID).Return(participant, nil) 180 in.committee.On("IdentityByEpoch", mock.Anything, participant.NodeID).Return(participant, nil) 181 } 182 in.committee.On("Self").Return(in.localID) 183 in.committee.On("LeaderForView", mock.Anything).Return( 184 func(view uint64) flow.Identifier { 185 return in.participants[int(view)%len(in.participants)].NodeID 186 }, nil, 187 ) 188 in.committee.On("QuorumThresholdForView", mock.Anything).Return(committees.WeightThresholdToBuildQC(in.participants.TotalWeight()), nil) 189 in.committee.On("TimeoutThresholdForView", mock.Anything).Return(committees.WeightThresholdToTimeout(in.participants.TotalWeight()), nil) 190 191 // program the builder module behaviour 192 in.builder.On("BuildOn", mock.Anything, mock.Anything).Return( 193 func(parentID flow.Identifier, setter func(*flow.Header) error) *flow.Header { 194 in.updatingBlocks.Lock() 195 defer in.updatingBlocks.Unlock() 196 197 parent, ok := in.headers[parentID] 198 if !ok { 199 return nil 200 } 201 header := &flow.Header{ 202 ChainID: "chain", 203 ParentID: parentID, 204 ParentView: parent.View, 205 Height: parent.Height + 1, 206 PayloadHash: unittest.IdentifierFixture(), 207 Timestamp: time.Now().UTC(), 208 } 209 require.NoError(t, setter(header)) 210 in.headers[header.ID()] = header 211 return header 212 }, 213 func(parentID flow.Identifier, setter func(*flow.Header) error) error { 214 in.updatingBlocks.RLock() 215 _, ok := in.headers[parentID] 216 in.updatingBlocks.RUnlock() 217 if !ok { 218 return fmt.Errorf("parent block not found (parent: %x)", parentID) 219 } 220 return nil 221 }, 222 ) 223 224 // check on stop condition, stop the tests as soon as entering a certain view 225 in.persist.On("PutSafetyData", mock.Anything).Return(nil) 226 in.persist.On("PutLivenessData", mock.Anything).Return(nil) 227 228 // program the hotstuff signer behaviour 229 in.signer.On("CreateProposal", mock.Anything).Return( 230 func(block *model.Block) *model.Proposal { 231 proposal := &model.Proposal{ 232 Block: block, 233 SigData: nil, 234 } 235 return proposal 236 }, 237 nil, 238 ) 239 in.signer.On("CreateVote", mock.Anything).Return( 240 func(block *model.Block) *model.Vote { 241 vote := &model.Vote{ 242 View: block.View, 243 BlockID: block.BlockID, 244 SignerID: in.localID, 245 SigData: unittest.RandomBytes(msig.SigLen * 2), // double sig, one staking, one beacon 246 } 247 return vote 248 }, 249 nil, 250 ) 251 in.signer.On("CreateTimeout", mock.Anything, mock.Anything, mock.Anything).Return( 252 func(curView uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) *model.TimeoutObject { 253 timeoutObject := &model.TimeoutObject{ 254 View: curView, 255 NewestQC: newestQC, 256 LastViewTC: lastViewTC, 257 SignerID: in.localID, 258 SigData: unittest.RandomBytes(msig.SigLen), 259 } 260 return timeoutObject 261 }, 262 nil, 263 ) 264 in.signer.On("CreateQC", mock.Anything).Return( 265 func(votes []*model.Vote) *flow.QuorumCertificate { 266 voterIDs := make(flow.IdentifierList, 0, len(votes)) 267 for _, vote := range votes { 268 voterIDs = append(voterIDs, vote.SignerID) 269 } 270 271 signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), voterIDs) 272 require.NoError(t, err, "could not encode signer indices") 273 274 qc := &flow.QuorumCertificate{ 275 View: votes[0].View, 276 BlockID: votes[0].BlockID, 277 SignerIndices: signerIndices, 278 SigData: nil, 279 } 280 return qc 281 }, 282 nil, 283 ) 284 285 // program the hotstuff verifier behaviour 286 in.verifier.On("VerifyVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 287 in.verifier.On("VerifyQC", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 288 in.verifier.On("VerifyTC", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 289 290 // program the hotstuff communicator behaviour 291 in.notifier.On("OnOwnProposal", mock.Anything, mock.Anything).Run( 292 func(args mock.Arguments) { 293 header, ok := args[0].(*flow.Header) 294 require.True(t, ok) 295 296 // sender should always have the parent 297 in.updatingBlocks.RLock() 298 _, exists := in.headers[header.ParentID] 299 in.updatingBlocks.RUnlock() 300 301 if !exists { 302 t.Fatalf("parent for proposal not found parent: %x", header.ParentID) 303 } 304 305 // convert into proposal immediately 306 proposal := model.ProposalFromFlow(header) 307 308 // store locally and loop back to engine for processing 309 in.ProcessBlock(proposal) 310 }, 311 ) 312 in.notifier.On("OnOwnTimeout", mock.Anything).Run(func(args mock.Arguments) { 313 timeoutObject, ok := args[0].(*model.TimeoutObject) 314 require.True(t, ok) 315 in.queue <- timeoutObject 316 }, 317 ) 318 // in case of single node setup we should just forward vote to our own node 319 // for multi-node setup this method will be overridden 320 in.notifier.On("OnOwnVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 321 in.queue <- &model.Vote{ 322 View: args[1].(uint64), 323 BlockID: args[0].(flow.Identifier), 324 SignerID: in.localID, 325 SigData: args[2].([]byte), 326 } 327 }) 328 329 // program the finalizer module behaviour 330 in.finalizer.On("MakeFinal", mock.Anything).Return( 331 func(blockID flow.Identifier) error { 332 333 // as we don't use mocks to assert expectations, but only to 334 // simulate behaviour, we should drop the call data regularly 335 in.updatingBlocks.RLock() 336 block, found := in.headers[blockID] 337 in.updatingBlocks.RUnlock() 338 if !found { 339 return fmt.Errorf("can't broadcast with unknown parent") 340 } 341 if block.Height%100 == 0 { 342 in.committee.Calls = nil 343 in.builder.Calls = nil 344 in.signer.Calls = nil 345 in.verifier.Calls = nil 346 in.notifier.Calls = nil 347 in.finalizer.Calls = nil 348 } 349 350 return nil 351 }, 352 ) 353 354 // initialize error handling and logging 355 var err error 356 zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } 357 // log with node index an ID 358 log := unittest.Logger().With(). 359 Int("index", int(index)). 360 Hex("node_id", in.localID[:]). 361 Logger() 362 notifier := pubsub.NewDistributor() 363 logConsumer := notifications.NewLogConsumer(log) 364 notifier.AddConsumer(logConsumer) 365 notifier.AddConsumer(in.notifier) 366 367 // initialize the block producer 368 in.producer, err = blockproducer.New(in.signer, in.committee, in.builder) 369 require.NoError(t, err) 370 371 // initialize the finalizer 372 rootBlock := model.BlockFromFlow(cfg.Root) 373 374 signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), in.participants.NodeIDs()) 375 require.NoError(t, err, "could not encode signer indices") 376 377 rootQC := &flow.QuorumCertificate{ 378 View: rootBlock.View, 379 BlockID: rootBlock.BlockID, 380 SignerIndices: signerIndices, 381 } 382 certifiedRootBlock, err := model.NewCertifiedBlock(rootBlock, rootQC) 383 require.NoError(t, err) 384 385 livenessData := &hotstuff.LivenessData{ 386 CurrentView: rootQC.View + 1, 387 NewestQC: rootQC, 388 } 389 390 in.persist.On("GetLivenessData").Return(livenessData, nil).Once() 391 392 // initialize the pacemaker 393 controller := timeout.NewController(cfg.Timeouts) 394 in.pacemaker, err = pacemaker.New(controller, pacemaker.NoProposalDelay(), notifier, in.persist) 395 require.NoError(t, err) 396 397 // initialize the forks handler 398 in.forks, err = forks.New(&certifiedRootBlock, in.finalizer, notifier) 399 require.NoError(t, err) 400 401 // initialize the validator 402 in.validator = validator.New(in.committee, in.verifier) 403 404 weight := uint64(flow.DefaultInitialWeight) 405 406 indices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), []flow.Identifier(in.participants.NodeIDs())) 407 require.NoError(t, err) 408 409 packer := &mocks.Packer{} 410 packer.On("Pack", mock.Anything, mock.Anything).Return(indices, unittest.RandomBytes(128), nil).Maybe() 411 412 onQCCreated := func(qc *flow.QuorumCertificate) { 413 in.queue <- qc 414 } 415 416 minRequiredWeight := committees.WeightThresholdToBuildQC(uint64(in.participants.Count()) * weight) 417 voteProcessorFactory := mocks.NewVoteProcessorFactory(t) 418 voteProcessorFactory.On("Create", mock.Anything, mock.Anything).Return( 419 func(log zerolog.Logger, proposal *model.Proposal) hotstuff.VerifyingVoteProcessor { 420 stakingSigAggtor := helper.MakeWeightedSignatureAggregator(weight) 421 stakingSigAggtor.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe() 422 423 rbRector := helper.MakeRandomBeaconReconstructor(msig.RandomBeaconThreshold(int(in.participants.Count()))) 424 rbRector.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe() 425 426 return votecollector.NewCombinedVoteProcessor( 427 log, proposal.Block, 428 stakingSigAggtor, rbRector, 429 onQCCreated, 430 packer, 431 minRequiredWeight, 432 ) 433 }, nil).Maybe() 434 435 voteAggregationDistributor := pubsub.NewVoteAggregationDistributor() 436 createCollectorFactoryMethod := votecollector.NewStateMachineFactory(log, voteAggregationDistributor, voteProcessorFactory.Create) 437 voteCollectors := voteaggregator.NewVoteCollectors(log, livenessData.CurrentView, workerpool.New(2), createCollectorFactoryMethod) 438 439 metricsCollector := metrics.NewNoopCollector() 440 441 // initialize the vote aggregator 442 in.voteAggregator, err = voteaggregator.NewVoteAggregator( 443 log, 444 metricsCollector, 445 metricsCollector, 446 metricsCollector, 447 voteAggregationDistributor, 448 livenessData.CurrentView, 449 voteCollectors, 450 ) 451 require.NoError(t, err) 452 453 // initialize factories for timeout collector and timeout processor 454 timeoutAggregationDistributor := pubsub.NewTimeoutAggregationDistributor() 455 timeoutProcessorFactory := mocks.NewTimeoutProcessorFactory(t) 456 timeoutProcessorFactory.On("Create", mock.Anything).Return( 457 func(view uint64) hotstuff.TimeoutProcessor { 458 // mock signature aggregator which doesn't perform any crypto operations and just tracks total weight 459 aggregator := &mocks.TimeoutSignatureAggregator{} 460 totalWeight := atomic.NewUint64(0) 461 newestView := counters.NewMonotonousCounter(0) 462 aggregator.On("View").Return(view).Maybe() 463 aggregator.On("TotalWeight").Return(func() uint64 { 464 return totalWeight.Load() 465 }).Maybe() 466 aggregator.On("VerifyAndAdd", mock.Anything, mock.Anything, mock.Anything).Return( 467 func(signerID flow.Identifier, _ crypto.Signature, newestQCView uint64) uint64 { 468 newestView.Set(newestQCView) 469 identity, ok := in.participants.ByNodeID(signerID) 470 require.True(t, ok) 471 return totalWeight.Add(identity.Weight) 472 }, nil, 473 ).Maybe() 474 aggregator.On("Aggregate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( 475 func() []hotstuff.TimeoutSignerInfo { 476 signersData := make([]hotstuff.TimeoutSignerInfo, 0, len(in.participants)) 477 newestQCView := newestView.Value() 478 for _, signer := range in.participants.NodeIDs() { 479 signersData = append(signersData, hotstuff.TimeoutSignerInfo{ 480 NewestQCView: newestQCView, 481 Signer: signer, 482 }) 483 } 484 return signersData 485 }, 486 unittest.SignatureFixture(), 487 nil, 488 ).Maybe() 489 490 p, err := timeoutcollector.NewTimeoutProcessor( 491 unittest.Logger(), 492 in.committee, 493 in.validator, 494 aggregator, 495 timeoutAggregationDistributor, 496 ) 497 require.NoError(t, err) 498 return p 499 }, nil).Maybe() 500 timeoutCollectorFactory := timeoutcollector.NewTimeoutCollectorFactory( 501 unittest.Logger(), 502 timeoutAggregationDistributor, 503 timeoutProcessorFactory, 504 ) 505 timeoutCollectors := timeoutaggregator.NewTimeoutCollectors( 506 log, 507 metricsCollector, 508 livenessData.CurrentView, 509 timeoutCollectorFactory, 510 ) 511 512 // initialize the timeout aggregator 513 in.timeoutAggregator, err = timeoutaggregator.NewTimeoutAggregator( 514 log, 515 metricsCollector, 516 metricsCollector, 517 metricsCollector, 518 livenessData.CurrentView, 519 timeoutCollectors, 520 ) 521 require.NoError(t, err) 522 523 safetyData := &hotstuff.SafetyData{ 524 LockedOneChainView: rootBlock.View, 525 HighestAcknowledgedView: rootBlock.View, 526 } 527 in.persist.On("GetSafetyData", mock.Anything).Return(safetyData, nil).Once() 528 529 // initialize the safety rules 530 in.safetyRules, err = safetyrules.New(in.signer, in.persist, in.committee) 531 require.NoError(t, err) 532 533 // initialize the event handler 534 in.handler, err = eventhandler.NewEventHandler( 535 log, 536 in.pacemaker, 537 in.producer, 538 in.forks, 539 in.persist, 540 in.committee, 541 in.safetyRules, 542 notifier, 543 ) 544 require.NoError(t, err) 545 546 timeoutAggregationDistributor.AddTimeoutCollectorConsumer(logConsumer) 547 timeoutAggregationDistributor.AddTimeoutCollectorConsumer(&in) 548 549 voteAggregationDistributor.AddVoteCollectorConsumer(logConsumer) 550 551 return &in 552 } 553 554 func (in *Instance) Run() error { 555 ctx, cancel := context.WithCancel(context.Background()) 556 defer func() { 557 cancel() 558 <-util.AllDone(in.voteAggregator, in.timeoutAggregator) 559 }() 560 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 561 in.voteAggregator.Start(signalerCtx) 562 in.timeoutAggregator.Start(signalerCtx) 563 <-util.AllReady(in.voteAggregator, in.timeoutAggregator) 564 565 // start the event handler 566 err := in.handler.Start(ctx) 567 if err != nil { 568 return fmt.Errorf("could not start event handler: %w", err) 569 } 570 571 // run until an error or stop condition is reached 572 for { 573 574 // check on stop conditions 575 if in.stop(in) { 576 return errStopCondition 577 } 578 579 // we handle timeouts with priority 580 select { 581 case <-in.handler.TimeoutChannel(): 582 err := in.handler.OnLocalTimeout() 583 if err != nil { 584 return fmt.Errorf("could not process timeout: %w", err) 585 } 586 default: 587 } 588 589 // check on stop conditions 590 if in.stop(in) { 591 return errStopCondition 592 } 593 594 // otherwise, process first received event 595 select { 596 case <-in.handler.TimeoutChannel(): 597 err := in.handler.OnLocalTimeout() 598 if err != nil { 599 return fmt.Errorf("could not process timeout: %w", err) 600 } 601 case msg := <-in.queue: 602 switch m := msg.(type) { 603 case *model.Proposal: 604 // add block to aggregator 605 in.voteAggregator.AddBlock(m) 606 // then pass to event handler 607 err := in.handler.OnReceiveProposal(m) 608 if err != nil { 609 return fmt.Errorf("could not process proposal: %w", err) 610 } 611 case *model.Vote: 612 in.voteAggregator.AddVote(m) 613 case *model.TimeoutObject: 614 in.timeoutAggregator.AddTimeout(m) 615 case *flow.QuorumCertificate: 616 err := in.handler.OnReceiveQc(m) 617 if err != nil { 618 return fmt.Errorf("could not process received QC: %w", err) 619 } 620 case *flow.TimeoutCertificate: 621 err := in.handler.OnReceiveTc(m) 622 if err != nil { 623 return fmt.Errorf("could not process received TC: %w", err) 624 } 625 case *hotstuff.PartialTcCreated: 626 err := in.handler.OnPartialTcCreated(m) 627 if err != nil { 628 return fmt.Errorf("could not process partial TC: %w", err) 629 } 630 } 631 } 632 } 633 } 634 635 func (in *Instance) ProcessBlock(proposal *model.Proposal) { 636 in.updatingBlocks.Lock() 637 defer in.updatingBlocks.Unlock() 638 _, parentExists := in.headers[proposal.Block.QC.BlockID] 639 640 if parentExists { 641 next := proposal 642 for next != nil { 643 in.headers[next.Block.BlockID] = model.ProposalToFlow(next) 644 645 in.queue <- next 646 // keep processing the pending blocks 647 next = in.pendings[next.Block.QC.BlockID] 648 } 649 } else { 650 // cache it in pendings by ParentID 651 in.pendings[proposal.Block.QC.BlockID] = proposal 652 } 653 } 654 655 func (in *Instance) OnTcConstructedFromTimeouts(tc *flow.TimeoutCertificate) { 656 in.queue <- tc 657 } 658 659 func (in *Instance) OnPartialTcCreated(view uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) { 660 in.queue <- &hotstuff.PartialTcCreated{ 661 View: view, 662 NewestQC: newestQC, 663 LastViewTC: lastViewTC, 664 } 665 } 666 667 func (in *Instance) OnNewQcDiscovered(qc *flow.QuorumCertificate) { 668 in.queue <- qc 669 } 670 671 func (in *Instance) OnNewTcDiscovered(tc *flow.TimeoutCertificate) { 672 in.queue <- tc 673 } 674 675 func (in *Instance) OnTimeoutProcessed(*model.TimeoutObject) {}