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 }