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 }