github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/integration/instance_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/gammazero/workerpool"
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/mock"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
    15  	"github.com/koko1123/flow-go-1/consensus/hotstuff/blockproducer"
    16  	"github.com/koko1123/flow-go-1/consensus/hotstuff/eventhandler"
    17  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks"
    18  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer"
    19  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks/forkchoice"
    20  	"github.com/koko1123/flow-go-1/consensus/hotstuff/helper"
    21  	"github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    22  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    23  	"github.com/koko1123/flow-go-1/consensus/hotstuff/notifications"
    24  	"github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker"
    25  	"github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker/timeout"
    26  	"github.com/koko1123/flow-go-1/consensus/hotstuff/validator"
    27  	"github.com/koko1123/flow-go-1/consensus/hotstuff/voteaggregator"
    28  	"github.com/koko1123/flow-go-1/consensus/hotstuff/votecollector"
    29  	"github.com/koko1123/flow-go-1/consensus/hotstuff/voter"
    30  	"github.com/koko1123/flow-go-1/model/flow"
    31  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    32  	module "github.com/koko1123/flow-go-1/module/mock"
    33  	msig "github.com/koko1123/flow-go-1/module/signature"
    34  	"github.com/koko1123/flow-go-1/utils/unittest"
    35  )
    36  
    37  type Instance struct {
    38  
    39  	// instance parameters
    40  	participants flow.IdentityList
    41  	localID      flow.Identifier
    42  	blockVoteIn  VoteFilter
    43  	blockVoteOut VoteFilter
    44  	blockPropIn  ProposalFilter
    45  	blockPropOut ProposalFilter
    46  	stop         Condition
    47  
    48  	// instance data
    49  	queue          chan interface{}
    50  	updatingBlocks sync.RWMutex
    51  	headers        map[flow.Identifier]*flow.Header
    52  	pendings       map[flow.Identifier]*model.Proposal // indexed by parent ID
    53  
    54  	// mocked dependencies
    55  	committee    *mocks.Committee
    56  	builder      *module.Builder
    57  	finalizer    *module.Finalizer
    58  	persist      *mocks.Persister
    59  	signer       *mocks.Signer
    60  	verifier     *mocks.Verifier
    61  	communicator *mocks.Communicator
    62  
    63  	// real dependencies
    64  	pacemaker  hotstuff.PaceMaker
    65  	producer   *blockproducer.BlockProducer
    66  	forks      *forks.Forks
    67  	aggregator *voteaggregator.VoteAggregator
    68  	voter      *voter.Voter
    69  	validator  *validator.Validator
    70  
    71  	// main logic
    72  	handler *eventhandler.EventHandler
    73  }
    74  
    75  func NewInstance(t require.TestingT, options ...Option) *Instance {
    76  
    77  	// generate random default identity
    78  	identity := unittest.IdentityFixture()
    79  
    80  	// initialize the default configuration
    81  	cfg := Config{
    82  		Root:              DefaultRoot(),
    83  		Participants:      flow.IdentityList{identity},
    84  		LocalID:           identity.NodeID,
    85  		Timeouts:          timeout.DefaultConfig,
    86  		IncomingVotes:     BlockNoVotes,
    87  		OutgoingVotes:     BlockNoVotes,
    88  		IncomingProposals: BlockNoProposals,
    89  		OutgoingProposals: BlockNoProposals,
    90  		StopCondition:     RightAway,
    91  	}
    92  
    93  	// apply the custom options
    94  	for _, option := range options {
    95  		option(&cfg)
    96  	}
    97  
    98  	// check the local ID is a participant
    99  	var index uint
   100  	takesPart := false
   101  	for i, participant := range cfg.Participants {
   102  		if participant.NodeID == cfg.LocalID {
   103  			index = uint(i)
   104  			takesPart = true
   105  			break
   106  		}
   107  	}
   108  	require.True(t, takesPart)
   109  
   110  	// initialize the instance
   111  	in := Instance{
   112  
   113  		// instance parameters
   114  		participants: cfg.Participants,
   115  		localID:      cfg.LocalID,
   116  		blockVoteIn:  cfg.IncomingVotes,
   117  		blockVoteOut: cfg.OutgoingVotes,
   118  		blockPropIn:  cfg.IncomingProposals,
   119  		blockPropOut: cfg.OutgoingProposals,
   120  		stop:         cfg.StopCondition,
   121  
   122  		// instance data
   123  		pendings: make(map[flow.Identifier]*model.Proposal),
   124  		headers:  make(map[flow.Identifier]*flow.Header),
   125  		queue:    make(chan interface{}, 1024),
   126  
   127  		// instance mocks
   128  		committee:    &mocks.Committee{},
   129  		builder:      &module.Builder{},
   130  		persist:      &mocks.Persister{},
   131  		signer:       &mocks.Signer{},
   132  		verifier:     &mocks.Verifier{},
   133  		communicator: &mocks.Communicator{},
   134  		finalizer:    &module.Finalizer{},
   135  	}
   136  
   137  	// insert root block into headers register
   138  	in.headers[cfg.Root.ID()] = cfg.Root
   139  
   140  	// program the hotstuff committee state
   141  	in.committee.On("Identities", mock.Anything).Return(
   142  		func(blockID flow.Identifier) flow.IdentityList {
   143  			return in.participants
   144  		},
   145  		nil,
   146  	)
   147  	for _, participant := range in.participants {
   148  		in.committee.On("Identity", mock.Anything, participant.NodeID).Return(participant, nil)
   149  	}
   150  	in.committee.On("Self").Return(in.localID)
   151  	in.committee.On("LeaderForView", mock.Anything).Return(
   152  		func(view uint64) flow.Identifier {
   153  			return in.participants[int(view)%len(in.participants)].NodeID
   154  		}, nil,
   155  	)
   156  
   157  	// program the builder module behaviour
   158  	in.builder.On("BuildOn", mock.Anything, mock.Anything).Return(
   159  		func(parentID flow.Identifier, setter func(*flow.Header) error) *flow.Header {
   160  			in.updatingBlocks.Lock()
   161  			defer in.updatingBlocks.Unlock()
   162  
   163  			parent, ok := in.headers[parentID]
   164  			if !ok {
   165  				return nil
   166  			}
   167  			header := &flow.Header{
   168  				ChainID:     "chain",
   169  				ParentID:    parentID,
   170  				Height:      parent.Height + 1,
   171  				PayloadHash: unittest.IdentifierFixture(),
   172  				Timestamp:   time.Now().UTC(),
   173  			}
   174  			require.NoError(t, setter(header))
   175  			in.headers[header.ID()] = header
   176  			return header
   177  		},
   178  		func(parentID flow.Identifier, setter func(*flow.Header) error) error {
   179  			in.updatingBlocks.RLock()
   180  			_, ok := in.headers[parentID]
   181  			in.updatingBlocks.RUnlock()
   182  			if !ok {
   183  				return fmt.Errorf("parent block not found (parent: %x)", parentID)
   184  			}
   185  			return nil
   186  		},
   187  	)
   188  
   189  	// check on stop condition, stop the tests as soon as entering a certain view
   190  	in.persist.On("PutStarted", mock.Anything).Return(nil)
   191  	in.persist.On("PutVoted", mock.Anything).Return(nil)
   192  
   193  	// program the hotstuff signer behaviour
   194  	in.signer.On("CreateProposal", mock.Anything).Return(
   195  		func(block *model.Block) *model.Proposal {
   196  			proposal := &model.Proposal{
   197  				Block:   block,
   198  				SigData: nil,
   199  			}
   200  			return proposal
   201  		},
   202  		nil,
   203  	)
   204  	in.signer.On("CreateVote", mock.Anything).Return(
   205  		func(block *model.Block) *model.Vote {
   206  			vote := &model.Vote{
   207  				View:     block.View,
   208  				BlockID:  block.BlockID,
   209  				SignerID: in.localID,
   210  				SigData:  unittest.RandomBytes(msig.SigLen * 2), // double sig, one staking, one beacon
   211  			}
   212  			return vote
   213  		},
   214  		nil,
   215  	)
   216  	in.signer.On("CreateQC", mock.Anything).Return(
   217  		func(votes []*model.Vote) *flow.QuorumCertificate {
   218  			voterIDs := make(flow.IdentifierList, 0, len(votes))
   219  			for _, vote := range votes {
   220  				voterIDs = append(voterIDs, vote.SignerID)
   221  			}
   222  
   223  			signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), voterIDs)
   224  			require.NoError(t, err, "could not encode signer indices")
   225  
   226  			qc := &flow.QuorumCertificate{
   227  				View:          votes[0].View,
   228  				BlockID:       votes[0].BlockID,
   229  				SignerIndices: signerIndices,
   230  				SigData:       nil,
   231  			}
   232  			return qc
   233  		},
   234  		nil,
   235  	)
   236  
   237  	// program the hotstuff verifier behaviour
   238  	in.verifier.On("VerifyVote", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   239  	in.verifier.On("VerifyQC", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   240  
   241  	// program the hotstuff communicator behaviour
   242  	in.communicator.On("BroadcastProposalWithDelay", mock.Anything, mock.Anything).Return(
   243  		func(header *flow.Header, delay time.Duration) error {
   244  
   245  			// sender should always have the parent
   246  			in.updatingBlocks.RLock()
   247  			parent, exists := in.headers[header.ParentID]
   248  			in.updatingBlocks.RUnlock()
   249  
   250  			if !exists {
   251  				return fmt.Errorf("parent for proposal not found (sender: %x, parent: %x)", in.localID, header.ParentID)
   252  			}
   253  
   254  			// set the height and chain ID
   255  			header.ChainID = parent.ChainID
   256  			header.Height = parent.Height + 1
   257  
   258  			// convert into proposal immediately
   259  			proposal := model.ProposalFromFlow(header, parent.View)
   260  
   261  			// store locally and loop back to engine for processing
   262  			in.ProcessBlock(proposal)
   263  
   264  			return nil
   265  		},
   266  	)
   267  	in.communicator.On("SendVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   268  
   269  	// program the finalizer module behaviour
   270  	in.finalizer.On("MakeFinal", mock.Anything).Return(
   271  		func(blockID flow.Identifier) error {
   272  
   273  			// as we don't use mocks to assert expectations, but only to
   274  			// simulate behaviour, we should drop the call data regularly
   275  			in.updatingBlocks.RLock()
   276  			block, found := in.headers[blockID]
   277  			in.updatingBlocks.RUnlock()
   278  			if !found {
   279  				return fmt.Errorf("can't broadcast with unknown parent")
   280  			}
   281  			if block.Height%100 == 0 {
   282  				in.committee.Calls = nil
   283  				in.builder.Calls = nil
   284  				in.signer.Calls = nil
   285  				in.verifier.Calls = nil
   286  				in.communicator.Calls = nil
   287  				in.finalizer.Calls = nil
   288  			}
   289  
   290  			// check on stop condition
   291  			// TODO: we can remove that once the single instance stop
   292  			// recursively calling into itself
   293  			if in.stop(&in) {
   294  				return errStopCondition
   295  			}
   296  
   297  			return nil
   298  		},
   299  	)
   300  
   301  	in.finalizer.On("MakeValid", mock.Anything).Return(nil)
   302  
   303  	// initialize error handling and logging
   304  	var err error
   305  	zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() }
   306  	// log with node index an ID
   307  	log := unittest.Logger().With().
   308  		Int("index", int(index)).
   309  		Hex("node_id", in.localID[:]).
   310  		Logger()
   311  	notifier := notifications.NewLogConsumer(log)
   312  
   313  	// initialize the pacemaker
   314  	controller := timeout.NewController(cfg.Timeouts)
   315  	in.pacemaker, err = pacemaker.New(DefaultStart(), controller, notifier)
   316  	require.NoError(t, err)
   317  
   318  	// initialize the block producer
   319  	in.producer, err = blockproducer.New(in.signer, in.committee, in.builder)
   320  	require.NoError(t, err)
   321  
   322  	// initialize the finalizer
   323  	rootBlock := model.BlockFromFlow(cfg.Root, 0)
   324  
   325  	signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), in.participants.NodeIDs())
   326  	require.NoError(t, err, "could not encode signer indices")
   327  
   328  	rootQC := &flow.QuorumCertificate{
   329  		View:          rootBlock.View,
   330  		BlockID:       rootBlock.BlockID,
   331  		SignerIndices: signerIndices,
   332  	}
   333  	rootBlockQC := &forks.BlockQC{Block: rootBlock, QC: rootQC}
   334  	forkalizer, err := finalizer.New(rootBlockQC, in.finalizer, notifier)
   335  	require.NoError(t, err)
   336  
   337  	// initialize the forks choice
   338  	choice, err := forkchoice.NewNewestForkChoice(forkalizer, notifier)
   339  	require.NoError(t, err)
   340  
   341  	// initialize the forks handler
   342  	in.forks = forks.New(forkalizer, choice)
   343  
   344  	// initialize the validator
   345  	in.validator = validator.New(in.committee, in.forks, in.verifier)
   346  
   347  	weight := uint64(flow.DefaultInitialWeight)
   348  	stakingSigAggtor := helper.MakeWeightedSignatureAggregator(weight)
   349  	stakingSigAggtor.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe()
   350  
   351  	rbRector := helper.MakeRandomBeaconReconstructor(msig.RandomBeaconThreshold(int(in.participants.Count())))
   352  	rbRector.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe()
   353  
   354  	indices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), []flow.Identifier(in.participants.NodeIDs()))
   355  	require.NoError(t, err)
   356  
   357  	packer := &mocks.Packer{}
   358  	packer.On("Pack", mock.Anything, mock.Anything).Return(indices, unittest.RandomBytes(128), nil).Maybe()
   359  
   360  	onQCCreated := func(qc *flow.QuorumCertificate) {
   361  		in.queue <- qc
   362  	}
   363  
   364  	minRequiredWeight := hotstuff.ComputeWeightThresholdForBuildingQC(uint64(in.participants.Count()) * weight)
   365  	voteProcessorFactory := &mocks.VoteProcessorFactory{}
   366  	voteProcessorFactory.On("Create", mock.Anything, mock.Anything).Return(
   367  		func(log zerolog.Logger, proposal *model.Proposal) hotstuff.VerifyingVoteProcessor {
   368  			return votecollector.NewCombinedVoteProcessor(
   369  				log, proposal.Block,
   370  				stakingSigAggtor, rbRector,
   371  				onQCCreated,
   372  				packer,
   373  				minRequiredWeight,
   374  			)
   375  		}, nil)
   376  
   377  	createCollectorFactoryMethod := votecollector.NewStateMachineFactory(log, notifier, voteProcessorFactory.Create)
   378  	voteCollectors := voteaggregator.NewVoteCollectors(log, DefaultPruned(), workerpool.New(2), createCollectorFactoryMethod)
   379  
   380  	// initialize the vote aggregator
   381  	in.aggregator, err = voteaggregator.NewVoteAggregator(log, notifier, DefaultPruned(), voteCollectors)
   382  	require.NoError(t, err)
   383  
   384  	// initialize the voter
   385  	in.voter = voter.New(in.signer, in.forks, in.persist, in.committee, DefaultVoted())
   386  
   387  	// initialize the event handler
   388  	in.handler, err = eventhandler.NewEventHandler(log, in.pacemaker, in.producer, in.forks, in.persist, in.communicator, in.committee, in.aggregator, in.voter, in.validator, notifier)
   389  	require.NoError(t, err)
   390  
   391  	return &in
   392  }
   393  
   394  func (in *Instance) Run() error {
   395  	ctx, cancel := context.WithCancel(context.Background())
   396  	defer func() {
   397  		cancel()
   398  		<-in.aggregator.Done()
   399  	}()
   400  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
   401  	in.aggregator.Start(signalerCtx)
   402  	<-in.aggregator.Ready()
   403  
   404  	// start the event handler
   405  	err := in.handler.Start()
   406  	if err != nil {
   407  		return fmt.Errorf("could not start event handler: %w", err)
   408  	}
   409  
   410  	// run until an error or stop condition is reached
   411  	for {
   412  
   413  		// check on stop conditions
   414  		if in.stop(in) {
   415  			return errStopCondition
   416  		}
   417  
   418  		// we handle timeouts with priority
   419  		select {
   420  		case <-in.handler.TimeoutChannel():
   421  			err := in.handler.OnLocalTimeout()
   422  			if err != nil {
   423  				return fmt.Errorf("could not process timeout: %w", err)
   424  			}
   425  		default:
   426  		}
   427  
   428  		// check on stop conditions
   429  		if in.stop(in) {
   430  			return errStopCondition
   431  		}
   432  
   433  		// otherwise, process first received event
   434  		select {
   435  		case <-in.handler.TimeoutChannel():
   436  			err := in.handler.OnLocalTimeout()
   437  			if err != nil {
   438  				return fmt.Errorf("could not process timeout: %w", err)
   439  			}
   440  		case msg := <-in.queue:
   441  			switch m := msg.(type) {
   442  			case *model.Proposal:
   443  				err := in.handler.OnReceiveProposal(m)
   444  				if err != nil {
   445  					return fmt.Errorf("could not process proposal: %w", err)
   446  				}
   447  			case *model.Vote:
   448  				in.aggregator.AddVote(m)
   449  			case *flow.QuorumCertificate:
   450  				err := in.handler.OnQCConstructed(m)
   451  				if err != nil {
   452  					return fmt.Errorf("could not process created qc: %w", err)
   453  				}
   454  			}
   455  		}
   456  
   457  	}
   458  }
   459  
   460  func (in *Instance) ProcessBlock(proposal *model.Proposal) {
   461  	in.updatingBlocks.Lock()
   462  	_, parentExists := in.headers[proposal.Block.QC.BlockID]
   463  	defer in.updatingBlocks.Unlock()
   464  
   465  	if parentExists {
   466  		next := proposal
   467  		for next != nil {
   468  			in.headers[next.Block.BlockID] = model.ProposalToFlow(next)
   469  
   470  			in.queue <- next
   471  			// keep processing the pending blocks
   472  			next = in.pendings[next.Block.QC.BlockID]
   473  		}
   474  	} else {
   475  		// cache it in pendings by ParentID
   476  		in.pendings[proposal.Block.QC.BlockID] = proposal
   477  	}
   478  }