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

     1  package votecollector
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/stretchr/testify/suite"
    13  	"go.uber.org/atomic"
    14  	"pgregory.net/rapid"
    15  
    16  	bootstrapDKG "github.com/koko1123/flow-go-1/cmd/bootstrap/dkg"
    17  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
    18  	"github.com/koko1123/flow-go-1/consensus/hotstuff/helper"
    19  	mockhotstuff "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    20  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    21  	"github.com/koko1123/flow-go-1/consensus/hotstuff/signature"
    22  	hsig "github.com/koko1123/flow-go-1/consensus/hotstuff/signature"
    23  	hotstuffvalidator "github.com/koko1123/flow-go-1/consensus/hotstuff/validator"
    24  	"github.com/koko1123/flow-go-1/consensus/hotstuff/verification"
    25  	"github.com/koko1123/flow-go-1/model/encodable"
    26  	"github.com/koko1123/flow-go-1/model/flow"
    27  	"github.com/koko1123/flow-go-1/module/local"
    28  	modulemock "github.com/koko1123/flow-go-1/module/mock"
    29  	msig "github.com/koko1123/flow-go-1/module/signature"
    30  	"github.com/koko1123/flow-go-1/state/protocol/inmem"
    31  	"github.com/koko1123/flow-go-1/state/protocol/seed"
    32  	storagemock "github.com/koko1123/flow-go-1/storage/mock"
    33  	"github.com/koko1123/flow-go-1/utils/unittest"
    34  	"github.com/onflow/flow-go/crypto"
    35  )
    36  
    37  func TestCombinedVoteProcessorV2(t *testing.T) {
    38  	suite.Run(t, new(CombinedVoteProcessorV2TestSuite))
    39  }
    40  
    41  // CombinedVoteProcessorV2TestSuite is a test suite that holds mocked state for isolated testing of CombinedVoteProcessorV2.
    42  type CombinedVoteProcessorV2TestSuite struct {
    43  	VoteProcessorTestSuiteBase
    44  
    45  	rbSharesTotal uint64
    46  
    47  	packer *mockhotstuff.Packer
    48  
    49  	reconstructor *mockhotstuff.RandomBeaconReconstructor
    50  
    51  	minRequiredShares uint64
    52  	processor         *CombinedVoteProcessorV2
    53  }
    54  
    55  func (s *CombinedVoteProcessorV2TestSuite) SetupTest() {
    56  	s.VoteProcessorTestSuiteBase.SetupTest()
    57  
    58  	s.reconstructor = &mockhotstuff.RandomBeaconReconstructor{}
    59  	s.packer = &mockhotstuff.Packer{}
    60  	s.proposal = helper.MakeProposal()
    61  
    62  	s.minRequiredShares = 9 // we require 9 RB shares to reconstruct signature
    63  	s.rbSharesTotal = 0
    64  
    65  	// setup rb reconstructor
    66  	s.reconstructor.On("TrustedAdd", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
    67  		s.rbSharesTotal++
    68  	}).Return(func(signerID flow.Identifier, sig crypto.Signature) bool {
    69  		return s.rbSharesTotal >= s.minRequiredShares
    70  	}, func(signerID flow.Identifier, sig crypto.Signature) error {
    71  		return nil
    72  	}).Maybe()
    73  	s.reconstructor.On("EnoughShares").Return(func() bool {
    74  		return s.rbSharesTotal >= s.minRequiredShares
    75  	}).Maybe()
    76  
    77  	s.processor = &CombinedVoteProcessorV2{
    78  		log:               unittest.Logger(),
    79  		block:             s.proposal.Block,
    80  		stakingSigAggtor:  s.stakingAggregator,
    81  		rbRector:          s.reconstructor,
    82  		onQCCreated:       s.onQCCreated,
    83  		packer:            s.packer,
    84  		minRequiredWeight: s.minRequiredWeight,
    85  		done:              *atomic.NewBool(false),
    86  	}
    87  }
    88  
    89  // TestInitialState tests that Block() and Status() return correct values after calling constructor
    90  func (s *CombinedVoteProcessorV2TestSuite) TestInitialState() {
    91  	require.Equal(s.T(), s.proposal.Block, s.processor.Block())
    92  	require.Equal(s.T(), hotstuff.VoteCollectorStatusVerifying, s.processor.Status())
    93  }
    94  
    95  // TestProcess_VoteNotForProposal tests that CombinedVoteProcessorV2 accepts only votes for the block it was initialized with
    96  // according to interface specification of `VoteProcessor`, we expect dedicated sentinel errors for votes
    97  // for different views (`VoteForIncompatibleViewError`) _or_ block (`VoteForIncompatibleBlockError`).
    98  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_VoteNotForProposal() {
    99  	err := s.processor.Process(unittest.VoteFixture(unittest.WithVoteView(s.proposal.Block.View)))
   100  	require.ErrorAs(s.T(), err, &VoteForIncompatibleBlockError)
   101  	require.False(s.T(), model.IsInvalidVoteError(err))
   102  
   103  	err = s.processor.Process(unittest.VoteFixture(unittest.WithVoteBlockID(s.proposal.Block.BlockID)))
   104  	require.ErrorAs(s.T(), err, &VoteForIncompatibleViewError)
   105  	require.False(s.T(), model.IsInvalidVoteError(err))
   106  
   107  	s.stakingAggregator.AssertNotCalled(s.T(), "Verify")
   108  	s.reconstructor.AssertNotCalled(s.T(), "Verify")
   109  }
   110  
   111  // TestProcess_InvalidSignatureFormat ensures that we process signatures only with valid format.
   112  // If we have received vote with signature in invalid format we should return with sentinel error
   113  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_InvalidSignatureFormat() {
   114  
   115  	// valid length is SigLen or 2*SigLen
   116  	generator := rapid.IntRange(0, 128).Filter(func(value int) bool {
   117  		return value != msig.SigLen && value != 2*msig.SigLen
   118  	})
   119  	rapid.Check(s.T(), func(t *rapid.T) {
   120  		// create a signature with invalid length
   121  		vote := unittest.VoteForBlockFixture(s.proposal.Block, func(vote *model.Vote) {
   122  			vote.SigData = unittest.RandomBytes(generator.Draw(t, "sig-size").(int))
   123  		})
   124  		err := s.processor.Process(vote)
   125  		require.Error(s.T(), err)
   126  		require.True(s.T(), model.IsInvalidVoteError(err), err)
   127  		require.True(s.T(), errors.Is(err, msig.ErrInvalidSignatureFormat), err)
   128  	})
   129  }
   130  
   131  // TestProcess_InvalidSignature tests that CombinedVoteProcessorV2 rejects invalid votes for the following scenarios:
   132  //  1. vote where `SignerID` is not a valid consensus participant;
   133  //     we test correct handling of votes that only a staking signature as well as votes with staking+beacon signatures
   134  //  2. vote from a valid consensus participant
   135  //     case 2a: vote contains only a staking signatures that is invalid
   136  //     case 2b: vote contains _invalid staking sig_, and valid random beacon sig
   137  //     case 2c: vote contains valid staking sig, but _invalid beacon sig_
   138  //
   139  // In all cases, the CombinedVoteProcessor should interpret these failure cases as invalid votes
   140  // and return an InvalidVoteError.
   141  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_InvalidSignature() {
   142  	// Scenario 1) vote where `SignerID` is not a valid consensus participant;
   143  	// sentinel error `InvalidSignerError` from WeightedSignatureAggregator should be wrapped as `InvalidVoteError`
   144  	s.Run("vote with invalid signerID", func() {
   145  		// vote with only a staking signature
   146  		stakingOnlyVote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   147  		s.stakingAggregator.On("Verify", stakingOnlyVote.SignerID, mock.Anything).Return(model.NewInvalidSignerErrorf("")).Once()
   148  		err := s.processor.Process(stakingOnlyVote)
   149  		require.Error(s.T(), err)
   150  		require.True(s.T(), model.IsInvalidVoteError(err))
   151  		require.True(s.T(), model.IsInvalidSignerError(err))
   152  
   153  		// vote with staking+beacon signatures
   154  		doubleSigVote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   155  		s.stakingAggregator.On("Verify", doubleSigVote.SignerID, mock.Anything).Return(model.NewInvalidSignerErrorf("")).Maybe()
   156  		s.reconstructor.On("Verify", doubleSigVote.SignerID, mock.Anything).Return(model.NewInvalidSignerErrorf("")).Maybe()
   157  		err = s.processor.Process(doubleSigVote)
   158  		require.Error(s.T(), err)
   159  		require.True(s.T(), model.IsInvalidVoteError(err))
   160  		require.True(s.T(), model.IsInvalidSignerError(err))
   161  
   162  		s.stakingAggregator.AssertNotCalled(s.T(), "TrustedAdd")
   163  		s.reconstructor.AssertNotCalled(s.T(), "TrustedAdd")
   164  	})
   165  
   166  	// Scenario 2) vote from a valid consensus participant but included sig(s) are cryptographically invalid;
   167  	// sentinel error `ErrInvalidSignature` from WeightedSignatureAggregator should be wrapped as `InvalidVoteError`
   168  	s.Run("vote from valid participant but with invalid signature(s)", func() {
   169  		// case 2a: vote contains only a staking signatures that is invalid
   170  		voteA := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   171  		s.stakingAggregator.On("Verify", voteA.SignerID, mock.Anything).Return(model.ErrInvalidSignature).Once()
   172  		err := s.processor.Process(voteA)
   173  		require.Error(s.T(), err)
   174  		require.True(s.T(), model.IsInvalidVoteError(err))
   175  		require.ErrorAs(s.T(), err, &model.ErrInvalidSignature)
   176  
   177  		// case 2b: vote contains _invalid staking sig_, and valid random beacon sig
   178  		voteB := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   179  		s.stakingAggregator.On("Verify", voteB.SignerID, mock.Anything).Return(model.ErrInvalidSignature).Once()
   180  		s.reconstructor.On("Verify", voteB.SignerID, mock.Anything).Return(nil).Maybe()
   181  		err = s.processor.Process(voteB)
   182  		require.Error(s.T(), err)
   183  		require.True(s.T(), model.IsInvalidVoteError(err))
   184  		require.ErrorAs(s.T(), err, &model.ErrInvalidSignature)
   185  
   186  		// case 2c: vote contains valid staking sig, but _invalid beacon sig_
   187  		voteC := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   188  		s.stakingAggregator.On("Verify", voteC.SignerID, mock.Anything).Return(nil).Maybe()
   189  		s.reconstructor.On("Verify", voteC.SignerID, mock.Anything).Return(model.ErrInvalidSignature).Once()
   190  		err = s.processor.Process(voteC)
   191  		require.Error(s.T(), err)
   192  		require.True(s.T(), model.IsInvalidVoteError(err))
   193  		require.ErrorAs(s.T(), err, &model.ErrInvalidSignature)
   194  
   195  		s.stakingAggregator.AssertNotCalled(s.T(), "TrustedAdd")
   196  		s.reconstructor.AssertNotCalled(s.T(), "TrustedAdd")
   197  	})
   198  }
   199  
   200  // TestProcess_TrustedAdd_Exception tests that unexpected exceptions returned by
   201  // WeightedSignatureAggregator.Verify(..) are _not_ interpreted as invalid votes
   202  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_Verify_Exception() {
   203  	exception := errors.New("unexpected-exception")
   204  
   205  	s.Run("vote with staking-sig only", func() {
   206  		stakingVote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   207  		s.stakingAggregator.On("Verify", stakingVote.SignerID, mock.Anything).Return(exception).Once()
   208  		err := s.processor.Process(stakingVote)
   209  		require.ErrorIs(s.T(), err, exception)
   210  		require.False(s.T(), model.IsInvalidVoteError(err))
   211  	})
   212  
   213  	s.Run("vote with staking+beacon sig", func() {
   214  		// verifying staking sig leads to unexpected exception
   215  		doubleSigVoteA := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   216  		s.stakingAggregator.On("Verify", doubleSigVoteA.SignerID, mock.Anything).Return(exception).Once()
   217  		s.reconstructor.On("Verify", doubleSigVoteA.SignerID, mock.Anything).Return(nil).Maybe()
   218  		err := s.processor.Process(doubleSigVoteA)
   219  		require.ErrorIs(s.T(), err, exception)
   220  		require.False(s.T(), model.IsInvalidVoteError(err))
   221  
   222  		// verifying beacon sig leads to unexpected exception
   223  		doubleSigVoteB := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   224  		s.stakingAggregator.On("Verify", doubleSigVoteB.SignerID, mock.Anything).Return(nil).Maybe()
   225  		s.reconstructor.On("Verify", doubleSigVoteB.SignerID, mock.Anything).Return(exception).Once()
   226  		err = s.processor.Process(doubleSigVoteB)
   227  		require.Error(s.T(), err)
   228  		require.ErrorIs(s.T(), err, exception)
   229  		require.False(s.T(), model.IsInvalidVoteError(err))
   230  	})
   231  }
   232  
   233  // TestProcess_TrustedAdd_Exception tests that unexpected exceptions returned by
   234  // WeightedSignatureAggregator.Verify(..) are propagated, but _not_ interpreted as invalid votes
   235  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_TrustedAdd_Exception() {
   236  	exception := errors.New("unexpected-exception")
   237  	s.Run("staking-sig", func() {
   238  		stakingVote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   239  		*s.stakingAggregator = mockhotstuff.WeightedSignatureAggregator{}
   240  		s.stakingAggregator.On("Verify", stakingVote.SignerID, mock.Anything).Return(nil).Once()
   241  		s.stakingAggregator.On("TrustedAdd", stakingVote.SignerID, mock.Anything).Return(uint64(0), exception).Once()
   242  		err := s.processor.Process(stakingVote)
   243  		require.ErrorIs(s.T(), err, exception)
   244  		require.False(s.T(), model.IsInvalidVoteError(err))
   245  	})
   246  	s.Run("beacon-sig", func() {
   247  		doubleSigVote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   248  		*s.stakingAggregator = mockhotstuff.WeightedSignatureAggregator{}
   249  		*s.reconstructor = mockhotstuff.RandomBeaconReconstructor{}
   250  
   251  		// first we will collect staking sig
   252  		s.stakingAggregator.On("Verify", doubleSigVote.SignerID, mock.Anything).Return(nil).Once()
   253  		s.stakingAggregator.On("TrustedAdd", doubleSigVote.SignerID, mock.Anything).Return(uint64(0), nil).Once()
   254  
   255  		// then beacon sig
   256  		s.reconstructor.On("Verify", doubleSigVote.SignerID, mock.Anything).Return(nil).Once()
   257  		s.reconstructor.On("TrustedAdd", doubleSigVote.SignerID, mock.Anything).Return(false, exception).Once()
   258  
   259  		err := s.processor.Process(doubleSigVote)
   260  		require.ErrorIs(s.T(), err, exception)
   261  		require.False(s.T(), model.IsInvalidVoteError(err))
   262  	})
   263  }
   264  
   265  // TestProcess_BuildQCError tests all error paths during process of building QC.
   266  // Building QC is a one time operation, we need to make sure that failing in one of the steps leads to exception.
   267  // Since it's a one time operation we need a complicated test to test all conditions.
   268  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_BuildQCError() {
   269  	mockAggregator := func(aggregator *mockhotstuff.WeightedSignatureAggregator) {
   270  		aggregator.On("Verify", mock.Anything, mock.Anything).Return(nil)
   271  		aggregator.On("TrustedAdd", mock.Anything, mock.Anything).Return(s.minRequiredWeight, nil)
   272  		aggregator.On("TotalWeight").Return(s.minRequiredWeight)
   273  	}
   274  
   275  	stakingSigAggregator := &mockhotstuff.WeightedSignatureAggregator{}
   276  	reconstructor := &mockhotstuff.RandomBeaconReconstructor{}
   277  	packer := &mockhotstuff.Packer{}
   278  
   279  	identities := unittest.IdentifierListFixture(5)
   280  
   281  	// In this test we will mock all dependencies for happy path, and replace some branches with unhappy path
   282  	// to simulate errors along the branches.
   283  
   284  	mockAggregator(stakingSigAggregator)
   285  	stakingSigAggregator.On("Aggregate").Return(identities, unittest.RandomBytes(128), nil)
   286  
   287  	reconstructor.On("EnoughShares").Return(true)
   288  	reconstructor.On("Reconstruct").Return(unittest.SignatureFixture(), nil)
   289  
   290  	packer.On("Pack", mock.Anything, mock.Anything).Return(unittest.RandomBytes(100), unittest.RandomBytes(128), nil)
   291  
   292  	// Helper factory function to create processors. We need new processor for every test case
   293  	// because QC creation is one time operation and is triggered as soon as we have collected enough weight and shares.
   294  	createProcessor := func(stakingAggregator *mockhotstuff.WeightedSignatureAggregator,
   295  		rbReconstructor *mockhotstuff.RandomBeaconReconstructor,
   296  		packer *mockhotstuff.Packer) *CombinedVoteProcessorV2 {
   297  		return &CombinedVoteProcessorV2{
   298  			log:               unittest.Logger(),
   299  			block:             s.proposal.Block,
   300  			stakingSigAggtor:  stakingAggregator,
   301  			rbRector:          rbReconstructor,
   302  			onQCCreated:       s.onQCCreated,
   303  			packer:            packer,
   304  			minRequiredWeight: s.minRequiredWeight,
   305  			done:              *atomic.NewBool(false),
   306  		}
   307  	}
   308  
   309  	vote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   310  
   311  	// in this test case we aren't able to aggregate staking signature
   312  	s.Run("staking-sig-aggregate", func() {
   313  		exception := errors.New("staking-aggregate-exception")
   314  		stakingSigAggregator := &mockhotstuff.WeightedSignatureAggregator{}
   315  		mockAggregator(stakingSigAggregator)
   316  		stakingSigAggregator.On("Aggregate").Return(nil, nil, exception)
   317  		processor := createProcessor(stakingSigAggregator, reconstructor, packer)
   318  		err := processor.Process(vote)
   319  		require.ErrorIs(s.T(), err, exception)
   320  		require.False(s.T(), model.IsInvalidVoteError(err))
   321  	})
   322  	// in this test case we aren't able to reconstruct signature
   323  	s.Run("reconstruct", func() {
   324  		exception := errors.New("reconstruct-exception")
   325  		reconstructor := &mockhotstuff.RandomBeaconReconstructor{}
   326  		reconstructor.On("EnoughShares").Return(true)
   327  		reconstructor.On("Reconstruct").Return(nil, exception)
   328  		processor := createProcessor(stakingSigAggregator, reconstructor, packer)
   329  		err := processor.Process(vote)
   330  		require.ErrorIs(s.T(), err, exception)
   331  		require.False(s.T(), model.IsInvalidVoteError(err))
   332  	})
   333  	// in this test case we aren't able to pack signatures
   334  	s.Run("pack", func() {
   335  		exception := errors.New("pack-qc-exception")
   336  		packer := &mockhotstuff.Packer{}
   337  		packer.On("Pack", mock.Anything, mock.Anything).Return(nil, nil, exception)
   338  		processor := createProcessor(stakingSigAggregator, reconstructor, packer)
   339  		err := processor.Process(vote)
   340  		require.ErrorIs(s.T(), err, exception)
   341  		require.False(s.T(), model.IsInvalidVoteError(err))
   342  	})
   343  }
   344  
   345  // TestProcess_EnoughWeightNotEnoughShares tests a scenario where we first don't have enough weight,
   346  // then we iteratively increase it to the point where we have enough staking weight. No QC should be created
   347  // in this scenario since there is not enough random beacon shares.
   348  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_EnoughWeightNotEnoughShares() {
   349  	for i := uint64(0); i < s.minRequiredWeight; i += s.sigWeight {
   350  		vote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   351  		s.stakingAggregator.On("Verify", vote.SignerID, mock.Anything).Return(nil)
   352  		err := s.processor.Process(vote)
   353  		require.NoError(s.T(), err)
   354  	}
   355  
   356  	require.False(s.T(), s.processor.done.Load())
   357  	s.reconstructor.AssertCalled(s.T(), "EnoughShares")
   358  	s.onQCCreatedState.AssertNotCalled(s.T(), "onQCCreated")
   359  }
   360  
   361  // TestProcess_EnoughSharesNotEnoughWeight tests a scenario where we are collecting votes with staking and beacon sigs
   362  // to the point where we have enough shares to reconstruct RB signature. No QC should be created
   363  // in this scenario since there is not enough staking weight.
   364  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_EnoughSharesNotEnoughWeight() {
   365  	// change sig weight to be really low, so we don't reach min staking weight while collecting
   366  	// beacon signatures
   367  	s.sigWeight = 10
   368  	for i := uint64(0); i < s.minRequiredShares; i++ {
   369  		vote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithDoubleSig())
   370  		s.stakingAggregator.On("Verify", vote.SignerID, mock.Anything).Return(nil).Once()
   371  		s.reconstructor.On("Verify", vote.SignerID, mock.Anything).Return(nil).Once()
   372  		err := s.processor.Process(vote)
   373  		require.NoError(s.T(), err)
   374  	}
   375  
   376  	require.False(s.T(), s.processor.done.Load())
   377  	s.reconstructor.AssertNotCalled(s.T(), "EnoughShares")
   378  	s.onQCCreatedState.AssertNotCalled(s.T(), "onQCCreated")
   379  	// verify if we indeed have enough shares
   380  	require.True(s.T(), s.reconstructor.EnoughShares())
   381  }
   382  
   383  // TestProcess_ConcurrentCreatingQC tests a scenario where multiple goroutines process vote at same time,
   384  // we expect only one QC created in this scenario.
   385  func (s *CombinedVoteProcessorV2TestSuite) TestProcess_ConcurrentCreatingQC() {
   386  	stakingSigners := unittest.IdentifierListFixture(10)
   387  	mockAggregator := func(aggregator *mockhotstuff.WeightedSignatureAggregator) {
   388  		aggregator.On("Verify", mock.Anything, mock.Anything).Return(nil)
   389  		aggregator.On("TrustedAdd", mock.Anything, mock.Anything).Return(s.minRequiredWeight, nil)
   390  		aggregator.On("TotalWeight").Return(s.minRequiredWeight)
   391  		aggregator.On("Aggregate").Return(stakingSigners, unittest.RandomBytes(128), nil)
   392  	}
   393  
   394  	// mock aggregators, so we have enough weight and shares for creating QC
   395  	*s.stakingAggregator = mockhotstuff.WeightedSignatureAggregator{}
   396  	mockAggregator(s.stakingAggregator)
   397  	*s.reconstructor = mockhotstuff.RandomBeaconReconstructor{}
   398  	s.reconstructor.On("Verify", mock.Anything, mock.Anything).Return(nil)
   399  	s.reconstructor.On("Reconstruct").Return(unittest.SignatureFixture(), nil)
   400  	s.reconstructor.On("EnoughShares").Return(true)
   401  
   402  	// at this point sending any vote should result in creating QC.
   403  	s.packer.On("Pack", s.proposal.Block.BlockID, mock.Anything).Return(unittest.RandomBytes(100), unittest.RandomBytes(128), nil)
   404  	s.onQCCreatedState.On("onQCCreated", mock.Anything).Return(nil).Once()
   405  
   406  	var startupWg, shutdownWg sync.WaitGroup
   407  
   408  	vote := unittest.VoteForBlockFixture(s.proposal.Block, VoteWithStakingSig())
   409  	startupWg.Add(1)
   410  	// prepare goroutines, so they are ready to submit a vote at roughly same time
   411  	for i := 0; i < 5; i++ {
   412  		shutdownWg.Add(1)
   413  		go func() {
   414  			defer shutdownWg.Done()
   415  			startupWg.Wait()
   416  			err := s.processor.Process(vote)
   417  			require.NoError(s.T(), err)
   418  		}()
   419  	}
   420  
   421  	startupWg.Done()
   422  
   423  	// wait for all routines to finish
   424  	shutdownWg.Wait()
   425  
   426  	s.onQCCreatedState.AssertNumberOfCalls(s.T(), "onQCCreated", 1)
   427  }
   428  
   429  // TestCombinedVoteProcessorV2_PropertyCreatingQCCorrectness uses property testing to test correctness of concurrent votes processing.
   430  // We randomly draw a committee with some number of staking, random beacon and byzantine nodes.
   431  // Values are drawn in a way that 1 <= honestParticipants <= participants <= maxParticipants
   432  // In each test iteration we expect to create a valid QC with all provided data as part of constructed QC.
   433  func TestCombinedVoteProcessorV2_PropertyCreatingQCCorrectness(testifyT *testing.T) {
   434  	maxParticipants := uint64(53)
   435  
   436  	rapid.Check(testifyT, func(t *rapid.T) {
   437  		// draw participants in range 1 <= participants <= maxParticipants
   438  		participants := rapid.Uint64Range(1, maxParticipants).Draw(t, "participants").(uint64)
   439  		beaconSignersCount := rapid.Uint64Range(participants/2+1, participants).Draw(t, "beaconSigners").(uint64)
   440  		stakingSignersCount := participants - beaconSignersCount
   441  		require.Equal(t, participants, stakingSignersCount+beaconSignersCount)
   442  
   443  		// setup how many votes we need to create a QC
   444  		// 1 <= honestParticipants <= participants <= maxParticipants
   445  		honestParticipants := participants*2/3 + 1
   446  		sigWeight := uint64(100)
   447  		minRequiredWeight := honestParticipants * sigWeight
   448  
   449  		// proposing block
   450  		block := helper.MakeBlock()
   451  
   452  		t.Logf("running conf\n\t"+
   453  			"staking signers: %v, beacon signers: %v\n\t"+
   454  			"required weight: %v", stakingSignersCount, beaconSignersCount, minRequiredWeight)
   455  
   456  		stakingTotalWeight, collectedShares := *atomic.NewUint64(0), *atomic.NewUint64(0)
   457  
   458  		// setup aggregators and reconstructor
   459  		stakingAggregator := &mockhotstuff.WeightedSignatureAggregator{}
   460  		reconstructor := &mockhotstuff.RandomBeaconReconstructor{}
   461  
   462  		stakingSigners := unittest.IdentifierListFixture(int(stakingSignersCount))
   463  		beaconSigners := unittest.IdentifierListFixture(int(beaconSignersCount))
   464  
   465  		// lists to track signers that actually contributed their signatures
   466  		var (
   467  			aggregatedStakingSigners flow.IdentifierList
   468  		)
   469  
   470  		// need separate locks to safely update vectors of voted signers
   471  		stakingAggregatorLock := &sync.Mutex{}
   472  
   473  		stakingAggregator.On("TotalWeight").Return(func() uint64 {
   474  			return stakingTotalWeight.Load()
   475  		})
   476  		reconstructor.On("EnoughShares").Return(func() bool {
   477  			return collectedShares.Load() >= beaconSignersCount
   478  		})
   479  
   480  		// mock expected calls to aggregators and reconstructor
   481  		combinedSigs := unittest.SignaturesFixture(2)
   482  		stakingAggregator.On("Aggregate").Return(
   483  			func() flow.IdentifierList {
   484  				stakingAggregatorLock.Lock()
   485  				defer stakingAggregatorLock.Unlock()
   486  				return aggregatedStakingSigners
   487  			},
   488  			func() []byte { return combinedSigs[0] },
   489  			func() error { return nil }).Once()
   490  		reconstructor.On("Reconstruct").Return(combinedSigs[1], nil).Once()
   491  
   492  		// mock expected call to Packer
   493  		mergedSignerIDs := make(flow.IdentifierList, 0)
   494  		packedSigData := unittest.RandomBytes(128)
   495  		pcker := &mockhotstuff.Packer{}
   496  		pcker.On("Pack", block.BlockID, mock.Anything).Run(func(args mock.Arguments) {
   497  			blockSigData := args.Get(1).(*hotstuff.BlockSignatureData)
   498  
   499  			// check that aggregated signers are part of all votes signers
   500  			// due to concurrent processing it is possible that Aggregate will return less that we have actually aggregated
   501  			// but still enough to construct the QC
   502  			stakingAggregatorLock.Lock()
   503  			require.Subset(t, aggregatedStakingSigners, blockSigData.StakingSigners)
   504  			stakingAggregatorLock.Unlock()
   505  			require.Nil(t, blockSigData.RandomBeaconSigners)
   506  			require.Nil(t, blockSigData.AggregatedRandomBeaconSig)
   507  			require.GreaterOrEqual(t, uint64(len(blockSigData.StakingSigners)),
   508  				honestParticipants)
   509  
   510  			expectedBlockSigData := &hotstuff.BlockSignatureData{
   511  				StakingSigners:               blockSigData.StakingSigners,
   512  				RandomBeaconSigners:          nil,
   513  				AggregatedStakingSig:         []byte(combinedSigs[0]),
   514  				AggregatedRandomBeaconSig:    nil,
   515  				ReconstructedRandomBeaconSig: combinedSigs[1],
   516  			}
   517  
   518  			require.Equal(t, expectedBlockSigData, blockSigData)
   519  
   520  			// fill merged signers with collected signers
   521  			mergedSignerIDs = append(expectedBlockSigData.StakingSigners, expectedBlockSigData.RandomBeaconSigners...)
   522  		}).Return(
   523  			func(flow.Identifier, *hotstuff.BlockSignatureData) []byte {
   524  				signerIndices, _ := msig.EncodeSignersToIndices(mergedSignerIDs, mergedSignerIDs)
   525  				return signerIndices
   526  			},
   527  			func(flow.Identifier, *hotstuff.BlockSignatureData) []byte { return packedSigData },
   528  			func(flow.Identifier, *hotstuff.BlockSignatureData) error { return nil }).Once()
   529  
   530  		// track if QC was created
   531  		qcCreated := atomic.NewBool(false)
   532  
   533  		// expected QC
   534  		onQCCreated := func(qc *flow.QuorumCertificate) {
   535  			// QC should be created only once
   536  			if !qcCreated.CompareAndSwap(false, true) {
   537  				t.Fatalf("QC created more than once")
   538  			}
   539  
   540  			signerIndices, err := msig.EncodeSignersToIndices(mergedSignerIDs, mergedSignerIDs)
   541  			require.NoError(t, err)
   542  
   543  			// ensure that QC contains correct field
   544  			expectedQC := &flow.QuorumCertificate{
   545  				View:          block.View,
   546  				BlockID:       block.BlockID,
   547  				SignerIndices: signerIndices,
   548  				SigData:       packedSigData,
   549  			}
   550  			require.Equalf(t, expectedQC, qc, "QC should be equal to what we expect")
   551  		}
   552  
   553  		processor := &CombinedVoteProcessorV2{
   554  			log:               unittest.Logger(),
   555  			block:             block,
   556  			stakingSigAggtor:  stakingAggregator,
   557  			rbRector:          reconstructor,
   558  			onQCCreated:       onQCCreated,
   559  			packer:            pcker,
   560  			minRequiredWeight: minRequiredWeight,
   561  			done:              *atomic.NewBool(false),
   562  		}
   563  
   564  		votes := make([]*model.Vote, 0, stakingSignersCount+beaconSignersCount)
   565  
   566  		expectStakingAggregatorCalls := func(vote *model.Vote) {
   567  			expectedSig := crypto.Signature(vote.SigData[:msig.SigLen])
   568  			stakingAggregator.On("Verify", vote.SignerID, expectedSig).Return(nil).Maybe()
   569  			stakingAggregator.On("TrustedAdd", vote.SignerID, expectedSig).Run(func(args mock.Arguments) {
   570  				signerID := args.Get(0).(flow.Identifier)
   571  				stakingAggregatorLock.Lock()
   572  				defer stakingAggregatorLock.Unlock()
   573  				stakingTotalWeight.Add(sigWeight)
   574  				aggregatedStakingSigners = append(aggregatedStakingSigners, signerID)
   575  			}).Return(uint64(0), nil).Maybe()
   576  		}
   577  
   578  		// prepare votes
   579  		for _, signer := range stakingSigners {
   580  			vote := unittest.VoteForBlockFixture(processor.Block(), VoteWithStakingSig())
   581  			vote.SignerID = signer
   582  			// this will set up mock
   583  			expectStakingAggregatorCalls(vote)
   584  			votes = append(votes, vote)
   585  		}
   586  		for _, signer := range beaconSigners {
   587  			vote := unittest.VoteForBlockFixture(processor.Block(), VoteWithDoubleSig())
   588  			vote.SignerID = signer
   589  			expectStakingAggregatorCalls(vote)
   590  			expectedSig := crypto.Signature(vote.SigData[msig.SigLen:])
   591  			reconstructor.On("Verify", vote.SignerID, expectedSig).Return(nil).Maybe()
   592  			reconstructor.On("TrustedAdd", vote.SignerID, expectedSig).Run(func(args mock.Arguments) {
   593  				collectedShares.Inc()
   594  			}).Return(true, nil).Maybe()
   595  			votes = append(votes, vote)
   596  		}
   597  
   598  		// shuffle votes in random order
   599  		rand.Seed(time.Now().UnixNano())
   600  		rand.Shuffle(len(votes), func(i, j int) {
   601  			votes[i], votes[j] = votes[j], votes[i]
   602  		})
   603  
   604  		var startProcessing, finishProcessing sync.WaitGroup
   605  		startProcessing.Add(1)
   606  		// process votes concurrently by multiple workers
   607  		for _, vote := range votes {
   608  			finishProcessing.Add(1)
   609  			go func(vote *model.Vote) {
   610  				defer finishProcessing.Done()
   611  				startProcessing.Wait()
   612  				err := processor.Process(vote)
   613  				require.NoError(t, err)
   614  			}(vote)
   615  		}
   616  
   617  		// start all goroutines at the same time
   618  		startProcessing.Done()
   619  		finishProcessing.Wait()
   620  
   621  		passed := processor.done.Load()
   622  		passed = passed && qcCreated.Load()
   623  		passed = passed && stakingAggregator.AssertExpectations(t)
   624  		passed = passed && reconstructor.AssertExpectations(t)
   625  
   626  		if !passed {
   627  			t.Fatalf("Assertions weren't met, staking weight: %v, shares collected: %v", stakingTotalWeight, collectedShares.Load())
   628  		}
   629  
   630  		//processing extra votes shouldn't result in creating new QCs
   631  		vote := unittest.VoteForBlockFixture(block, VoteWithDoubleSig())
   632  		err := processor.Process(vote)
   633  		require.NoError(t, err)
   634  	})
   635  }
   636  
   637  // TestCombinedVoteProcessorV2_PropertyCreatingQCLiveness uses property testing to test liveness of concurrent votes processing.
   638  // We randomly draw a committee and check if we are able to create a QC with minimal number of nodes.
   639  // In each test iteration we expect to create a QC, we don't check correctness of data since it's checked by another test.
   640  func TestCombinedVoteProcessorV2_PropertyCreatingQCLiveness(testifyT *testing.T) {
   641  	rapid.Check(testifyT, func(t *rapid.T) {
   642  		// draw beacon signers in range 1 <= beaconSignersCount <= 53
   643  		beaconSignersCount := rapid.Uint64Range(1, 53).Draw(t, "beaconSigners").(uint64)
   644  		// draw staking signers in range 0 <= stakingSignersCount <= 10
   645  		stakingSignersCount := rapid.Uint64Range(0, 10).Draw(t, "stakingSigners").(uint64)
   646  
   647  		stakingWeightRange, beaconWeightRange := rapid.Uint64Range(1, 10), rapid.Uint64Range(1, 10)
   648  
   649  		minRequiredWeight := uint64(0)
   650  		// draw weight for each signer randomly
   651  		stakingSigners := unittest.IdentityListFixture(int(stakingSignersCount), func(identity *flow.Identity) {
   652  			identity.Weight = stakingWeightRange.Draw(t, identity.String()).(uint64)
   653  			minRequiredWeight += identity.Weight
   654  		})
   655  		beaconSigners := unittest.IdentityListFixture(int(beaconSignersCount), func(identity *flow.Identity) {
   656  			identity.Weight = beaconWeightRange.Draw(t, identity.String()).(uint64)
   657  			minRequiredWeight += identity.Weight
   658  		})
   659  
   660  		// proposing block
   661  		block := helper.MakeBlock()
   662  
   663  		t.Logf("running conf\n\t"+
   664  			"staking signers: %v, beacon signers: %v\n\t"+
   665  			"required weight: %v", stakingSignersCount, beaconSignersCount, minRequiredWeight)
   666  
   667  		stakingTotalWeight, collectedShares := atomic.NewUint64(0), atomic.NewUint64(0)
   668  
   669  		// setup aggregators and reconstructor
   670  		stakingAggregator := &mockhotstuff.WeightedSignatureAggregator{}
   671  		reconstructor := &mockhotstuff.RandomBeaconReconstructor{}
   672  
   673  		stakingAggregator.On("TotalWeight").Return(func() uint64 {
   674  			return stakingTotalWeight.Load()
   675  		})
   676  		// don't require shares
   677  		reconstructor.On("EnoughShares").Return(func() bool {
   678  			return collectedShares.Load() >= beaconSignersCount
   679  		})
   680  
   681  		// mock expected calls to aggregator and reconstructor
   682  		combinedSigs := unittest.SignaturesFixture(2)
   683  		stakingAggregator.On("Aggregate").Return(stakingSigners.NodeIDs(), []byte(combinedSigs[0]), nil).Once()
   684  		reconstructor.On("Reconstruct").Return(combinedSigs[1], nil).Once()
   685  
   686  		// mock expected call to Packer
   687  		mergedSignerIDs := append(stakingSigners.NodeIDs(), beaconSigners.NodeIDs()...)
   688  		packedSigData := unittest.RandomBytes(128)
   689  		pcker := &mockhotstuff.Packer{}
   690  
   691  		signerIndices, err := msig.EncodeSignersToIndices(mergedSignerIDs, mergedSignerIDs)
   692  		require.NoError(t, err)
   693  		pcker.On("Pack", block.BlockID, mock.Anything).Return(signerIndices, packedSigData, nil)
   694  
   695  		// track if QC was created
   696  		qcCreated := atomic.NewBool(false)
   697  
   698  		// expected QC
   699  		onQCCreated := func(qc *flow.QuorumCertificate) {
   700  			// QC should be created only once
   701  			if !qcCreated.CompareAndSwap(false, true) {
   702  				t.Fatalf("QC created more than once")
   703  			}
   704  		}
   705  
   706  		processor := &CombinedVoteProcessorV2{
   707  			log:               unittest.Logger(),
   708  			block:             block,
   709  			stakingSigAggtor:  stakingAggregator,
   710  			rbRector:          reconstructor,
   711  			onQCCreated:       onQCCreated,
   712  			packer:            pcker,
   713  			minRequiredWeight: minRequiredWeight,
   714  			done:              *atomic.NewBool(false),
   715  		}
   716  
   717  		votes := make([]*model.Vote, 0, stakingSignersCount+beaconSignersCount)
   718  
   719  		expectStakingAggregatorCalls := func(vote *model.Vote, weight uint64) {
   720  			expectedSig := crypto.Signature(vote.SigData[:msig.SigLen])
   721  			stakingAggregator.On("Verify", vote.SignerID, expectedSig).Return(nil).Maybe()
   722  			stakingAggregator.On("TrustedAdd", vote.SignerID, expectedSig).Run(func(args mock.Arguments) {
   723  				stakingTotalWeight.Add(weight)
   724  			}).Return(uint64(0), nil).Maybe()
   725  		}
   726  
   727  		// prepare votes
   728  		for _, signer := range stakingSigners {
   729  			vote := unittest.VoteForBlockFixture(processor.Block(), VoteWithStakingSig())
   730  			vote.SignerID = signer.ID()
   731  			expectStakingAggregatorCalls(vote, signer.Weight)
   732  			votes = append(votes, vote)
   733  		}
   734  		for _, signer := range beaconSigners {
   735  			vote := unittest.VoteForBlockFixture(processor.Block(), VoteWithDoubleSig())
   736  			vote.SignerID = signer.ID()
   737  			expectStakingAggregatorCalls(vote, signer.Weight)
   738  			expectedSig := crypto.Signature(vote.SigData[msig.SigLen:])
   739  			reconstructor.On("Verify", vote.SignerID, expectedSig).Return(nil).Maybe()
   740  			reconstructor.On("TrustedAdd", vote.SignerID, expectedSig).Run(func(args mock.Arguments) {
   741  				collectedShares.Inc()
   742  			}).Return(true, nil).Maybe()
   743  			votes = append(votes, vote)
   744  		}
   745  
   746  		// shuffle votes in random order
   747  		rand.Seed(time.Now().UnixNano())
   748  		rand.Shuffle(len(votes), func(i, j int) {
   749  			votes[i], votes[j] = votes[j], votes[i]
   750  		})
   751  
   752  		var startProcessing, finishProcessing sync.WaitGroup
   753  		startProcessing.Add(1)
   754  		// process votes concurrently by multiple workers
   755  		for _, vote := range votes {
   756  			finishProcessing.Add(1)
   757  			go func(vote *model.Vote) {
   758  				defer finishProcessing.Done()
   759  				startProcessing.Wait()
   760  				err := processor.Process(vote)
   761  				require.NoError(t, err)
   762  			}(vote)
   763  		}
   764  
   765  		// start all goroutines at the same time
   766  		startProcessing.Done()
   767  		finishProcessing.Wait()
   768  
   769  		passed := processor.done.Load()
   770  		passed = passed && qcCreated.Load()
   771  		passed = passed && stakingAggregator.AssertExpectations(t)
   772  		passed = passed && reconstructor.AssertExpectations(t)
   773  
   774  		if !passed {
   775  			t.Fatalf("Assertions weren't met, staking weight: %v, collected shares: %v", stakingTotalWeight.Load(), collectedShares.Load())
   776  		}
   777  	})
   778  }
   779  
   780  // TestCombinedVoteProcessorV2_BuildVerifyQC tests a complete path from creating votes to collecting votes and then
   781  // building & verifying QC.
   782  // We start with leader proposing a block, then new leader collects votes and builds a QC.
   783  // Need to verify that QC that was produced is valid and can be embedded in new proposal.
   784  func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) {
   785  	epochCounter := uint64(3)
   786  	epochLookup := &modulemock.EpochLookup{}
   787  	view := uint64(20)
   788  	epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil)
   789  
   790  	// all committee members run DKG
   791  	dkgData, err := bootstrapDKG.RunFastKG(11, unittest.RandomBytes(32))
   792  	require.NoError(t, err)
   793  
   794  	// signers hold objects that are created with private key and can sign votes and proposals
   795  	signers := make(map[flow.Identifier]*verification.CombinedSigner)
   796  
   797  	// prepare staking signers, each signer has it's own private/public key pair
   798  	// stakingSigners sign only with staking key, meaning they have failed DKG
   799  	stakingSigners := unittest.IdentityListFixture(3)
   800  	beaconSigners := unittest.IdentityListFixture(8)
   801  	allIdentities := append(stakingSigners, beaconSigners...)
   802  	require.Equal(t, len(dkgData.PubKeyShares), len(allIdentities))
   803  	dkgParticipants := make(map[flow.Identifier]flow.DKGParticipant)
   804  	// fill dkg participants data
   805  	for index, identity := range allIdentities {
   806  		dkgParticipants[identity.NodeID] = flow.DKGParticipant{
   807  			Index:    uint(index),
   808  			KeyShare: dkgData.PubKeyShares[index],
   809  		}
   810  	}
   811  
   812  	for _, identity := range stakingSigners {
   813  		stakingPriv := unittest.StakingPrivKeyFixture()
   814  		identity.StakingPubKey = stakingPriv.PublicKey()
   815  
   816  		keys := &storagemock.SafeBeaconKeys{}
   817  		// there is no DKG key for this epoch
   818  		keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil)
   819  
   820  		beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
   821  
   822  		me, err := local.New(identity, stakingPriv)
   823  		require.NoError(t, err)
   824  
   825  		signers[identity.NodeID] = verification.NewCombinedSigner(me, beaconSignerStore)
   826  	}
   827  
   828  	for _, identity := range beaconSigners {
   829  		stakingPriv := unittest.StakingPrivKeyFixture()
   830  		identity.StakingPubKey = stakingPriv.PublicKey()
   831  
   832  		participantData := dkgParticipants[identity.NodeID]
   833  
   834  		dkgKey := encodable.RandomBeaconPrivKey{
   835  			PrivateKey: dkgData.PrivKeyShares[participantData.Index],
   836  		}
   837  
   838  		keys := &storagemock.SafeBeaconKeys{}
   839  		// there is DKG key for this epoch
   840  		keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil)
   841  
   842  		beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
   843  
   844  		me, err := local.New(identity, stakingPriv)
   845  		require.NoError(t, err)
   846  
   847  		signers[identity.NodeID] = verification.NewCombinedSigner(me, beaconSignerStore)
   848  	}
   849  
   850  	leader := stakingSigners[0]
   851  
   852  	block := helper.MakeBlock(helper.WithBlockView(view),
   853  		helper.WithBlockProposer(leader.NodeID))
   854  
   855  	inmemDKG, err := inmem.DKGFromEncodable(inmem.EncodableDKG{
   856  		GroupKey: encodable.RandomBeaconPubKey{
   857  			PublicKey: dkgData.PubGroupKey,
   858  		},
   859  		Participants: dkgParticipants,
   860  	})
   861  	require.NoError(t, err)
   862  
   863  	committee := &mockhotstuff.Committee{}
   864  	committee.On("Identities", block.BlockID, mock.Anything).Return(allIdentities, nil)
   865  	committee.On("DKG", block.BlockID).Return(inmemDKG, nil)
   866  
   867  	votes := make([]*model.Vote, 0, len(allIdentities))
   868  
   869  	// first staking signer will be leader collecting votes for proposal
   870  	// prepare votes for every member of committee except leader
   871  	for _, signer := range allIdentities[1:] {
   872  		vote, err := signers[signer.NodeID].CreateVote(block)
   873  		require.NoError(t, err)
   874  		votes = append(votes, vote)
   875  	}
   876  
   877  	// create and sign proposal
   878  	proposal, err := signers[leader.NodeID].CreateProposal(block)
   879  	require.NoError(t, err)
   880  
   881  	qcCreated := false
   882  	onQCCreated := func(qc *flow.QuorumCertificate) {
   883  		packer := hsig.NewConsensusSigDataPacker(committee)
   884  
   885  		// create verifier that will do crypto checks of created QC
   886  		verifier := verification.NewCombinedVerifier(committee, packer)
   887  		forks := &mockhotstuff.Forks{}
   888  		// create validator which will do compliance and crypto checked of created QC
   889  		validator := hotstuffvalidator.New(committee, forks, verifier)
   890  		// check if QC is valid against parent
   891  		err := validator.ValidateQC(qc, block)
   892  		require.NoError(t, err)
   893  
   894  		qcCreated = true
   895  	}
   896  
   897  	voteProcessorFactory := NewCombinedVoteProcessorFactory(committee, onQCCreated)
   898  	voteProcessor, err := voteProcessorFactory.Create(unittest.Logger(), proposal)
   899  	require.NoError(t, err)
   900  
   901  	// process votes by new leader, this will result in producing new QC
   902  	for _, vote := range votes {
   903  		err := voteProcessor.Process(vote)
   904  		require.NoError(t, err)
   905  	}
   906  
   907  	require.True(t, qcCreated)
   908  }
   909  
   910  func VoteWithStakingSig() func(*model.Vote) {
   911  	return func(vote *model.Vote) {
   912  		vote.SigData = unittest.RandomBytes(msig.SigLen)
   913  	}
   914  }
   915  
   916  func VoteWithDoubleSig() func(*model.Vote) {
   917  	return func(vote *model.Vote) {
   918  		vote.SigData = unittest.RandomBytes(96)
   919  	}
   920  }
   921  
   922  // TestReadRandomSourceFromPackedQCV2 tests that a constructed QC can be unpacked and from which the random source can be
   923  // retrieved
   924  func TestReadRandomSourceFromPackedQCV2(t *testing.T) {
   925  
   926  	// for V2 there is no aggregated random beacon sig or random beacon signers
   927  	allSigners := unittest.IdentityListFixture(5)
   928  	// staking signers are only subset of all signers
   929  	stakingSigners := allSigners.NodeIDs()[:3]
   930  
   931  	aggregatedStakingSig := unittest.SignatureFixture()
   932  	reconstructedBeaconSig := unittest.SignatureFixture()
   933  
   934  	blockSigData := buildBlockSignatureDataForV2(stakingSigners, aggregatedStakingSig, reconstructedBeaconSig)
   935  
   936  	// making a mock block
   937  	header := unittest.BlockHeaderFixture()
   938  	block := model.BlockFromFlow(header, header.View-1)
   939  
   940  	// create a packer
   941  	committee := &mockhotstuff.Committee{}
   942  	committee.On("Identities", block.BlockID, mock.Anything).Return(allSigners, nil)
   943  	packer := signature.NewConsensusSigDataPacker(committee)
   944  
   945  	qc, err := buildQCWithPackerAndSigData(packer, block, blockSigData)
   946  	require.NoError(t, err)
   947  
   948  	randomSource, err := seed.FromParentQCSignature(qc.SigData)
   949  	require.NoError(t, err)
   950  
   951  	randomSourceAgain, err := seed.FromParentQCSignature(qc.SigData)
   952  	require.NoError(t, err)
   953  
   954  	// verify the random source is deterministic
   955  	require.Equal(t, randomSource, randomSourceAgain)
   956  }