github.com/onflow/flow-go@v0.33.17/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/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 clusterCommittee flow.IdentityList, 197 source []byte, 198 ) (*flow.ExecutionResult, *ExecutionReceiptData) { 199 200 // setups up the first collection of block consists of three transactions 201 tx1 := testutil.DeployCounterContractTransaction(chain.ServiceAddress(), chain) 202 err := testutil.SignTransactionAsServiceAccount(tx1, 0, chain) 203 require.NoError(t, err) 204 205 tx2 := testutil.CreateCounterTransaction(chain.ServiceAddress(), chain.ServiceAddress()) 206 err = testutil.SignTransactionAsServiceAccount(tx2, 1, chain) 207 require.NoError(t, err) 208 tx3 := testutil.CreateCounterPanicTransaction(chain.ServiceAddress(), chain.ServiceAddress()) 209 err = testutil.SignTransactionAsServiceAccount(tx3, 2, chain) 210 require.NoError(t, err) 211 transactions := []*flow.TransactionBody{tx1, tx2, tx3} 212 collection := flow.Collection{Transactions: transactions} 213 collections := []*flow.Collection{&collection} 214 clusterChainID := cluster.CanonicalClusterID(1, clusterCommittee.NodeIDs()) 215 216 guarantee := unittest.CollectionGuaranteeFixture(unittest.WithCollection(&collection), unittest.WithCollRef(refBlkHeader.ParentID)) 217 guarantee.ChainID = clusterChainID 218 indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) 219 require.NoError(t, err) 220 guarantee.SignerIndices = indices 221 guarantees := []*flow.CollectionGuarantee{guarantee} 222 223 metricsCollector := &metrics.NoopCollector{} 224 log := zerolog.Nop() 225 226 // setups execution outputs: 227 var referenceBlock flow.Block 228 var spockSecrets [][]byte 229 var chunkDataPacks []*flow.ChunkDataPack 230 var result *flow.ExecutionResult 231 232 unittest.RunWithTempDir(t, func(dir string) { 233 234 w := &fixtures.NoopWAL{} 235 236 led, err := completeLedger.NewLedger(w, 100, metricsCollector, zerolog.Nop(), completeLedger.DefaultPathFinderVersion) 237 require.NoError(t, err) 238 239 compactor := fixtures.NewNoopCompactor(led) 240 <-compactor.Ready() 241 242 defer func() { 243 <-led.Done() 244 <-compactor.Done() 245 }() 246 247 // set 0 clusters to pass n_collectors >= n_clusters check 248 epochConfig := epochs.DefaultEpochConfig() 249 epochConfig.NumCollectorClusters = 0 250 startStateCommitment, err := bootstrap.NewBootstrapper(log).BootstrapLedger( 251 led, 252 unittest.ServiceAccountPublicKey, 253 chain, 254 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 255 fvm.WithEpochConfig(epochConfig), 256 ) 257 require.NoError(t, err) 258 259 vm := fvm.NewVirtualMachine() 260 261 blocks := new(envMock.Blocks) 262 263 execCtx := fvm.NewContext( 264 fvm.WithLogger(log), 265 fvm.WithChain(chain), 266 fvm.WithBlocks(blocks), 267 ) 268 269 // create state.View 270 snapshot := exstate.NewLedgerStorageSnapshot( 271 led, 272 startStateCommitment) 273 committer := committer.NewLedgerViewCommitter(led, trace.NewNoopTracer()) 274 275 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 276 trackerStorage := mocktracker.NewMockStorage() 277 278 prov := provider.NewProvider( 279 zerolog.Nop(), 280 metrics.NewNoopCollector(), 281 execution_data.DefaultSerializer, 282 bservice, 283 trackerStorage, 284 ) 285 286 me := new(moduleMock.Local) 287 me.On("NodeID").Return(unittest.IdentifierFixture()) 288 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 289 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 290 Return(nil, nil) 291 292 // create BlockComputer 293 bc, err := computer.NewBlockComputer( 294 vm, 295 execCtx, 296 metrics.NewNoopCollector(), 297 trace.NewNoopTracer(), 298 log, 299 committer, 300 me, 301 prov, 302 nil, 303 testutil.ProtocolStateWithSourceFixture(source), 304 testMaxConcurrency) 305 require.NoError(t, err) 306 307 completeColls := make(map[flow.Identifier]*entity.CompleteCollection) 308 completeColls[guarantee.ID()] = &entity.CompleteCollection{ 309 Guarantee: guarantee, 310 Transactions: collection.Transactions, 311 } 312 313 for i := 1; i < chunkCount; i++ { 314 tx := testutil.CreateCounterTransaction(chain.ServiceAddress(), chain.ServiceAddress()) 315 err = testutil.SignTransactionAsServiceAccount(tx, 3+uint64(i), chain) 316 require.NoError(t, err) 317 318 collection := flow.Collection{Transactions: []*flow.TransactionBody{tx}} 319 guarantee := unittest.CollectionGuaranteeFixture(unittest.WithCollection(&collection), unittest.WithCollRef(refBlkHeader.ParentID)) 320 guarantee.SignerIndices = indices 321 guarantee.ChainID = clusterChainID 322 323 collections = append(collections, &collection) 324 guarantees = append(guarantees, guarantee) 325 326 completeColls[guarantee.ID()] = &entity.CompleteCollection{ 327 Guarantee: guarantee, 328 Transactions: collection.Transactions, 329 } 330 } 331 332 payload := flow.Payload{ 333 Guarantees: guarantees, 334 } 335 referenceBlock = flow.Block{ 336 Header: refBlkHeader, 337 } 338 referenceBlock.SetPayload(payload) 339 340 executableBlock := &entity.ExecutableBlock{ 341 Block: &referenceBlock, 342 CompleteCollections: completeColls, 343 StartState: &startStateCommitment, 344 } 345 computationResult, err := bc.ExecuteBlock( 346 context.Background(), 347 unittest.IdentifierFixture(), 348 executableBlock, 349 snapshot, 350 derived.NewEmptyDerivedBlockData(0)) 351 require.NoError(t, err) 352 353 for _, snapshot := range computationResult.AllExecutionSnapshots() { 354 spockSecrets = append(spockSecrets, snapshot.SpockSecret) 355 } 356 357 chunkDataPacks = computationResult.AllChunkDataPacks() 358 result = &computationResult.ExecutionResult 359 }) 360 361 return result, &ExecutionReceiptData{ 362 ReferenceBlock: &referenceBlock, 363 ChunkDataPacks: chunkDataPacks, 364 SpockSecrets: spockSecrets, 365 } 366 } 367 368 // CompleteExecutionReceiptChainFixture is a test fixture that creates a chain of blocks of size `count`. 369 // The chain is in the form of root <- R1,1 <- R1,2 <- ... <- C1 <- R2,1 <- R2,2 <- ... <- C2 <- ... 370 // In this chain R refers to reference blocks that contain guarantees. 371 // C refers to a container block that contains an execution receipt for its preceding reference blocks. 372 // e.g., C1 contains an execution receipt for R1,1, R1,2, etc., and C2 contains a receipt for R2,1, R2,2, etc. 373 // For sake of simplicity and test, container blocks (i.e., C) do not contain any guarantee. 374 // 375 // It returns a slice of complete execution receipt fixtures that contains a container block as well as all data to verify its contained receipts. 376 func CompleteExecutionReceiptChainFixture(t *testing.T, 377 root *flow.Header, 378 count int, 379 sources [][]byte, 380 opts ...CompleteExecutionReceiptBuilderOpt, 381 ) []*CompleteExecutionReceipt { 382 completeERs := make([]*CompleteExecutionReceipt, 0, count) 383 parent := root 384 385 builder := &CompleteExecutionReceiptBuilder{ 386 resultsCount: 1, 387 executorCount: 1, 388 chunksCount: 1, 389 chain: root.ChainID.Chain(), 390 } 391 392 for _, apply := range opts { 393 apply(builder) 394 } 395 396 if len(builder.executorIDs) == 0 { 397 builder.executorIDs = unittest.IdentifierListFixture(builder.executorCount) 398 } 399 400 require.GreaterOrEqual(t, len(builder.executorIDs), builder.executorCount, 401 "number of executors in the tests should be greater than or equal to the number of receipts per block") 402 403 var sourcesIndex = 0 404 for i := 0; i < count; i++ { 405 // Generates two blocks as parent <- R <- C where R is a reference block containing guarantees, 406 // and C is a container block containing execution receipt for R. 407 receipts, allData, head := ExecutionReceiptsFromParentBlockFixture(t, parent, builder, sources[sourcesIndex:]) 408 sourcesIndex += builder.resultsCount 409 containerBlock := ContainerBlockFixture(head, receipts, sources[sourcesIndex]) 410 sourcesIndex++ 411 completeERs = append(completeERs, &CompleteExecutionReceipt{ 412 ContainerBlock: containerBlock, 413 Receipts: receipts, 414 ReceiptsData: allData, 415 }) 416 417 parent = containerBlock.Header 418 } 419 return completeERs 420 } 421 422 // ExecutionReceiptsFromParentBlockFixture creates a chain of receipts from a parent block. 423 // 424 // By default each result refers to a distinct reference block, and it extends the block chain after generating each 425 // result (i.e., for the next result). 426 // 427 // Each result may appear in more than one receipt depending on the builder parameters. 428 func ExecutionReceiptsFromParentBlockFixture(t *testing.T, 429 parent *flow.Header, 430 builder *CompleteExecutionReceiptBuilder, 431 sources [][]byte) ( 432 []*flow.ExecutionReceipt, 433 []*ExecutionReceiptData, *flow.Header) { 434 435 allData := make([]*ExecutionReceiptData, 0, builder.resultsCount*builder.executorCount) 436 allReceipts := make([]*flow.ExecutionReceipt, 0, builder.resultsCount*builder.executorCount) 437 438 for i := 0; i < builder.resultsCount; i++ { 439 result, data := ExecutionResultFromParentBlockFixture(t, parent, builder, sources[i:]) 440 441 // makes several copies of the same result 442 for cp := 0; cp < builder.executorCount; cp++ { 443 allReceipts = append(allReceipts, &flow.ExecutionReceipt{ 444 ExecutorID: builder.executorIDs[cp], 445 ExecutionResult: *result, 446 }) 447 448 allData = append(allData, data) 449 } 450 parent = data.ReferenceBlock.Header 451 } 452 453 return allReceipts, allData, parent 454 } 455 456 // ExecutionResultFromParentBlockFixture is a test helper that creates a child (reference) block from the parent, as well as an execution for it. 457 func ExecutionResultFromParentBlockFixture(t *testing.T, 458 parent *flow.Header, 459 builder *CompleteExecutionReceiptBuilder, 460 sources [][]byte, 461 ) (*flow.ExecutionResult, *ExecutionReceiptData) { 462 // create the block header including a QC with source a index `i` 463 refBlkHeader := unittest.BlockHeaderWithParentWithSoRFixture(parent, sources[0]) 464 // execute the block with the source a index `i+1` (which will be included later in the child block) 465 return ExecutionResultFixture(t, builder.chunksCount, builder.chain, refBlkHeader, builder.clusterCommittee, sources[1]) 466 } 467 468 // ContainerBlockFixture builds and returns a block that contains input execution receipts. 469 func ContainerBlockFixture(parent *flow.Header, receipts []*flow.ExecutionReceipt, source []byte) *flow.Block { 470 // container block is the block that contains the execution receipt of reference block 471 containerBlock := unittest.BlockWithParentFixture(parent) 472 containerBlock.Header.ParentVoterSigData = unittest.QCSigDataWithSoRFixture(source) 473 containerBlock.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipts...))) 474 475 return containerBlock 476 } 477 478 // ExecutionResultForkFixture creates two conflicting execution results out of the same block ID. 479 // Each execution result has two chunks. 480 // First chunks of both results are the same, i.e., have same ID. 481 // It returns both results, their shared block, and collection corresponding to their first chunk. 482 func ExecutionResultForkFixture(t *testing.T) (*flow.ExecutionResult, *flow.ExecutionResult, *flow.Collection, *flow.Block) { 483 // collection and block 484 collections := unittest.CollectionListFixture(1) 485 block := unittest.BlockWithGuaranteesFixture( 486 unittest.CollectionGuaranteesWithCollectionIDFixture(collections), 487 ) 488 489 // execution fork at block with resultA and resultB that share first chunk 490 resultA := unittest.ExecutionResultFixture( 491 unittest.WithBlock(block), 492 unittest.WithChunks(2)) 493 resultB := &flow.ExecutionResult{ 494 PreviousResultID: resultA.PreviousResultID, 495 BlockID: resultA.BlockID, 496 Chunks: append(flow.ChunkList{resultA.Chunks[0]}, unittest.ChunkListFixture(1, resultA.BlockID)...), 497 ServiceEvents: nil, 498 } 499 500 // to be a valid fixture, results A and B must share first chunk. 501 require.Equal(t, resultA.Chunks[0].ID(), resultB.Chunks[0].ID()) 502 // and they must represent a fork 503 require.NotEqual(t, resultA.ID(), resultB.ID()) 504 505 return resultA, resultB, collections[0], block 506 }