github.com/koko1123/flow-go-1@v0.29.6/engine/verification/fetcher/execution_fork_test.go (about) 1 package fetcher_test 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/mock" 9 "github.com/stretchr/testify/require" 10 11 verificationtest "github.com/koko1123/flow-go-1/engine/verification/utils/unittest" 12 "github.com/koko1123/flow-go-1/model/chunks" 13 "github.com/koko1123/flow-go-1/model/flow" 14 "github.com/koko1123/flow-go-1/model/verification" 15 storage "github.com/koko1123/flow-go-1/storage/mock" 16 "github.com/koko1123/flow-go-1/utils/unittest" 17 ) 18 19 // TestExecutionForkWithDuplicateAssignedChunks evaluates behavior of fetcher engine with respect to 20 // receiving duplicate identical assigned chunks on execution forks, i.e., an execution fork with two distinct results, where 21 // first chunk of both execution results are the same (i.e., duplicate), and both duplicate chunks are assigned to this 22 // verification node. 23 // 24 // The test asserts that a chunk data pack is requested for those duplicate chunks, and 25 // once the chunk data pack arrives, a verifiable chunk is shaped for each of those duplicate chunks, and 26 // verifiable chunks are pushed to verifier engine. 27 func TestExecutionForkWithDuplicateAssignedChunks(t *testing.T) { 28 s := setupTest() 29 e := newFetcherEngine(s) 30 31 // resultsA and resultB belong to an execution fork and their first chunk (statusA and statusB) 32 // is duplicate and assigned to this verification node. 33 block, resultA, statusA, resultB, statusB, collMap := executionResultForkFixture(t) 34 assignedChunkStatuses := verification.ChunkStatusList{statusA, statusB} 35 36 // executorsA and executorsB are execution node identities that executed resultA and resultB, respectively. 37 _, _, executorsA, executorsB := mockReceiptsBlockIDForConflictingResults(t, block.ID(), s.receipts, resultA, resultB) 38 s.metrics.On("OnAssignedChunkReceivedAtFetcher").Return().Times(2) 39 mockStateAtBlockIDForIdentities(s.state, block.ID(), executorsA.Union(executorsB)) 40 41 // the chunks belong to an unsealed block, so their chunk data pack is requested. 42 mockBlockSealingStatus(s.state, s.headers, block.Header, false) 43 44 // mocks resources on fetcher engine side. 45 mockResultsByIDs(s.results, []*flow.ExecutionResult{resultA, resultB}) 46 mockBlocksStorage(s.blocks, s.headers, block) 47 mockPendingChunksAdd(t, s.pendingChunks, assignedChunkStatuses, true) 48 mockPendingChunksRemove(t, s.pendingChunks, assignedChunkStatuses, true) 49 mockPendingChunksGet(s.pendingChunks, assignedChunkStatuses) 50 51 // fetcher engine must create a chunk data request for each of chunk statusA and statusB 52 requestA := chunkRequestFixture(resultA.ID(), statusA, executorsA, executorsB) 53 requestB := chunkRequestFixture(resultB.ID(), statusB, executorsB, executorsA) 54 requests := make(map[flow.Identifier]*verification.ChunkDataPackRequest) 55 requests[requestA.ID()] = requestA 56 requests[requestB.ID()] = requestB 57 s.metrics.On("OnChunkDataPackRequestSentByFetcher").Return().Times(len(assignedChunkStatuses)) 58 59 // each chunk data request is answered by requester engine on a distinct chunk data response 60 chunkALocatorID := statusA.ChunkLocatorID() 61 chunkBLocatorID := statusB.ChunkLocatorID() 62 chunkDataResponse := make(map[flow.Identifier]*verification.ChunkDataPackResponse) 63 chunkDataResponse[chunkALocatorID] = chunkDataPackResponseFixture(t, statusA.Chunk(), collMap[statusA.Chunk().ID()], resultA) 64 chunkDataResponse[chunkBLocatorID] = chunkDataPackResponseFixture(t, statusB.Chunk(), collMap[statusA.Chunk().ID()], resultB) 65 s.metrics.On("OnChunkDataPackArrivedAtFetcher").Return().Times(len(assignedChunkStatuses)) 66 67 // on receiving the chunk data responses, fetcher engine creates verifiable chunks 68 verifiableChunks := make(map[flow.Identifier]*verification.VerifiableChunkData) 69 verifiableChunks[chunkALocatorID] = verifiableChunkFixture(t, statusA.Chunk(), block, resultA, chunkDataResponse[chunkALocatorID].Cdp) 70 verifiableChunks[chunkBLocatorID] = verifiableChunkFixture(t, statusA.Chunk(), block, resultB, chunkDataResponse[chunkBLocatorID].Cdp) 71 s.metrics.On("OnVerifiableChunkSentToVerifier").Return().Times(len(assignedChunkStatuses)) 72 73 requesterWg := mockRequester(t, s.requester, requests, chunkDataResponse, 74 func(originID flow.Identifier, response *verification.ChunkDataPackResponse) { 75 // mocks replying to the requests by sending a chunk data pack. 76 e.HandleChunkDataPack(originID, response) 77 }) 78 79 verifierWG := mockVerifierEngine(t, s.verifier, verifiableChunks) 80 mockChunkConsumerNotifier(t, s.chunkConsumerNotifier, flow.IdentifierList{chunkALocatorID, chunkBLocatorID}) 81 82 // passes chunk data requests in parallel. 83 processWG := &sync.WaitGroup{} 84 processWG.Add(len(assignedChunkStatuses)) 85 for _, status := range assignedChunkStatuses { 86 locator := &chunks.Locator{ 87 Index: status.ChunkIndex, 88 ResultID: status.ExecutionResult.ID(), 89 } 90 91 go func(l *chunks.Locator) { 92 e.ProcessAssignedChunk(l) 93 processWG.Done() 94 }(locator) 95 96 } 97 98 unittest.RequireReturnsBefore(t, requesterWg.Wait, 100*time.Millisecond, "could not handle received chunk data pack on time") 99 unittest.RequireReturnsBefore(t, verifierWG.Wait, 100*time.Millisecond, "could not push verifiable chunk on time") 100 unittest.RequireReturnsBefore(t, processWG.Wait, 100*time.Millisecond, "could not process chunks on time") 101 102 mock.AssertExpectationsForObjects(t, s.results, s.requester, s.pendingChunks, s.chunkConsumerNotifier, s.metrics) 103 } 104 105 // executionResultForkFixture creates a reference block with two conflicting execution results that share the same first chunk, and 106 // creates chunk statuses for that first duplicate chunk on both results. 107 // 108 // It returns the block, results, assigned chunk statuses, their corresponding locators, and a map between chunks to their collections. 109 func executionResultForkFixture(t *testing.T) (*flow.Block, 110 *flow.ExecutionResult, 111 *verification.ChunkStatus, 112 *flow.ExecutionResult, 113 *verification.ChunkStatus, 114 map[flow.Identifier]*flow.Collection) { 115 116 resultA, resultB, collection, block := verificationtest.ExecutionResultForkFixture(t) 117 118 // creates chunk statuses for shared chunk of result A and B. 119 // this imitates that both identical chunks on execution fork are assigned to 120 // verification node. 121 statusA := &verification.ChunkStatus{ 122 ChunkIndex: 0, 123 ExecutionResult: resultA, 124 BlockHeight: block.Header.Height, 125 } 126 statusB := &verification.ChunkStatus{ 127 ChunkIndex: 0, 128 ExecutionResult: resultB, 129 BlockHeight: block.Header.Height, 130 } 131 132 // keeps collections of assigned chunks 133 collMap := make(map[flow.Identifier]*flow.Collection) 134 collMap[statusA.Chunk().ID()] = collection 135 136 return block, resultA, statusA, resultB, statusB, collMap 137 } 138 139 func mockReceiptsBlockIDForConflictingResults(t *testing.T, 140 blockID flow.Identifier, 141 receipts *storage.ExecutionReceipts, 142 resultA *flow.ExecutionResult, 143 resultB *flow.ExecutionResult, 144 ) (flow.ExecutionReceiptList, flow.ExecutionReceiptList, flow.IdentityList, flow.IdentityList) { 145 146 executorIdsA := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 147 executorIdsB := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 148 require.Len(t, executorIdsA.Union(executorIdsB), 4) // no overlap must be between executor ids 149 150 receiptsA := receiptsForResultFixture(resultA, executorIdsA.NodeIDs()) 151 receiptsB := receiptsForResultFixture(resultB, executorIdsB.NodeIDs()) 152 153 all := append(receiptsA, receiptsB...) 154 155 receipts.On("ByBlockID", blockID).Return(all, nil) 156 return receiptsA, receiptsB, executorIdsA, executorIdsB 157 } 158 159 func receiptsForResultFixture(result *flow.ExecutionResult, executors flow.IdentifierList) flow.ExecutionReceiptList { 160 receipts := flow.ExecutionReceiptList{} 161 162 for _, executor := range executors { 163 receipt := unittest.ExecutionReceiptFixture( 164 unittest.WithResult(result), 165 unittest.WithExecutorID(executor)) 166 167 receipts = append(receipts, receipt) 168 } 169 170 return receipts 171 }