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) {}