github.com/koko1123/flow-go-1@v0.29.6/engine/verification/utils/unittest/fixture.go (about)

     1  package verificationtest
     2  
     3  import (
     4  	"context"
     5  	"math/rand"
     6  	"testing"
     7  
     8  	"github.com/ipfs/go-datastore"
     9  	dssync "github.com/ipfs/go-datastore/sync"
    10  	blockstore "github.com/ipfs/go-ipfs-blockstore"
    11  	"github.com/rs/zerolog"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/koko1123/flow-go-1/engine/execution"
    16  	"github.com/koko1123/flow-go-1/engine/execution/computation/committer"
    17  	"github.com/koko1123/flow-go-1/engine/execution/computation/computer"
    18  	"github.com/koko1123/flow-go-1/engine/execution/state"
    19  	"github.com/koko1123/flow-go-1/engine/execution/state/bootstrap"
    20  	"github.com/koko1123/flow-go-1/engine/execution/state/delta"
    21  	"github.com/koko1123/flow-go-1/engine/execution/testutil"
    22  	"github.com/koko1123/flow-go-1/fvm"
    23  	"github.com/koko1123/flow-go-1/fvm/derived"
    24  	completeLedger "github.com/koko1123/flow-go-1/ledger/complete"
    25  	"github.com/koko1123/flow-go-1/ledger/complete/wal/fixtures"
    26  	"github.com/koko1123/flow-go-1/model/convert"
    27  	"github.com/koko1123/flow-go-1/model/messages"
    28  	"github.com/koko1123/flow-go-1/module/epochs"
    29  	"github.com/koko1123/flow-go-1/module/signature"
    30  	"github.com/koko1123/flow-go-1/state/cluster"
    31  
    32  	envMock "github.com/koko1123/flow-go-1/fvm/environment/mock"
    33  	"github.com/koko1123/flow-go-1/model/flow"
    34  	"github.com/koko1123/flow-go-1/module/executiondatasync/execution_data"
    35  	"github.com/koko1123/flow-go-1/module/executiondatasync/provider"
    36  	mocktracker "github.com/koko1123/flow-go-1/module/executiondatasync/tracker/mock"
    37  	"github.com/koko1123/flow-go-1/module/mempool/entity"
    38  	"github.com/koko1123/flow-go-1/module/metrics"
    39  	moduleMock "github.com/koko1123/flow-go-1/module/mock"
    40  	requesterunit "github.com/koko1123/flow-go-1/module/state_synchronization/requester/unittest"
    41  	"github.com/koko1123/flow-go-1/module/trace"
    42  	"github.com/koko1123/flow-go-1/utils/unittest"
    43  )
    44  
    45  // ExecutionReceiptData is a test helper struct that represents all data required
    46  // to verify the result of an execution receipt.
    47  type ExecutionReceiptData struct {
    48  	ReferenceBlock *flow.Block // block that execution receipt refers to
    49  	ChunkDataPacks []*flow.ChunkDataPack
    50  	SpockSecrets   [][]byte
    51  }
    52  
    53  // CompleteExecutionReceipt is a test helper struct that represents a container block accompanied with all
    54  // data required to verify its execution receipts.
    55  // TODO update this as needed based on execution requirements
    56  type CompleteExecutionReceipt struct {
    57  	ContainerBlock *flow.Block // block that contains execution receipt of reference block
    58  
    59  	// TODO: this is a temporary field to support finder engine logic
    60  	// It should be removed once we replace finder engine.
    61  	Receipts     []*flow.ExecutionReceipt // copy of execution receipts in container block
    62  	ReceiptsData []*ExecutionReceiptData  // execution receipts data of the container block
    63  }
    64  
    65  type CompleteExecutionReceiptList []*CompleteExecutionReceipt
    66  
    67  // ChunkDataResponseOf is a test helper method that returns a chunk data pack response message for the specified chunk ID that
    68  // should belong to this complete execution receipt list.
    69  //
    70  // It fails the test if no chunk with specified chunk ID is found in this complete execution receipt list.
    71  func (c CompleteExecutionReceiptList) ChunkDataResponseOf(t *testing.T, chunkID flow.Identifier) *messages.ChunkDataResponse {
    72  	_, chunkIndex := c.resultOf(t, chunkID)
    73  	receiptData := c.ReceiptDataOf(t, chunkID)
    74  
    75  	// publishes the chunk data pack response to the network
    76  	res := &messages.ChunkDataResponse{
    77  		ChunkDataPack: *receiptData.ChunkDataPacks[chunkIndex],
    78  		Nonce:         rand.Uint64(),
    79  	}
    80  
    81  	return res
    82  }
    83  
    84  // ChunkOf is a test helper method that returns the chunk of the specified index from the specified result that
    85  // should belong to this complete execution receipt list.
    86  //
    87  // It fails the test if no execution result with the specified identifier is found in this complete execution receipt list.
    88  func (c CompleteExecutionReceiptList) ChunkOf(t *testing.T, resultID flow.Identifier, chunkIndex uint64) *flow.Chunk {
    89  	for _, completeER := range c {
    90  		for _, result := range completeER.ContainerBlock.Payload.Results {
    91  			if result.ID() == resultID {
    92  				return result.Chunks[chunkIndex]
    93  			}
    94  		}
    95  	}
    96  
    97  	require.Fail(t, "could not find specified chunk in the complete execution result list")
    98  	return nil
    99  }
   100  
   101  // ReceiptDataOf is a test helper method that returns the receipt data of the specified chunk ID that
   102  // should belong to this complete execution receipt list.
   103  //
   104  // It fails the test if no chunk with specified chunk ID is found in this complete execution receipt list.
   105  func (c CompleteExecutionReceiptList) ReceiptDataOf(t *testing.T, chunkID flow.Identifier) *ExecutionReceiptData {
   106  	for _, completeER := range c {
   107  		for _, receiptData := range completeER.ReceiptsData {
   108  			for _, cdp := range receiptData.ChunkDataPacks {
   109  				if cdp.ChunkID == chunkID {
   110  					return receiptData
   111  				}
   112  			}
   113  		}
   114  	}
   115  
   116  	require.Fail(t, "could not find receipt data of specified chunk in the complete execution result list")
   117  	return nil
   118  }
   119  
   120  // resultOf is a test helper method that returns the execution result and chunk index of the specified chunk ID that
   121  // should belong to this complete execution receipt list.
   122  //
   123  // It fails the test if no chunk with specified chunk ID is found in this complete execution receipt list.
   124  func (c CompleteExecutionReceiptList) resultOf(t *testing.T, chunkID flow.Identifier) (*flow.ExecutionResult, uint64) {
   125  	for _, completeER := range c {
   126  		for _, result := range completeER.ContainerBlock.Payload.Results {
   127  			for _, chunk := range result.Chunks {
   128  				if chunk.ID() == chunkID {
   129  					return result, chunk.Index
   130  				}
   131  			}
   132  		}
   133  	}
   134  
   135  	require.Fail(t, "could not find specified chunk in the complete execution result list")
   136  	return nil, uint64(0)
   137  }
   138  
   139  // CompleteExecutionReceiptBuilder is a test helper struct that specifies the parameters to build a CompleteExecutionReceipt.
   140  type CompleteExecutionReceiptBuilder struct {
   141  	resultsCount     int // number of execution results in the container block.
   142  	executorCount    int // number of times each execution result is copied in a block (by different receipts).
   143  	chunksCount      int // number of chunks in each execution result.
   144  	chain            flow.Chain
   145  	executorIDs      flow.IdentifierList // identifier of execution nodes in the test.
   146  	clusterCommittee flow.IdentityList
   147  }
   148  
   149  type CompleteExecutionReceiptBuilderOpt func(builder *CompleteExecutionReceiptBuilder)
   150  
   151  func WithResults(count int) CompleteExecutionReceiptBuilderOpt {
   152  	return func(builder *CompleteExecutionReceiptBuilder) {
   153  		builder.resultsCount = count
   154  	}
   155  }
   156  
   157  func WithChunksCount(count int) CompleteExecutionReceiptBuilderOpt {
   158  	return func(builder *CompleteExecutionReceiptBuilder) {
   159  		builder.chunksCount = count
   160  	}
   161  }
   162  
   163  func WithCopies(count int) CompleteExecutionReceiptBuilderOpt {
   164  	return func(builder *CompleteExecutionReceiptBuilder) {
   165  		builder.executorCount = count
   166  	}
   167  }
   168  
   169  func WithChain(chain flow.Chain) CompleteExecutionReceiptBuilderOpt {
   170  	return func(builder *CompleteExecutionReceiptBuilder) {
   171  		builder.chain = chain
   172  	}
   173  }
   174  
   175  func WithExecutorIDs(executorIDs flow.IdentifierList) CompleteExecutionReceiptBuilderOpt {
   176  	return func(builder *CompleteExecutionReceiptBuilder) {
   177  		builder.executorIDs = executorIDs
   178  	}
   179  }
   180  
   181  func WithClusterCommittee(clusterCommittee flow.IdentityList) CompleteExecutionReceiptBuilderOpt {
   182  	return func(builder *CompleteExecutionReceiptBuilder) {
   183  		builder.clusterCommittee = clusterCommittee
   184  	}
   185  }
   186  
   187  // ExecutionResultFixture is a test helper that returns an execution result for the reference block header as well as the execution receipt data
   188  // for that result.
   189  func ExecutionResultFixture(t *testing.T, chunkCount int, chain flow.Chain, refBlkHeader *flow.Header, clusterCommittee flow.IdentityList) (*flow.ExecutionResult,
   190  	*ExecutionReceiptData) {
   191  
   192  	// setups up the first collection of block consists of three transactions
   193  	tx1 := testutil.DeployCounterContractTransaction(chain.ServiceAddress(), chain)
   194  	err := testutil.SignTransactionAsServiceAccount(tx1, 0, chain)
   195  	require.NoError(t, err)
   196  
   197  	tx2 := testutil.CreateCounterTransaction(chain.ServiceAddress(), chain.ServiceAddress())
   198  	err = testutil.SignTransactionAsServiceAccount(tx2, 1, chain)
   199  	require.NoError(t, err)
   200  	tx3 := testutil.CreateCounterPanicTransaction(chain.ServiceAddress(), chain.ServiceAddress())
   201  	err = testutil.SignTransactionAsServiceAccount(tx3, 2, chain)
   202  	require.NoError(t, err)
   203  	transactions := []*flow.TransactionBody{tx1, tx2, tx3}
   204  	collection := flow.Collection{Transactions: transactions}
   205  	collections := []*flow.Collection{&collection}
   206  	clusterChainID := cluster.CanonicalClusterID(1, clusterCommittee)
   207  
   208  	guarantee := unittest.CollectionGuaranteeFixture(unittest.WithCollection(&collection), unittest.WithCollRef(refBlkHeader.ParentID))
   209  	guarantee.ChainID = clusterChainID
   210  	indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs())
   211  	require.NoError(t, err)
   212  	guarantee.SignerIndices = indices
   213  	guarantees := []*flow.CollectionGuarantee{guarantee}
   214  
   215  	metricsCollector := &metrics.NoopCollector{}
   216  	log := zerolog.Nop()
   217  
   218  	// setups execution outputs:
   219  	spockSecrets := make([][]byte, 0)
   220  	chunks := make([]*flow.Chunk, 0)
   221  	chunkDataPacks := make([]*flow.ChunkDataPack, 0)
   222  
   223  	var payload flow.Payload
   224  	var referenceBlock flow.Block
   225  	var serviceEvents flow.ServiceEventList
   226  
   227  	unittest.RunWithTempDir(t, func(dir string) {
   228  
   229  		w := &fixtures.NoopWAL{}
   230  
   231  		led, err := completeLedger.NewLedger(w, 100, metricsCollector, zerolog.Nop(), completeLedger.DefaultPathFinderVersion)
   232  		require.NoError(t, err)
   233  
   234  		compactor := fixtures.NewNoopCompactor(led)
   235  		<-compactor.Ready()
   236  
   237  		defer func() {
   238  			<-led.Done()
   239  			<-compactor.Done()
   240  		}()
   241  
   242  		// set 0 clusters to pass n_collectors >= n_clusters check
   243  		epochConfig := epochs.DefaultEpochConfig()
   244  		epochConfig.NumCollectorClusters = 0
   245  		startStateCommitment, err := bootstrap.NewBootstrapper(log).BootstrapLedger(
   246  			led,
   247  			unittest.ServiceAccountPublicKey,
   248  			chain,
   249  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   250  			fvm.WithEpochConfig(epochConfig),
   251  		)
   252  		require.NoError(t, err)
   253  
   254  		vm := fvm.NewVirtualMachine()
   255  
   256  		blocks := new(envMock.Blocks)
   257  
   258  		execCtx := fvm.NewContext(
   259  			fvm.WithLogger(log),
   260  			fvm.WithChain(chain),
   261  			fvm.WithBlocks(blocks),
   262  		)
   263  
   264  		// create state.View
   265  		view := delta.NewView(state.LedgerGetRegister(led, startStateCommitment))
   266  		committer := committer.NewLedgerViewCommitter(led, trace.NewNoopTracer())
   267  		derivedBlockData := derived.NewEmptyDerivedBlockData()
   268  
   269  		bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   270  		trackerStorage := mocktracker.NewMockStorage()
   271  
   272  		prov := provider.NewProvider(
   273  			zerolog.Nop(),
   274  			metrics.NewNoopCollector(),
   275  			execution_data.DefaultSerializer,
   276  			bservice,
   277  			trackerStorage,
   278  		)
   279  
   280  		me := new(moduleMock.Local)
   281  		me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything).
   282  			Return(nil, nil)
   283  
   284  		// create BlockComputer
   285  		bc, err := computer.NewBlockComputer(
   286  			vm,
   287  			execCtx,
   288  			metrics.NewNoopCollector(),
   289  			trace.NewNoopTracer(),
   290  			log,
   291  			committer,
   292  			me,
   293  			prov)
   294  		require.NoError(t, err)
   295  
   296  		completeColls := make(map[flow.Identifier]*entity.CompleteCollection)
   297  		completeColls[guarantee.ID()] = &entity.CompleteCollection{
   298  			Guarantee:    guarantee,
   299  			Transactions: collection.Transactions,
   300  		}
   301  
   302  		for i := 1; i < chunkCount; i++ {
   303  			tx := testutil.CreateCounterTransaction(chain.ServiceAddress(), chain.ServiceAddress())
   304  			err = testutil.SignTransactionAsServiceAccount(tx, 3+uint64(i), chain)
   305  			require.NoError(t, err)
   306  
   307  			collection := flow.Collection{Transactions: []*flow.TransactionBody{tx}}
   308  			guarantee := unittest.CollectionGuaranteeFixture(unittest.WithCollection(&collection), unittest.WithCollRef(refBlkHeader.ParentID))
   309  			guarantee.SignerIndices = indices
   310  			guarantee.ChainID = clusterChainID
   311  
   312  			collections = append(collections, &collection)
   313  			guarantees = append(guarantees, guarantee)
   314  
   315  			completeColls[guarantee.ID()] = &entity.CompleteCollection{
   316  				Guarantee:    guarantee,
   317  				Transactions: collection.Transactions,
   318  			}
   319  		}
   320  
   321  		payload = flow.Payload{
   322  			Guarantees: guarantees,
   323  		}
   324  		referenceBlock = flow.Block{
   325  			Header: refBlkHeader,
   326  		}
   327  		referenceBlock.SetPayload(payload)
   328  
   329  		executableBlock := &entity.ExecutableBlock{
   330  			Block:               &referenceBlock,
   331  			CompleteCollections: completeColls,
   332  			StartState:          &startStateCommitment,
   333  		}
   334  		computationResult, err := bc.ExecuteBlock(context.Background(), executableBlock, view, derivedBlockData)
   335  		require.NoError(t, err)
   336  		serviceEvents = make([]flow.ServiceEvent, 0, len(computationResult.ServiceEvents))
   337  		for _, event := range computationResult.ServiceEvents {
   338  			converted, err := convert.ServiceEvent(referenceBlock.Header.ChainID, event)
   339  			require.NoError(t, err)
   340  			serviceEvents = append(serviceEvents, *converted)
   341  		}
   342  
   343  		startState := startStateCommitment
   344  
   345  		for i := range computationResult.StateCommitments {
   346  			endState := computationResult.StateCommitments[i]
   347  
   348  			// generates chunk and chunk data pack
   349  			var chunkDataPack *flow.ChunkDataPack
   350  			var chunk *flow.Chunk
   351  			if i < len(computationResult.StateCommitments)-1 {
   352  				// generates chunk data pack fixture for non-system chunk
   353  				collectionGuarantee := executableBlock.Block.Payload.Guarantees[i]
   354  				completeCollection := executableBlock.CompleteCollections[collectionGuarantee.ID()]
   355  				collection := completeCollection.Collection()
   356  
   357  				eventsHash, err := flow.EventsMerkleRootHash(computationResult.Events[i])
   358  				require.NoError(t, err)
   359  
   360  				chunk = execution.GenerateChunk(i, startState, endState, executableBlock.ID(), eventsHash, uint64(len(completeCollection.Transactions)))
   361  				chunkDataPack = execution.GenerateChunkDataPack(chunk.ID(), chunk.StartState, &collection, computationResult.Proofs[i])
   362  			} else {
   363  				// generates chunk data pack fixture for system chunk
   364  				eventsHash, err := flow.EventsMerkleRootHash(computationResult.Events[i])
   365  				require.NoError(t, err)
   366  
   367  				chunk = execution.GenerateChunk(i, startState, endState, executableBlock.ID(), eventsHash, uint64(1))
   368  				chunkDataPack = execution.GenerateChunkDataPack(chunk.ID(), chunk.StartState, nil, computationResult.Proofs[i])
   369  			}
   370  
   371  			chunks = append(chunks, chunk)
   372  			chunkDataPacks = append(chunkDataPacks, chunkDataPack)
   373  			spockSecrets = append(spockSecrets, computationResult.StateSnapshots[i].SpockSecret)
   374  			startState = endState
   375  		}
   376  
   377  	})
   378  
   379  	// makes sure all chunks are referencing the correct block id.
   380  	blockID := referenceBlock.ID()
   381  	for _, chunk := range chunks {
   382  		require.Equal(t, blockID, chunk.BlockID, "inconsistent block id in chunk fixture")
   383  	}
   384  
   385  	result := &flow.ExecutionResult{
   386  		BlockID:       blockID,
   387  		Chunks:        chunks,
   388  		ServiceEvents: serviceEvents,
   389  	}
   390  
   391  	return result, &ExecutionReceiptData{
   392  		ReferenceBlock: &referenceBlock,
   393  		ChunkDataPacks: chunkDataPacks,
   394  		SpockSecrets:   spockSecrets,
   395  	}
   396  }
   397  
   398  // CompleteExecutionReceiptChainFixture is a test fixture that creates a chain of blocks of size `count`.
   399  // The chain is in the form of root <- R1,1 <- R1,2 <- ... <- C1 <- R2,1 <- R2,2 <- ... <- C2 <- ...
   400  // In this chain R refers to reference blocks that contain guarantees.
   401  // C refers to a container block that contains an execution receipt for its preceding reference blocks.
   402  // e.g., C1 contains an execution receipt for R1,1, R1,2, etc., and C2 contains a receipt for R2,1, R2,2, etc.
   403  // For sake of simplicity and test, container blocks (i.e., C) do not contain any guarantee.
   404  //
   405  // It returns a slice of complete execution receipt fixtures that contains a container block as well as all data to verify its contained receipts.
   406  func CompleteExecutionReceiptChainFixture(t *testing.T, root *flow.Header, count int, opts ...CompleteExecutionReceiptBuilderOpt) []*CompleteExecutionReceipt {
   407  	completeERs := make([]*CompleteExecutionReceipt, 0, count)
   408  	parent := root
   409  
   410  	builder := &CompleteExecutionReceiptBuilder{
   411  		resultsCount:  1,
   412  		executorCount: 1,
   413  		chunksCount:   1,
   414  		chain:         root.ChainID.Chain(),
   415  	}
   416  
   417  	for _, apply := range opts {
   418  		apply(builder)
   419  	}
   420  
   421  	if len(builder.executorIDs) == 0 {
   422  		builder.executorIDs = unittest.IdentifierListFixture(builder.executorCount)
   423  	}
   424  
   425  	require.GreaterOrEqual(t, len(builder.executorIDs), builder.executorCount,
   426  		"number of executors in the tests should be greater than or equal to the number of receipts per block")
   427  
   428  	for i := 0; i < count; i++ {
   429  		// Generates two blocks as parent <- R <- C where R is a reference block containing guarantees,
   430  		// and C is a container block containing execution receipt for R.
   431  		receipts, allData, head := ExecutionReceiptsFromParentBlockFixture(t, parent, builder)
   432  		containerBlock := ContainerBlockFixture(head, receipts)
   433  		completeERs = append(completeERs, &CompleteExecutionReceipt{
   434  			ContainerBlock: containerBlock,
   435  			Receipts:       receipts,
   436  			ReceiptsData:   allData,
   437  		})
   438  
   439  		parent = containerBlock.Header
   440  	}
   441  	return completeERs
   442  }
   443  
   444  // ExecutionReceiptsFromParentBlockFixture creates a chain of receipts from a parent block.
   445  //
   446  // By default each result refers to a distinct reference block, and it extends the block chain after generating each
   447  // result (i.e., for the next result).
   448  //
   449  // Each result may appear in more than one receipt depending on the builder parameters.
   450  func ExecutionReceiptsFromParentBlockFixture(t *testing.T, parent *flow.Header, builder *CompleteExecutionReceiptBuilder) (
   451  	[]*flow.ExecutionReceipt,
   452  	[]*ExecutionReceiptData, *flow.Header) {
   453  
   454  	allData := make([]*ExecutionReceiptData, 0, builder.resultsCount*builder.executorCount)
   455  	allReceipts := make([]*flow.ExecutionReceipt, 0, builder.resultsCount*builder.executorCount)
   456  
   457  	for i := 0; i < builder.resultsCount; i++ {
   458  		result, data := ExecutionResultFromParentBlockFixture(t, parent, builder)
   459  
   460  		// makes several copies of the same result
   461  		for cp := 0; cp < builder.executorCount; cp++ {
   462  			allReceipts = append(allReceipts, &flow.ExecutionReceipt{
   463  				ExecutorID:      builder.executorIDs[cp],
   464  				ExecutionResult: *result,
   465  			})
   466  
   467  			allData = append(allData, data)
   468  		}
   469  		parent = data.ReferenceBlock.Header
   470  	}
   471  
   472  	return allReceipts, allData, parent
   473  }
   474  
   475  // ExecutionResultFromParentBlockFixture is a test helper that creates a child (reference) block from the parent, as well as an execution for it.
   476  func ExecutionResultFromParentBlockFixture(t *testing.T, parent *flow.Header, builder *CompleteExecutionReceiptBuilder) (*flow.ExecutionResult,
   477  	*ExecutionReceiptData) {
   478  	refBlkHeader := unittest.BlockHeaderWithParentFixture(parent)
   479  	return ExecutionResultFixture(t, builder.chunksCount, builder.chain, refBlkHeader, builder.clusterCommittee)
   480  }
   481  
   482  // ContainerBlockFixture builds and returns a block that contains input execution receipts.
   483  func ContainerBlockFixture(parent *flow.Header, receipts []*flow.ExecutionReceipt) *flow.Block {
   484  	// container block is the block that contains the execution receipt of reference block
   485  	containerBlock := unittest.BlockWithParentFixture(parent)
   486  	containerBlock.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipts...)))
   487  
   488  	return containerBlock
   489  }
   490  
   491  // ExecutionResultForkFixture creates two conflicting execution results out of the same block ID.
   492  // Each execution result has two chunks.
   493  // First chunks of both results are the same, i.e., have same ID.
   494  // It returns both results, their shared block, and collection corresponding to their first chunk.
   495  func ExecutionResultForkFixture(t *testing.T) (*flow.ExecutionResult, *flow.ExecutionResult, *flow.Collection, *flow.Block) {
   496  	// collection and block
   497  	collections := unittest.CollectionListFixture(1)
   498  	block := unittest.BlockWithGuaranteesFixture(
   499  		unittest.CollectionGuaranteesWithCollectionIDFixture(collections),
   500  	)
   501  
   502  	// execution fork at block with resultA and resultB that share first chunk
   503  	resultA := unittest.ExecutionResultFixture(
   504  		unittest.WithBlock(block),
   505  		unittest.WithChunks(2))
   506  	resultB := &flow.ExecutionResult{
   507  		PreviousResultID: resultA.PreviousResultID,
   508  		BlockID:          resultA.BlockID,
   509  		Chunks:           append(flow.ChunkList{resultA.Chunks[0]}, unittest.ChunkListFixture(1, resultA.BlockID)...),
   510  		ServiceEvents:    nil,
   511  	}
   512  
   513  	// to be a valid fixture, results A and B must share first chunk.
   514  	require.Equal(t, resultA.Chunks[0].ID(), resultB.Chunks[0].ID())
   515  	// and they must represent a fork
   516  	require.NotEqual(t, resultA.ID(), resultB.ID())
   517  
   518  	return resultA, resultB, collections[0], block
   519  }