github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/onflow/crypto"
    12  	"github.com/rs/zerolog"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  	"go.uber.org/atomic"
    16  
    17  	"github.com/onflow/flow-go/consensus/hotstuff"
    18  	"github.com/onflow/flow-go/consensus/hotstuff/blockproducer"
    19  	"github.com/onflow/flow-go/consensus/hotstuff/committees"
    20  	"github.com/onflow/flow-go/consensus/hotstuff/eventhandler"
    21  	"github.com/onflow/flow-go/consensus/hotstuff/forks"
    22  	"github.com/onflow/flow-go/consensus/hotstuff/helper"
    23  	"github.com/onflow/flow-go/consensus/hotstuff/mocks"
    24  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    25  	"github.com/onflow/flow-go/consensus/hotstuff/notifications"
    26  	"github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub"
    27  	"github.com/onflow/flow-go/consensus/hotstuff/pacemaker"
    28  	"github.com/onflow/flow-go/consensus/hotstuff/pacemaker/timeout"
    29  	"github.com/onflow/flow-go/consensus/hotstuff/safetyrules"
    30  	"github.com/onflow/flow-go/consensus/hotstuff/timeoutaggregator"
    31  	"github.com/onflow/flow-go/consensus/hotstuff/timeoutcollector"
    32  	"github.com/onflow/flow-go/consensus/hotstuff/validator"
    33  	"github.com/onflow/flow-go/consensus/hotstuff/voteaggregator"
    34  	"github.com/onflow/flow-go/consensus/hotstuff/votecollector"
    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.IdentitySkeletonList {
   174  			return in.participants.ToSkeleton()
   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.IdentitySkeleton, 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.ToSkeleton().TotalWeight()), nil)
   189  	in.committee.On("TimeoutThresholdForView", mock.Anything).Return(committees.WeightThresholdToTimeout(in.participants.ToSkeleton().TotalWeight()), nil)
   190  
   191  	// program the builder module behaviour
   192  	in.builder.On("BuildOn", mock.Anything, mock.Anything, mock.Anything).Return(
   193  		func(parentID flow.Identifier, setter func(*flow.Header) error, sign 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  			require.NoError(t, sign(header))
   211  			in.headers[header.ID()] = header
   212  			return header
   213  		},
   214  		func(parentID flow.Identifier, _ func(*flow.Header) error, _ func(*flow.Header) error) error {
   215  			in.updatingBlocks.RLock()
   216  			_, ok := in.headers[parentID]
   217  			in.updatingBlocks.RUnlock()
   218  			if !ok {
   219  				return fmt.Errorf("parent block not found (parent: %x)", parentID)
   220  			}
   221  			return nil
   222  		},
   223  	)
   224  
   225  	// check on stop condition, stop the tests as soon as entering a certain view
   226  	in.persist.On("PutSafetyData", mock.Anything).Return(nil)
   227  	in.persist.On("PutLivenessData", mock.Anything).Return(nil)
   228  
   229  	// program the hotstuff signer behaviour
   230  	in.signer.On("CreateProposal", mock.Anything).Return(
   231  		func(block *model.Block) *model.Proposal {
   232  			proposal := &model.Proposal{
   233  				Block:   block,
   234  				SigData: nil,
   235  			}
   236  			return proposal
   237  		},
   238  		nil,
   239  	)
   240  	in.signer.On("CreateVote", mock.Anything).Return(
   241  		func(block *model.Block) *model.Vote {
   242  			vote := &model.Vote{
   243  				View:     block.View,
   244  				BlockID:  block.BlockID,
   245  				SignerID: in.localID,
   246  				SigData:  unittest.RandomBytes(msig.SigLen * 2), // double sig, one staking, one beacon
   247  			}
   248  			return vote
   249  		},
   250  		nil,
   251  	)
   252  	in.signer.On("CreateTimeout", mock.Anything, mock.Anything, mock.Anything).Return(
   253  		func(curView uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) *model.TimeoutObject {
   254  			timeoutObject := &model.TimeoutObject{
   255  				View:       curView,
   256  				NewestQC:   newestQC,
   257  				LastViewTC: lastViewTC,
   258  				SignerID:   in.localID,
   259  				SigData:    unittest.RandomBytes(msig.SigLen),
   260  			}
   261  			return timeoutObject
   262  		},
   263  		nil,
   264  	)
   265  	in.signer.On("CreateQC", mock.Anything).Return(
   266  		func(votes []*model.Vote) *flow.QuorumCertificate {
   267  			voterIDs := make(flow.IdentifierList, 0, len(votes))
   268  			for _, vote := range votes {
   269  				voterIDs = append(voterIDs, vote.SignerID)
   270  			}
   271  
   272  			signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), voterIDs)
   273  			require.NoError(t, err, "could not encode signer indices")
   274  
   275  			qc := &flow.QuorumCertificate{
   276  				View:          votes[0].View,
   277  				BlockID:       votes[0].BlockID,
   278  				SignerIndices: signerIndices,
   279  				SigData:       nil,
   280  			}
   281  			return qc
   282  		},
   283  		nil,
   284  	)
   285  
   286  	// program the hotstuff verifier behaviour
   287  	in.verifier.On("VerifyVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   288  	in.verifier.On("VerifyQC", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   289  	in.verifier.On("VerifyTC", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   290  
   291  	// program the hotstuff communicator behaviour
   292  	in.notifier.On("OnOwnProposal", mock.Anything, mock.Anything).Run(
   293  		func(args mock.Arguments) {
   294  			header, ok := args[0].(*flow.Header)
   295  			require.True(t, ok)
   296  
   297  			// sender should always have the parent
   298  			in.updatingBlocks.RLock()
   299  			_, exists := in.headers[header.ParentID]
   300  			in.updatingBlocks.RUnlock()
   301  
   302  			if !exists {
   303  				t.Fatalf("parent for proposal not found parent: %x", header.ParentID)
   304  			}
   305  
   306  			// convert into proposal immediately
   307  			proposal := model.ProposalFromFlow(header)
   308  
   309  			// store locally and loop back to engine for processing
   310  			in.ProcessBlock(proposal)
   311  		},
   312  	)
   313  	in.notifier.On("OnOwnTimeout", mock.Anything).Run(func(args mock.Arguments) {
   314  		timeoutObject, ok := args[0].(*model.TimeoutObject)
   315  		require.True(t, ok)
   316  		in.queue <- timeoutObject
   317  	},
   318  	)
   319  	// in case of single node setup we should just forward vote to our own node
   320  	// for multi-node setup this method will be overridden
   321  	in.notifier.On("OnOwnVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   322  		in.queue <- &model.Vote{
   323  			View:     args[1].(uint64),
   324  			BlockID:  args[0].(flow.Identifier),
   325  			SignerID: in.localID,
   326  			SigData:  args[2].([]byte),
   327  		}
   328  	})
   329  
   330  	// program the finalizer module behaviour
   331  	in.finalizer.On("MakeFinal", mock.Anything).Return(
   332  		func(blockID flow.Identifier) error {
   333  
   334  			// as we don't use mocks to assert expectations, but only to
   335  			// simulate behaviour, we should drop the call data regularly
   336  			in.updatingBlocks.RLock()
   337  			block, found := in.headers[blockID]
   338  			in.updatingBlocks.RUnlock()
   339  			if !found {
   340  				return fmt.Errorf("can't broadcast with unknown parent")
   341  			}
   342  			if block.Height%100 == 0 {
   343  				in.committee.Calls = nil
   344  				in.builder.Calls = nil
   345  				in.signer.Calls = nil
   346  				in.verifier.Calls = nil
   347  				in.notifier.Calls = nil
   348  				in.finalizer.Calls = nil
   349  			}
   350  
   351  			return nil
   352  		},
   353  	)
   354  
   355  	// initialize error handling and logging
   356  	var err error
   357  	zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() }
   358  	// log with node index an ID
   359  	log := unittest.Logger().With().
   360  		Int("index", int(index)).
   361  		Hex("node_id", in.localID[:]).
   362  		Logger()
   363  	notifier := pubsub.NewDistributor()
   364  	logConsumer := notifications.NewLogConsumer(log)
   365  	notifier.AddConsumer(logConsumer)
   366  	notifier.AddConsumer(in.notifier)
   367  
   368  	// initialize the block producer
   369  	in.producer, err = blockproducer.New(in.signer, in.committee, in.builder)
   370  	require.NoError(t, err)
   371  
   372  	// initialize the finalizer
   373  	rootBlock := model.BlockFromFlow(cfg.Root)
   374  
   375  	signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), in.participants.NodeIDs())
   376  	require.NoError(t, err, "could not encode signer indices")
   377  
   378  	rootQC := &flow.QuorumCertificate{
   379  		View:          rootBlock.View,
   380  		BlockID:       rootBlock.BlockID,
   381  		SignerIndices: signerIndices,
   382  	}
   383  	certifiedRootBlock, err := model.NewCertifiedBlock(rootBlock, rootQC)
   384  	require.NoError(t, err)
   385  
   386  	livenessData := &hotstuff.LivenessData{
   387  		CurrentView: rootQC.View + 1,
   388  		NewestQC:    rootQC,
   389  	}
   390  
   391  	in.persist.On("GetLivenessData").Return(livenessData, nil).Once()
   392  
   393  	// initialize the pacemaker
   394  	controller := timeout.NewController(cfg.Timeouts)
   395  	in.pacemaker, err = pacemaker.New(controller, pacemaker.NoProposalDelay(), notifier, in.persist)
   396  	require.NoError(t, err)
   397  
   398  	// initialize the forks handler
   399  	in.forks, err = forks.New(&certifiedRootBlock, in.finalizer, notifier)
   400  	require.NoError(t, err)
   401  
   402  	// initialize the validator
   403  	in.validator = validator.New(in.committee, in.verifier)
   404  
   405  	weight := uint64(flow.DefaultInitialWeight)
   406  
   407  	indices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), []flow.Identifier(in.participants.NodeIDs()))
   408  	require.NoError(t, err)
   409  
   410  	packer := &mocks.Packer{}
   411  	packer.On("Pack", mock.Anything, mock.Anything).Return(indices, unittest.RandomBytes(128), nil).Maybe()
   412  
   413  	onQCCreated := func(qc *flow.QuorumCertificate) {
   414  		in.queue <- qc
   415  	}
   416  
   417  	minRequiredWeight := committees.WeightThresholdToBuildQC(uint64(len(in.participants)) * weight)
   418  	voteProcessorFactory := mocks.NewVoteProcessorFactory(t)
   419  	voteProcessorFactory.On("Create", mock.Anything, mock.Anything).Return(
   420  		func(log zerolog.Logger, proposal *model.Proposal) hotstuff.VerifyingVoteProcessor {
   421  			stakingSigAggtor := helper.MakeWeightedSignatureAggregator(weight)
   422  			stakingSigAggtor.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe()
   423  
   424  			rbRector := helper.MakeRandomBeaconReconstructor(msig.RandomBeaconThreshold(len(in.participants)))
   425  			rbRector.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe()
   426  
   427  			return votecollector.NewCombinedVoteProcessor(
   428  				log, proposal.Block,
   429  				stakingSigAggtor, rbRector,
   430  				onQCCreated,
   431  				packer,
   432  				minRequiredWeight,
   433  			)
   434  		}, nil).Maybe()
   435  
   436  	voteAggregationDistributor := pubsub.NewVoteAggregationDistributor()
   437  	createCollectorFactoryMethod := votecollector.NewStateMachineFactory(log, voteAggregationDistributor, voteProcessorFactory.Create)
   438  	voteCollectors := voteaggregator.NewVoteCollectors(log, livenessData.CurrentView, workerpool.New(2), createCollectorFactoryMethod)
   439  
   440  	metricsCollector := metrics.NewNoopCollector()
   441  
   442  	// initialize the vote aggregator
   443  	in.voteAggregator, err = voteaggregator.NewVoteAggregator(
   444  		log,
   445  		metricsCollector,
   446  		metricsCollector,
   447  		metricsCollector,
   448  		voteAggregationDistributor,
   449  		livenessData.CurrentView,
   450  		voteCollectors,
   451  	)
   452  	require.NoError(t, err)
   453  
   454  	// initialize factories for timeout collector and timeout processor
   455  	timeoutAggregationDistributor := pubsub.NewTimeoutAggregationDistributor()
   456  	timeoutProcessorFactory := mocks.NewTimeoutProcessorFactory(t)
   457  	timeoutProcessorFactory.On("Create", mock.Anything).Return(
   458  		func(view uint64) hotstuff.TimeoutProcessor {
   459  			// mock signature aggregator which doesn't perform any crypto operations and just tracks total weight
   460  			aggregator := &mocks.TimeoutSignatureAggregator{}
   461  			totalWeight := atomic.NewUint64(0)
   462  			newestView := counters.NewMonotonousCounter(0)
   463  			aggregator.On("View").Return(view).Maybe()
   464  			aggregator.On("TotalWeight").Return(func() uint64 {
   465  				return totalWeight.Load()
   466  			}).Maybe()
   467  			aggregator.On("VerifyAndAdd", mock.Anything, mock.Anything, mock.Anything).Return(
   468  				func(signerID flow.Identifier, _ crypto.Signature, newestQCView uint64) uint64 {
   469  					newestView.Set(newestQCView)
   470  					identity, ok := in.participants.ByNodeID(signerID)
   471  					require.True(t, ok)
   472  					return totalWeight.Add(identity.InitialWeight)
   473  				}, nil,
   474  			).Maybe()
   475  			aggregator.On("Aggregate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(
   476  				func() []hotstuff.TimeoutSignerInfo {
   477  					signersData := make([]hotstuff.TimeoutSignerInfo, 0, len(in.participants))
   478  					newestQCView := newestView.Value()
   479  					for _, signer := range in.participants.NodeIDs() {
   480  						signersData = append(signersData, hotstuff.TimeoutSignerInfo{
   481  							NewestQCView: newestQCView,
   482  							Signer:       signer,
   483  						})
   484  					}
   485  					return signersData
   486  				},
   487  				unittest.SignatureFixture(),
   488  				nil,
   489  			).Maybe()
   490  
   491  			p, err := timeoutcollector.NewTimeoutProcessor(
   492  				unittest.Logger(),
   493  				in.committee,
   494  				in.validator,
   495  				aggregator,
   496  				timeoutAggregationDistributor,
   497  			)
   498  			require.NoError(t, err)
   499  			return p
   500  		}, nil).Maybe()
   501  	timeoutCollectorFactory := timeoutcollector.NewTimeoutCollectorFactory(
   502  		unittest.Logger(),
   503  		timeoutAggregationDistributor,
   504  		timeoutProcessorFactory,
   505  	)
   506  	timeoutCollectors := timeoutaggregator.NewTimeoutCollectors(
   507  		log,
   508  		metricsCollector,
   509  		livenessData.CurrentView,
   510  		timeoutCollectorFactory,
   511  	)
   512  
   513  	// initialize the timeout aggregator
   514  	in.timeoutAggregator, err = timeoutaggregator.NewTimeoutAggregator(
   515  		log,
   516  		metricsCollector,
   517  		metricsCollector,
   518  		metricsCollector,
   519  		livenessData.CurrentView,
   520  		timeoutCollectors,
   521  	)
   522  	require.NoError(t, err)
   523  
   524  	safetyData := &hotstuff.SafetyData{
   525  		LockedOneChainView:      rootBlock.View,
   526  		HighestAcknowledgedView: rootBlock.View,
   527  	}
   528  	in.persist.On("GetSafetyData", mock.Anything).Return(safetyData, nil).Once()
   529  
   530  	// initialize the safety rules
   531  	in.safetyRules, err = safetyrules.New(in.signer, in.persist, in.committee)
   532  	require.NoError(t, err)
   533  
   534  	// initialize the event handler
   535  	in.handler, err = eventhandler.NewEventHandler(
   536  		log,
   537  		in.pacemaker,
   538  		in.producer,
   539  		in.forks,
   540  		in.persist,
   541  		in.committee,
   542  		in.safetyRules,
   543  		notifier,
   544  	)
   545  	require.NoError(t, err)
   546  
   547  	timeoutAggregationDistributor.AddTimeoutCollectorConsumer(logConsumer)
   548  	timeoutAggregationDistributor.AddTimeoutCollectorConsumer(&in)
   549  
   550  	voteAggregationDistributor.AddVoteCollectorConsumer(logConsumer)
   551  
   552  	return &in
   553  }
   554  
   555  func (in *Instance) Run() error {
   556  	ctx, cancel := context.WithCancel(context.Background())
   557  	defer func() {
   558  		cancel()
   559  		<-util.AllDone(in.voteAggregator, in.timeoutAggregator)
   560  	}()
   561  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
   562  	in.voteAggregator.Start(signalerCtx)
   563  	in.timeoutAggregator.Start(signalerCtx)
   564  	<-util.AllReady(in.voteAggregator, in.timeoutAggregator)
   565  
   566  	// start the event handler
   567  	err := in.handler.Start(ctx)
   568  	if err != nil {
   569  		return fmt.Errorf("could not start event handler: %w", err)
   570  	}
   571  
   572  	// run until an error or stop condition is reached
   573  	for {
   574  
   575  		// check on stop conditions
   576  		if in.stop(in) {
   577  			return errStopCondition
   578  		}
   579  
   580  		// we handle timeouts with priority
   581  		select {
   582  		case <-in.handler.TimeoutChannel():
   583  			err := in.handler.OnLocalTimeout()
   584  			if err != nil {
   585  				return fmt.Errorf("could not process timeout: %w", err)
   586  			}
   587  		default:
   588  		}
   589  
   590  		// check on stop conditions
   591  		if in.stop(in) {
   592  			return errStopCondition
   593  		}
   594  
   595  		// otherwise, process first received event
   596  		select {
   597  		case <-in.handler.TimeoutChannel():
   598  			err := in.handler.OnLocalTimeout()
   599  			if err != nil {
   600  				return fmt.Errorf("could not process timeout: %w", err)
   601  			}
   602  		case msg := <-in.queue:
   603  			switch m := msg.(type) {
   604  			case *model.Proposal:
   605  				// add block to aggregator
   606  				in.voteAggregator.AddBlock(m)
   607  				// then pass to event handler
   608  				err := in.handler.OnReceiveProposal(m)
   609  				if err != nil {
   610  					return fmt.Errorf("could not process proposal: %w", err)
   611  				}
   612  			case *model.Vote:
   613  				in.voteAggregator.AddVote(m)
   614  			case *model.TimeoutObject:
   615  				in.timeoutAggregator.AddTimeout(m)
   616  			case *flow.QuorumCertificate:
   617  				err := in.handler.OnReceiveQc(m)
   618  				if err != nil {
   619  					return fmt.Errorf("could not process received QC: %w", err)
   620  				}
   621  			case *flow.TimeoutCertificate:
   622  				err := in.handler.OnReceiveTc(m)
   623  				if err != nil {
   624  					return fmt.Errorf("could not process received TC: %w", err)
   625  				}
   626  			case *hotstuff.PartialTcCreated:
   627  				err := in.handler.OnPartialTcCreated(m)
   628  				if err != nil {
   629  					return fmt.Errorf("could not process partial TC: %w", err)
   630  				}
   631  			}
   632  		}
   633  	}
   634  }
   635  
   636  func (in *Instance) ProcessBlock(proposal *model.Proposal) {
   637  	in.updatingBlocks.Lock()
   638  	defer in.updatingBlocks.Unlock()
   639  	_, parentExists := in.headers[proposal.Block.QC.BlockID]
   640  
   641  	if parentExists {
   642  		next := proposal
   643  		for next != nil {
   644  			in.headers[next.Block.BlockID] = model.ProposalToFlow(next)
   645  
   646  			in.queue <- next
   647  			// keep processing the pending blocks
   648  			next = in.pendings[next.Block.QC.BlockID]
   649  		}
   650  	} else {
   651  		// cache it in pendings by ParentID
   652  		in.pendings[proposal.Block.QC.BlockID] = proposal
   653  	}
   654  }
   655  
   656  func (in *Instance) OnTcConstructedFromTimeouts(tc *flow.TimeoutCertificate) {
   657  	in.queue <- tc
   658  }
   659  
   660  func (in *Instance) OnPartialTcCreated(view uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) {
   661  	in.queue <- &hotstuff.PartialTcCreated{
   662  		View:       view,
   663  		NewestQC:   newestQC,
   664  		LastViewTC: lastViewTC,
   665  	}
   666  }
   667  
   668  func (in *Instance) OnNewQcDiscovered(qc *flow.QuorumCertificate) {
   669  	in.queue <- qc
   670  }
   671  
   672  func (in *Instance) OnNewTcDiscovered(tc *flow.TimeoutCertificate) {
   673  	in.queue <- tc
   674  }
   675  
   676  func (in *Instance) OnTimeoutProcessed(*model.TimeoutObject) {}