github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/approvals/approval_collector_test.go (about) 1 package approvals 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/mock" 7 "github.com/stretchr/testify/require" 8 "github.com/stretchr/testify/suite" 9 10 "github.com/onflow/flow-go/engine" 11 "github.com/onflow/flow-go/model/flow" 12 mempool "github.com/onflow/flow-go/module/mempool/mock" 13 "github.com/onflow/flow-go/utils/unittest" 14 ) 15 16 // TestApprovalCollector performs isolated testing of ApprovalCollector 17 // ApprovalCollector is responsible for delegating approval processing to ChunkApprovalCollector 18 // ApprovalCollector stores aggregated signatures for every chunk, once there is a signature for each chunk it is responsible 19 // for creating IncorporatedResultSeal and submitting it to the mempool. 20 // ApprovalCollector should reject approvals with invalid chunk index. 21 func TestApprovalCollector(t *testing.T) { 22 suite.Run(t, new(ApprovalCollectorTestSuite)) 23 } 24 25 type ApprovalCollectorTestSuite struct { 26 BaseApprovalsTestSuite 27 28 sealsPL *mempool.IncorporatedResultSeals 29 collector *ApprovalCollector 30 } 31 32 func (s *ApprovalCollectorTestSuite) SetupTest() { 33 s.BaseApprovalsTestSuite.SetupTest() 34 s.sealsPL = &mempool.IncorporatedResultSeals{} 35 36 var err error 37 s.collector, err = NewApprovalCollector(unittest.Logger(), s.IncorporatedResult, s.IncorporatedBlock, s.Block, s.ChunksAssignment, s.sealsPL, uint(len(s.AuthorizedVerifiers))) 38 require.NoError(s.T(), err) 39 } 40 41 // TestProcessApproval_ValidApproval tests that valid approval is processed without error 42 func (s *ApprovalCollectorTestSuite) TestProcessApproval_ValidApproval() { 43 approval := unittest.ResultApprovalFixture(unittest.WithChunk(s.Chunks[0].Index), unittest.WithApproverID(s.VerID)) 44 err := s.collector.ProcessApproval(approval) 45 require.NoError(s.T(), err) 46 } 47 48 // TestProcessApproval_SealResult tests that after collecting enough approvals for every chunk ApprovalCollector will 49 // generate a seal and put it into seals mempool. This logic should be event driven and happen as soon as required threshold is 50 // met for each chunk. 51 func (s *ApprovalCollectorTestSuite) TestProcessApproval_SealResult() { 52 expectedSignatures := make([]flow.AggregatedSignature, s.IncorporatedResult.Result.Chunks.Len()) 53 s.sealsPL.On("Add", mock.Anything).Run( 54 func(args mock.Arguments) { 55 seal := args.Get(0).(*flow.IncorporatedResultSeal) 56 require.Equal(s.T(), s.Block.ID(), seal.Seal.BlockID) 57 require.Equal(s.T(), s.IncorporatedResult.Result.ID(), seal.Seal.ResultID) 58 require.Equal(s.T(), s.IncorporatedResult.Result.BlockID, seal.Seal.BlockID) 59 require.Equal(s.T(), seal.Seal.BlockID, seal.Header.ID()) 60 }, 61 ).Return(true, nil).Once() 62 63 for i, chunk := range s.Chunks { 64 var err error 65 sigCollector := NewSignatureCollector() 66 for verID := range s.AuthorizedVerifiers { 67 approval := unittest.ResultApprovalFixture(unittest.WithChunk(chunk.Index), unittest.WithApproverID(verID)) 68 err = s.collector.ProcessApproval(approval) 69 require.NoError(s.T(), err) 70 sigCollector.Add(approval.Body.ApproverID, approval.Body.AttestationSignature) 71 } 72 expectedSignatures[i] = sigCollector.ToAggregatedSignature() 73 } 74 75 s.sealsPL.AssertExpectations(s.T()) 76 } 77 78 // TestProcessApproval_InvalidChunk tests that approval with invalid chunk index will be rejected without 79 // processing. 80 func (s *ApprovalCollectorTestSuite) TestProcessApproval_InvalidChunk() { 81 approval := unittest.ResultApprovalFixture(unittest.WithChunk(uint64(s.Chunks.Len()+1)), 82 unittest.WithApproverID(s.VerID)) 83 err := s.collector.ProcessApproval(approval) 84 require.Error(s.T(), err) 85 require.True(s.T(), engine.IsInvalidInputError(err)) 86 } 87 88 // TestCollectMissingVerifiers tests that approval collector correctly assembles list of verifiers that haven't provided approvals 89 // for each chunk 90 func (s *ApprovalCollectorTestSuite) TestCollectMissingVerifiers() { 91 s.sealsPL.On("Add", mock.Anything).Return(true, nil).Maybe() 92 93 assignedVerifiers := make(map[uint64]flow.IdentifierList) 94 for _, chunk := range s.Chunks { 95 assignedVerifiers[chunk.Index] = s.ChunksAssignment.Verifiers(chunk) 96 } 97 98 // no approvals processed 99 for index, ids := range s.collector.CollectMissingVerifiers() { 100 require.ElementsMatch(s.T(), ids, assignedVerifiers[index]) 101 } 102 103 // process one approval for one each chunk 104 for _, chunk := range s.Chunks { 105 verID := assignedVerifiers[chunk.Index][0] 106 approval := unittest.ResultApprovalFixture(unittest.WithChunk(chunk.Index), 107 unittest.WithApproverID(verID)) 108 err := s.collector.ProcessApproval(approval) 109 require.NoError(s.T(), err) 110 } 111 112 for index, ids := range s.collector.CollectMissingVerifiers() { 113 // skip first ID since we should have approval for it 114 require.ElementsMatch(s.T(), ids, assignedVerifiers[index][1:]) 115 } 116 117 // process remaining approvals for each chunk 118 for _, chunk := range s.Chunks { 119 for _, verID := range assignedVerifiers[chunk.Index] { 120 approval := unittest.ResultApprovalFixture(unittest.WithChunk(chunk.Index), 121 unittest.WithApproverID(verID)) 122 err := s.collector.ProcessApproval(approval) 123 require.NoError(s.T(), err) 124 } 125 } 126 127 // skip first ID since we should have approval for it 128 require.Empty(s.T(), s.collector.CollectMissingVerifiers()) 129 }