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  }