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