github.com/onflow/flow-go@v0.33.17/engine/execution/computation/computer/computer_test.go (about) 1 package computer_test 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "sync/atomic" 8 "testing" 9 10 "github.com/onflow/cadence" 11 "github.com/onflow/cadence/encoding/ccf" 12 "github.com/onflow/cadence/runtime" 13 "github.com/onflow/cadence/runtime/common" 14 "github.com/onflow/cadence/runtime/interpreter" 15 "github.com/onflow/cadence/runtime/sema" 16 "github.com/onflow/cadence/runtime/stdlib" 17 18 "github.com/ipfs/go-datastore" 19 dssync "github.com/ipfs/go-datastore/sync" 20 blockstore "github.com/ipfs/go-ipfs-blockstore" 21 "github.com/rs/zerolog" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/mock" 24 "github.com/stretchr/testify/require" 25 26 "github.com/onflow/flow-go/engine/execution" 27 "github.com/onflow/flow-go/engine/execution/computation/committer" 28 "github.com/onflow/flow-go/engine/execution/computation/computer" 29 computermock "github.com/onflow/flow-go/engine/execution/computation/computer/mock" 30 "github.com/onflow/flow-go/engine/execution/storehouse" 31 "github.com/onflow/flow-go/engine/execution/testutil" 32 "github.com/onflow/flow-go/fvm" 33 "github.com/onflow/flow-go/fvm/environment" 34 fvmErrors "github.com/onflow/flow-go/fvm/errors" 35 fvmmock "github.com/onflow/flow-go/fvm/mock" 36 reusableRuntime "github.com/onflow/flow-go/fvm/runtime" 37 "github.com/onflow/flow-go/fvm/storage" 38 "github.com/onflow/flow-go/fvm/storage/derived" 39 "github.com/onflow/flow-go/fvm/storage/logical" 40 "github.com/onflow/flow-go/fvm/storage/snapshot" 41 "github.com/onflow/flow-go/fvm/storage/state" 42 "github.com/onflow/flow-go/fvm/systemcontracts" 43 "github.com/onflow/flow-go/ledger" 44 "github.com/onflow/flow-go/ledger/common/convert" 45 "github.com/onflow/flow-go/ledger/common/pathfinder" 46 "github.com/onflow/flow-go/ledger/complete" 47 "github.com/onflow/flow-go/model/flow" 48 "github.com/onflow/flow-go/module/epochs" 49 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 50 "github.com/onflow/flow-go/module/executiondatasync/provider" 51 mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock" 52 "github.com/onflow/flow-go/module/mempool/entity" 53 "github.com/onflow/flow-go/module/metrics" 54 modulemock "github.com/onflow/flow-go/module/mock" 55 requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest" 56 "github.com/onflow/flow-go/module/trace" 57 "github.com/onflow/flow-go/utils/unittest" 58 ) 59 60 const ( 61 testMaxConcurrency = 2 62 ) 63 64 func incStateCommitment(startState flow.StateCommitment) flow.StateCommitment { 65 endState := flow.StateCommitment(startState) 66 endState[0] += 1 67 return endState 68 } 69 70 type fakeCommitter struct { 71 callCount int 72 } 73 74 func (committer *fakeCommitter) CommitView( 75 view *snapshot.ExecutionSnapshot, 76 baseStorageSnapshot execution.ExtendableStorageSnapshot, 77 ) ( 78 flow.StateCommitment, 79 []byte, 80 *ledger.TrieUpdate, 81 execution.ExtendableStorageSnapshot, 82 error, 83 ) { 84 committer.callCount++ 85 86 startState := baseStorageSnapshot.Commitment() 87 endState := incStateCommitment(startState) 88 89 reg := unittest.MakeOwnerReg("key", fmt.Sprintf("%v", committer.callCount)) 90 regKey := convert.RegisterIDToLedgerKey(reg.Key) 91 path, err := pathfinder.KeyToPath( 92 regKey, 93 complete.DefaultPathFinderVersion, 94 ) 95 if err != nil { 96 return flow.DummyStateCommitment, nil, nil, nil, err 97 } 98 trieUpdate := &ledger.TrieUpdate{ 99 RootHash: ledger.RootHash(startState), 100 Paths: []ledger.Path{ 101 path, 102 }, 103 Payloads: []*ledger.Payload{ 104 ledger.NewPayload(regKey, reg.Value), 105 }, 106 } 107 108 newStorageSnapshot := baseStorageSnapshot.Extend(endState, map[flow.RegisterID]flow.RegisterValue{ 109 reg.Key: reg.Value, 110 }) 111 112 return newStorageSnapshot.Commitment(), 113 []byte{byte(committer.callCount)}, 114 trieUpdate, 115 newStorageSnapshot, 116 nil 117 } 118 119 func TestBlockExecutor_ExecuteBlock(t *testing.T) { 120 121 rag := &RandomAddressGenerator{} 122 123 executorID := unittest.IdentifierFixture() 124 125 me := new(modulemock.Local) 126 me.On("NodeID").Return(executorID) 127 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 128 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 129 Return(nil, nil) 130 131 t.Run("single collection", func(t *testing.T) { 132 133 execCtx := fvm.NewContext() 134 135 vm := &testVM{ 136 t: t, 137 eventsPerTransaction: 1, 138 } 139 140 committer := &fakeCommitter{ 141 callCount: 0, 142 } 143 144 exemetrics := new(modulemock.ExecutionMetrics) 145 exemetrics.On("ExecutionBlockExecuted", 146 mock.Anything, // duration 147 mock.Anything). // stats 148 Return(nil). 149 Times(1) 150 151 exemetrics.On("ExecutionCollectionExecuted", 152 mock.Anything, // duration 153 mock.Anything). // stats 154 Return(nil). 155 Times(2) // 1 collection + system collection 156 157 exemetrics.On("ExecutionTransactionExecuted", 158 mock.Anything, // duration 159 mock.Anything, // conflict retry count 160 mock.Anything, // computation used 161 mock.Anything, // memory used 162 mock.Anything, // number of events 163 mock.Anything, // size of events 164 false). // no failure 165 Return(nil). 166 Times(2 + 1) // 2 txs in collection + system chunk tx 167 168 exemetrics.On( 169 "ExecutionChunkDataPackGenerated", 170 mock.Anything, 171 mock.Anything). 172 Return(nil). 173 Times(2) // 1 collection + system collection 174 175 expectedProgramsInCache := 1 // we set one program in the cache 176 exemetrics.On( 177 "ExecutionBlockCachedPrograms", 178 expectedProgramsInCache). 179 Return(nil). 180 Times(1) // 1 block 181 182 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 183 trackerStorage := mocktracker.NewMockStorage() 184 185 prov := provider.NewProvider( 186 zerolog.Nop(), 187 metrics.NewNoopCollector(), 188 execution_data.DefaultSerializer, 189 bservice, 190 trackerStorage, 191 ) 192 193 exe, err := computer.NewBlockComputer( 194 vm, 195 execCtx, 196 exemetrics, 197 trace.NewNoopTracer(), 198 zerolog.Nop(), 199 committer, 200 me, 201 prov, 202 nil, 203 testutil.ProtocolStateWithSourceFixture(nil), 204 testMaxConcurrency) 205 require.NoError(t, err) 206 207 // create a block with 1 collection with 2 transactions 208 block := generateBlock(1, 2, rag) 209 210 parentBlockExecutionResultID := unittest.IdentifierFixture() 211 result, err := exe.ExecuteBlock( 212 context.Background(), 213 parentBlockExecutionResultID, 214 block, 215 nil, 216 derived.NewEmptyDerivedBlockData(0)) 217 assert.NoError(t, err) 218 assert.Len(t, result.AllExecutionSnapshots(), 1+1) // +1 system chunk 219 220 require.Equal(t, 2, committer.callCount) 221 222 assert.Equal(t, block.ID(), result.BlockExecutionData.BlockID) 223 224 expectedChunk1EndState := incStateCommitment(*block.StartState) 225 expectedChunk2EndState := incStateCommitment(expectedChunk1EndState) 226 227 assert.Equal(t, expectedChunk2EndState, result.CurrentEndState()) 228 229 assertEventHashesMatch(t, 1+1, result) 230 231 // Verify ExecutionReceipt 232 receipt := result.ExecutionReceipt 233 234 assert.Equal(t, executorID, receipt.ExecutorID) 235 assert.Equal( 236 t, 237 parentBlockExecutionResultID, 238 receipt.PreviousResultID) 239 assert.Equal(t, block.ID(), receipt.BlockID) 240 assert.NotEqual(t, flow.ZeroID, receipt.ExecutionDataID) 241 242 assert.Len(t, receipt.Chunks, 1+1) // +1 system chunk 243 244 chunk1 := receipt.Chunks[0] 245 246 eventCommits := result.AllEventCommitments() 247 assert.Equal(t, block.ID(), chunk1.BlockID) 248 assert.Equal(t, uint(0), chunk1.CollectionIndex) 249 assert.Equal(t, uint64(2), chunk1.NumberOfTransactions) 250 assert.Equal(t, eventCommits[0], chunk1.EventCollection) 251 252 assert.Equal(t, *block.StartState, chunk1.StartState) 253 254 assert.NotEqual(t, *block.StartState, chunk1.EndState) 255 assert.NotEqual(t, flow.DummyStateCommitment, chunk1.EndState) 256 assert.Equal(t, expectedChunk1EndState, chunk1.EndState) 257 258 chunk2 := receipt.Chunks[1] 259 assert.Equal(t, block.ID(), chunk2.BlockID) 260 assert.Equal(t, uint(1), chunk2.CollectionIndex) 261 assert.Equal(t, uint64(1), chunk2.NumberOfTransactions) 262 assert.Equal(t, eventCommits[1], chunk2.EventCollection) 263 264 assert.Equal(t, expectedChunk1EndState, chunk2.StartState) 265 266 assert.NotEqual(t, *block.StartState, chunk2.EndState) 267 assert.NotEqual(t, flow.DummyStateCommitment, chunk2.EndState) 268 assert.NotEqual(t, expectedChunk1EndState, chunk2.EndState) 269 assert.Equal(t, expectedChunk2EndState, chunk2.EndState) 270 271 // Verify ChunkDataPacks 272 273 chunkDataPacks := result.AllChunkDataPacks() 274 assert.Len(t, chunkDataPacks, 1+1) // +1 system chunk 275 276 chunkDataPack1 := chunkDataPacks[0] 277 278 assert.Equal(t, chunk1.ID(), chunkDataPack1.ChunkID) 279 assert.Equal(t, *block.StartState, chunkDataPack1.StartState) 280 assert.Equal(t, []byte{1}, chunkDataPack1.Proof) 281 assert.NotNil(t, chunkDataPack1.Collection) 282 283 chunkDataPack2 := chunkDataPacks[1] 284 285 assert.Equal(t, chunk2.ID(), chunkDataPack2.ChunkID) 286 assert.Equal(t, chunk2.StartState, chunkDataPack2.StartState) 287 assert.Equal(t, []byte{2}, chunkDataPack2.Proof) 288 assert.Nil(t, chunkDataPack2.Collection) 289 290 // Verify BlockExecutionData 291 292 assert.Len(t, result.ChunkExecutionDatas, 1+1) // +1 system chunk 293 294 chunkExecutionData1 := result.ChunkExecutionDatas[0] 295 assert.Equal( 296 t, 297 chunkDataPack1.Collection, 298 chunkExecutionData1.Collection) 299 assert.NotNil(t, chunkExecutionData1.TrieUpdate) 300 assert.Equal(t, ledger.RootHash(chunk1.StartState), chunkExecutionData1.TrieUpdate.RootHash) 301 302 chunkExecutionData2 := result.ChunkExecutionDatas[1] 303 assert.NotNil(t, chunkExecutionData2.Collection) 304 assert.NotNil(t, chunkExecutionData2.TrieUpdate) 305 assert.Equal(t, ledger.RootHash(chunk2.StartState), chunkExecutionData2.TrieUpdate.RootHash) 306 307 assert.GreaterOrEqual(t, vm.CallCount(), 3) 308 // if every transaction is retried once, then the call count should be 309 // (1+totalTransactionCount) /2 * totalTransactionCount 310 assert.LessOrEqual(t, vm.CallCount(), (1+3)/2*3) 311 }) 312 313 t.Run("empty block still computes system chunk", func(t *testing.T) { 314 315 execCtx := fvm.NewContext() 316 317 vm := new(fvmmock.VM) 318 committer := new(computermock.ViewCommitter) 319 320 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 321 trackerStorage := mocktracker.NewMockStorage() 322 323 prov := provider.NewProvider( 324 zerolog.Nop(), 325 metrics.NewNoopCollector(), 326 execution_data.DefaultSerializer, 327 bservice, 328 trackerStorage, 329 ) 330 331 exe, err := computer.NewBlockComputer( 332 vm, 333 execCtx, 334 metrics.NewNoopCollector(), 335 trace.NewNoopTracer(), 336 zerolog.Nop(), 337 committer, 338 me, 339 prov, 340 nil, 341 testutil.ProtocolStateWithSourceFixture(nil), 342 testMaxConcurrency) 343 require.NoError(t, err) 344 345 // create an empty block 346 block := generateBlock(0, 0, rag) 347 derivedBlockData := derived.NewEmptyDerivedBlockData(0) 348 349 vm.On("NewExecutor", mock.Anything, mock.Anything, mock.Anything). 350 Return(noOpExecutor{}). 351 Once() // just system chunk 352 353 snapshot := storehouse.NewExecutingBlockSnapshot( 354 snapshot.MapStorageSnapshot{}, 355 unittest.StateCommitmentFixture(), 356 ) 357 358 committer.On("CommitView", mock.Anything, mock.Anything). 359 Return(nil, nil, nil, snapshot, nil). 360 Once() // just system chunk 361 362 result, err := exe.ExecuteBlock( 363 context.Background(), 364 unittest.IdentifierFixture(), 365 block, 366 nil, 367 derivedBlockData) 368 assert.NoError(t, err) 369 assert.Len(t, result.AllExecutionSnapshots(), 1) 370 assert.Len(t, result.AllTransactionResults(), 1) 371 assert.Len(t, result.ChunkExecutionDatas, 1) 372 373 assertEventHashesMatch(t, 1, result) 374 375 vm.AssertExpectations(t) 376 }) 377 378 t.Run("system chunk transaction should not fail", func(t *testing.T) { 379 380 // include all fees. System chunk should ignore them 381 contextOptions := []fvm.Option{ 382 fvm.WithTransactionFeesEnabled(true), 383 fvm.WithAccountStorageLimit(true), 384 fvm.WithBlocks(&environment.NoopBlockFinder{}), 385 } 386 // set 0 clusters to pass n_collectors >= n_clusters check 387 epochConfig := epochs.DefaultEpochConfig() 388 epochConfig.NumCollectorClusters = 0 389 bootstrapOptions := []fvm.BootstrapProcedureOption{ 390 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 391 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 392 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 393 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 394 fvm.WithEpochConfig(epochConfig), 395 } 396 397 chain := flow.Localnet.Chain() 398 vm := fvm.NewVirtualMachine() 399 derivedBlockData := derived.NewEmptyDerivedBlockData(0) 400 baseOpts := []fvm.Option{ 401 fvm.WithChain(chain), 402 fvm.WithDerivedBlockData(derivedBlockData), 403 } 404 405 opts := append(baseOpts, contextOptions...) 406 ctx := fvm.NewContext(opts...) 407 snapshotTree := snapshot.NewSnapshotTree(nil) 408 409 baseBootstrapOpts := []fvm.BootstrapProcedureOption{ 410 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 411 } 412 bootstrapOpts := append(baseBootstrapOpts, bootstrapOptions...) 413 executionSnapshot, _, err := vm.Run( 414 ctx, 415 fvm.Bootstrap(unittest.ServiceAccountPublicKey, bootstrapOpts...), 416 snapshotTree) 417 require.NoError(t, err) 418 419 snapshotTree = snapshotTree.Append(executionSnapshot) 420 421 comm := new(computermock.ViewCommitter) 422 423 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 424 trackerStorage := mocktracker.NewMockStorage() 425 426 prov := provider.NewProvider( 427 zerolog.Nop(), 428 metrics.NewNoopCollector(), 429 execution_data.DefaultSerializer, 430 bservice, 431 trackerStorage, 432 ) 433 434 exe, err := computer.NewBlockComputer( 435 vm, 436 ctx, 437 metrics.NewNoopCollector(), 438 trace.NewNoopTracer(), 439 zerolog.Nop(), 440 comm, 441 me, 442 prov, 443 nil, 444 testutil.ProtocolStateWithSourceFixture(nil), 445 testMaxConcurrency) 446 require.NoError(t, err) 447 448 // create an empty block 449 block := generateBlock(0, 0, rag) 450 451 snapshot := storehouse.NewExecutingBlockSnapshot( 452 snapshot.MapStorageSnapshot{}, 453 unittest.StateCommitmentFixture(), 454 ) 455 456 comm.On("CommitView", mock.Anything, mock.Anything). 457 Return(nil, nil, nil, snapshot, nil). 458 Once() // just system chunk 459 460 result, err := exe.ExecuteBlock( 461 context.Background(), 462 unittest.IdentifierFixture(), 463 block, 464 snapshotTree, 465 derivedBlockData.NewChildDerivedBlockData()) 466 assert.NoError(t, err) 467 assert.Len(t, result.AllExecutionSnapshots(), 1) 468 assert.Len(t, result.AllTransactionResults(), 1) 469 assert.Len(t, result.ChunkExecutionDatas, 1) 470 471 assert.Empty(t, result.AllTransactionResults()[0].ErrorMessage) 472 }) 473 474 t.Run("multiple collections", func(t *testing.T) { 475 execCtx := fvm.NewContext() 476 477 committer := new(computermock.ViewCommitter) 478 479 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 480 trackerStorage := mocktracker.NewMockStorage() 481 482 prov := provider.NewProvider( 483 zerolog.Nop(), 484 metrics.NewNoopCollector(), 485 execution_data.DefaultSerializer, 486 bservice, 487 trackerStorage, 488 ) 489 490 eventsPerTransaction := 2 491 vm := &testVM{ 492 t: t, 493 eventsPerTransaction: eventsPerTransaction, 494 err: fvmErrors.NewInvalidAddressErrorf( 495 flow.EmptyAddress, 496 "no payer address provided"), 497 } 498 499 exe, err := computer.NewBlockComputer( 500 vm, 501 execCtx, 502 metrics.NewNoopCollector(), 503 trace.NewNoopTracer(), 504 zerolog.Nop(), 505 committer, 506 me, 507 prov, 508 nil, 509 testutil.ProtocolStateWithSourceFixture(nil), 510 testMaxConcurrency) 511 require.NoError(t, err) 512 513 collectionCount := 2 514 transactionsPerCollection := 2 515 eventsPerCollection := eventsPerTransaction * transactionsPerCollection 516 totalTransactionCount := (collectionCount * transactionsPerCollection) + 1 // +1 for system chunk 517 // totalEventCount := eventsPerTransaction * totalTransactionCount 518 519 // create a block with 2 collections with 2 transactions each 520 block := generateBlock(collectionCount, transactionsPerCollection, rag) 521 derivedBlockData := derived.NewEmptyDerivedBlockData(0) 522 523 snapshot := storehouse.NewExecutingBlockSnapshot( 524 snapshot.MapStorageSnapshot{}, 525 unittest.StateCommitmentFixture(), 526 ) 527 528 committer.On("CommitView", mock.Anything, mock.Anything). 529 Return(nil, nil, nil, snapshot, nil). 530 Times(collectionCount + 1) 531 532 result, err := exe.ExecuteBlock( 533 context.Background(), 534 unittest.IdentifierFixture(), 535 block, 536 nil, 537 derivedBlockData) 538 assert.NoError(t, err) 539 540 // chunk count should match collection count 541 assert.Equal(t, result.BlockExecutionResult.Size(), collectionCount+1) // system chunk 542 543 // all events should have been collected 544 for i := 0; i < collectionCount; i++ { 545 events := result.CollectionExecutionResultAt(i).Events() 546 assert.Len(t, events, eventsPerCollection) 547 } 548 549 // system chunk 550 assert.Len(t, result.CollectionExecutionResultAt(collectionCount).Events(), eventsPerTransaction) 551 552 events := result.AllEvents() 553 554 // events should have been indexed by transaction and event 555 k := 0 556 for expectedTxIndex := 0; expectedTxIndex < totalTransactionCount; expectedTxIndex++ { 557 for expectedEventIndex := 0; expectedEventIndex < eventsPerTransaction; expectedEventIndex++ { 558 e := events[k] 559 assert.EqualValues(t, expectedEventIndex, int(e.EventIndex)) 560 assert.EqualValues(t, expectedTxIndex, e.TransactionIndex) 561 k++ 562 } 563 } 564 565 expectedResults := make([]flow.TransactionResult, 0) 566 for _, c := range block.CompleteCollections { 567 for _, t := range c.Transactions { 568 txResult := flow.TransactionResult{ 569 TransactionID: t.ID(), 570 ErrorMessage: fvmErrors.NewInvalidAddressErrorf( 571 flow.EmptyAddress, 572 "no payer address provided").Error(), 573 } 574 expectedResults = append(expectedResults, txResult) 575 } 576 } 577 txResults := result.AllTransactionResults() 578 assert.ElementsMatch(t, expectedResults, txResults[0:len(txResults)-1]) // strip system chunk 579 580 assertEventHashesMatch(t, collectionCount+1, result) 581 582 assert.GreaterOrEqual(t, vm.CallCount(), totalTransactionCount) 583 // if every transaction is retried once, then the call count should be 584 // (1+totalTransactionCount) /2 * totalTransactionCount 585 assert.LessOrEqual(t, vm.CallCount(), (1+totalTransactionCount)/2*totalTransactionCount) 586 }) 587 588 t.Run( 589 "service events are emitted", func(t *testing.T) { 590 execCtx := fvm.NewContext( 591 fvm.WithServiceEventCollectionEnabled(), 592 fvm.WithAuthorizationChecksEnabled(false), 593 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 594 ) 595 596 collectionCount := 2 597 transactionsPerCollection := 2 598 599 // create a block with 2 collections with 2 transactions each 600 block := generateBlock(collectionCount, transactionsPerCollection, rag) 601 602 serviceEvents := systemcontracts.ServiceEventsForChain(execCtx.Chain.ChainID()) 603 604 randomSource := unittest.EpochSetupRandomSourceFixture() 605 payload, err := ccf.Decode(nil, unittest.EpochSetupFixtureCCF(randomSource)) 606 require.NoError(t, err) 607 608 serviceEventA, ok := payload.(cadence.Event) 609 require.True(t, ok) 610 611 serviceEventA.EventType.Location = common.AddressLocation{ 612 Address: common.Address(serviceEvents.EpochSetup.Address), 613 } 614 serviceEventA.EventType.QualifiedIdentifier = serviceEvents.EpochSetup.QualifiedIdentifier() 615 616 payload, err = ccf.Decode(nil, unittest.EpochCommitFixtureCCF) 617 require.NoError(t, err) 618 619 serviceEventB, ok := payload.(cadence.Event) 620 require.True(t, ok) 621 622 serviceEventB.EventType.Location = common.AddressLocation{ 623 Address: common.Address(serviceEvents.EpochCommit.Address), 624 } 625 serviceEventB.EventType.QualifiedIdentifier = serviceEvents.EpochCommit.QualifiedIdentifier() 626 627 payload, err = ccf.Decode(nil, unittest.VersionBeaconFixtureCCF) 628 require.NoError(t, err) 629 630 serviceEventC, ok := payload.(cadence.Event) 631 require.True(t, ok) 632 633 serviceEventC.EventType.Location = common.AddressLocation{ 634 Address: common.Address(serviceEvents.VersionBeacon.Address), 635 } 636 serviceEventC.EventType.QualifiedIdentifier = serviceEvents.VersionBeacon.QualifiedIdentifier() 637 638 transactions := []*flow.TransactionBody{} 639 for _, col := range block.Collections() { 640 transactions = append(transactions, col.Transactions...) 641 } 642 643 // events to emit for each iteration/transaction 644 events := map[common.Location][]cadence.Event{ 645 common.TransactionLocation(transactions[0].ID()): nil, 646 common.TransactionLocation(transactions[1].ID()): { 647 serviceEventA, 648 { 649 EventType: &cadence.EventType{ 650 Location: stdlib.FlowLocation{}, 651 QualifiedIdentifier: "what.ever", 652 }, 653 }, 654 }, 655 common.TransactionLocation(transactions[2].ID()): { 656 { 657 EventType: &cadence.EventType{ 658 Location: stdlib.FlowLocation{}, 659 QualifiedIdentifier: "what.ever", 660 }, 661 }, 662 }, 663 common.TransactionLocation(transactions[3].ID()): nil, 664 } 665 666 systemTransactionEvents := []cadence.Event{ 667 serviceEventB, 668 serviceEventC, 669 } 670 671 emittingRuntime := &testRuntime{ 672 executeTransaction: func( 673 script runtime.Script, 674 context runtime.Context, 675 ) error { 676 scriptEvents, ok := events[context.Location] 677 if !ok { 678 scriptEvents = systemTransactionEvents 679 } 680 681 for _, e := range scriptEvents { 682 err := context.Interface.EmitEvent(e) 683 if err != nil { 684 return err 685 } 686 } 687 return nil 688 }, 689 readStored: func( 690 address common.Address, 691 path cadence.Path, 692 r runtime.Context, 693 ) (cadence.Value, error) { 694 return nil, nil 695 }, 696 } 697 698 execCtx = fvm.NewContextFromParent( 699 execCtx, 700 fvm.WithReusableCadenceRuntimePool( 701 reusableRuntime.NewCustomReusableCadenceRuntimePool( 702 0, 703 runtime.Config{}, 704 func(_ runtime.Config) runtime.Runtime { 705 return emittingRuntime 706 }, 707 ), 708 ), 709 ) 710 711 vm := fvm.NewVirtualMachine() 712 713 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 714 trackerStorage := mocktracker.NewMockStorage() 715 716 prov := provider.NewProvider( 717 zerolog.Nop(), 718 metrics.NewNoopCollector(), 719 execution_data.DefaultSerializer, 720 bservice, 721 trackerStorage, 722 ) 723 724 exe, err := computer.NewBlockComputer( 725 vm, 726 execCtx, 727 metrics.NewNoopCollector(), 728 trace.NewNoopTracer(), 729 zerolog.Nop(), 730 committer.NewNoopViewCommitter(), 731 me, 732 prov, 733 nil, 734 testutil.ProtocolStateWithSourceFixture(nil), 735 testMaxConcurrency) 736 require.NoError(t, err) 737 738 result, err := exe.ExecuteBlock( 739 context.Background(), 740 unittest.IdentifierFixture(), 741 block, 742 nil, 743 derived.NewEmptyDerivedBlockData(0), 744 ) 745 require.NoError(t, err) 746 747 // make sure event index sequence are valid 748 for i := 0; i < result.BlockExecutionResult.Size(); i++ { 749 collectionResult := result.CollectionExecutionResultAt(i) 750 unittest.EnsureEventsIndexSeq(t, collectionResult.Events(), execCtx.Chain.ChainID()) 751 } 752 753 sEvents := result.AllServiceEvents() // all events should have been collected 754 require.Len(t, sEvents, 3) 755 756 // events are ordered 757 require.Equal( 758 t, 759 serviceEventA.EventType.ID(), 760 string(sEvents[0].Type), 761 ) 762 require.Equal( 763 t, 764 serviceEventB.EventType.ID(), 765 string(sEvents[1].Type), 766 ) 767 768 require.Equal( 769 t, 770 serviceEventC.EventType.ID(), 771 string(sEvents[2].Type), 772 ) 773 774 assertEventHashesMatch(t, collectionCount+1, result) 775 }, 776 ) 777 778 t.Run("succeeding transactions store programs", func(t *testing.T) { 779 780 execCtx := fvm.NewContext() 781 782 address := common.Address{0x1} 783 contractLocation := common.AddressLocation{ 784 Address: address, 785 Name: "Test", 786 } 787 788 contractProgram := &interpreter.Program{} 789 790 rt := &testRuntime{ 791 executeTransaction: func(script runtime.Script, r runtime.Context) error { 792 793 _, err := r.Interface.GetOrLoadProgram( 794 contractLocation, 795 func() (*interpreter.Program, error) { 796 return contractProgram, nil 797 }, 798 ) 799 require.NoError(t, err) 800 801 return nil 802 }, 803 readStored: func( 804 address common.Address, 805 path cadence.Path, 806 r runtime.Context, 807 ) (cadence.Value, error) { 808 return nil, nil 809 }, 810 } 811 812 execCtx = fvm.NewContextFromParent( 813 execCtx, 814 fvm.WithReusableCadenceRuntimePool( 815 reusableRuntime.NewCustomReusableCadenceRuntimePool( 816 0, 817 runtime.Config{}, 818 func(_ runtime.Config) runtime.Runtime { 819 return rt 820 }))) 821 822 vm := fvm.NewVirtualMachine() 823 824 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 825 trackerStorage := mocktracker.NewMockStorage() 826 827 prov := provider.NewProvider( 828 zerolog.Nop(), 829 metrics.NewNoopCollector(), 830 execution_data.DefaultSerializer, 831 bservice, 832 trackerStorage, 833 ) 834 835 exe, err := computer.NewBlockComputer( 836 vm, 837 execCtx, 838 metrics.NewNoopCollector(), 839 trace.NewNoopTracer(), 840 zerolog.Nop(), 841 committer.NewNoopViewCommitter(), 842 me, 843 prov, 844 nil, 845 testutil.ProtocolStateWithSourceFixture(nil), 846 testMaxConcurrency) 847 require.NoError(t, err) 848 849 const collectionCount = 2 850 const transactionCount = 2 851 block := generateBlock(collectionCount, transactionCount, rag) 852 853 key := flow.AccountStatusRegisterID( 854 flow.BytesToAddress(address.Bytes())) 855 value := environment.NewAccountStatus().ToBytes() 856 857 result, err := exe.ExecuteBlock( 858 context.Background(), 859 unittest.IdentifierFixture(), 860 block, 861 snapshot.MapStorageSnapshot{key: value}, 862 derived.NewEmptyDerivedBlockData(0)) 863 assert.NoError(t, err) 864 assert.Len(t, result.AllExecutionSnapshots(), collectionCount+1) // +1 system chunk 865 }) 866 867 t.Run("failing transactions do not store programs", func(t *testing.T) { 868 execCtx := fvm.NewContext( 869 fvm.WithAuthorizationChecksEnabled(false), 870 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 871 ) 872 873 address := common.Address{0x1} 874 875 contractLocation := common.AddressLocation{ 876 Address: address, 877 Name: "Test", 878 } 879 880 contractProgram := &interpreter.Program{} 881 882 const collectionCount = 2 883 const transactionCount = 2 884 block := generateBlock(collectionCount, transactionCount, rag) 885 886 normalTransactions := map[common.Location]struct{}{} 887 for _, col := range block.Collections() { 888 for _, txn := range col.Transactions { 889 loc := common.TransactionLocation(txn.ID()) 890 normalTransactions[loc] = struct{}{} 891 } 892 } 893 894 rt := &testRuntime{ 895 executeTransaction: func(script runtime.Script, r runtime.Context) error { 896 897 // NOTE: set a program and revert all transactions but the 898 // system chunk transaction 899 _, err := r.Interface.GetOrLoadProgram( 900 contractLocation, 901 func() (*interpreter.Program, error) { 902 return contractProgram, nil 903 }, 904 ) 905 require.NoError(t, err) 906 907 _, ok := normalTransactions[r.Location] 908 if ok { 909 return runtime.Error{ 910 Err: fmt.Errorf("TX reverted"), 911 } 912 } 913 914 return nil 915 }, 916 readStored: func( 917 address common.Address, 918 path cadence.Path, 919 r runtime.Context, 920 ) (cadence.Value, error) { 921 return nil, nil 922 }, 923 } 924 925 execCtx = fvm.NewContextFromParent( 926 execCtx, 927 fvm.WithReusableCadenceRuntimePool( 928 reusableRuntime.NewCustomReusableCadenceRuntimePool( 929 0, 930 runtime.Config{}, 931 func(_ runtime.Config) runtime.Runtime { 932 return rt 933 }))) 934 935 vm := fvm.NewVirtualMachine() 936 937 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 938 trackerStorage := mocktracker.NewMockStorage() 939 940 prov := provider.NewProvider( 941 zerolog.Nop(), 942 metrics.NewNoopCollector(), 943 execution_data.DefaultSerializer, 944 bservice, 945 trackerStorage, 946 ) 947 948 exe, err := computer.NewBlockComputer( 949 vm, 950 execCtx, 951 metrics.NewNoopCollector(), 952 trace.NewNoopTracer(), 953 zerolog.Nop(), 954 committer.NewNoopViewCommitter(), 955 me, 956 prov, 957 nil, 958 testutil.ProtocolStateWithSourceFixture(nil), 959 testMaxConcurrency) 960 require.NoError(t, err) 961 962 key := flow.AccountStatusRegisterID( 963 flow.BytesToAddress(address.Bytes())) 964 value := environment.NewAccountStatus().ToBytes() 965 966 result, err := exe.ExecuteBlock( 967 context.Background(), 968 unittest.IdentifierFixture(), 969 block, 970 snapshot.MapStorageSnapshot{key: value}, 971 derived.NewEmptyDerivedBlockData(0)) 972 require.NoError(t, err) 973 assert.Len(t, result.AllExecutionSnapshots(), collectionCount+1) // +1 system chunk 974 }) 975 976 t.Run("internal error", func(t *testing.T) { 977 execCtx := fvm.NewContext() 978 979 committer := new(computermock.ViewCommitter) 980 981 bservice := requesterunit.MockBlobService( 982 blockstore.NewBlockstore( 983 dssync.MutexWrap(datastore.NewMapDatastore()))) 984 trackerStorage := mocktracker.NewMockStorage() 985 986 prov := provider.NewProvider( 987 zerolog.Nop(), 988 metrics.NewNoopCollector(), 989 execution_data.DefaultSerializer, 990 bservice, 991 trackerStorage) 992 993 exe, err := computer.NewBlockComputer( 994 errorVM{errorAt: 5}, 995 execCtx, 996 metrics.NewNoopCollector(), 997 trace.NewNoopTracer(), 998 zerolog.Nop(), 999 committer, 1000 me, 1001 prov, 1002 nil, 1003 testutil.ProtocolStateWithSourceFixture(nil), 1004 testMaxConcurrency) 1005 require.NoError(t, err) 1006 1007 collectionCount := 5 1008 transactionsPerCollection := 3 1009 block := generateBlock(collectionCount, transactionsPerCollection, rag) 1010 1011 snapshot := storehouse.NewExecutingBlockSnapshot( 1012 snapshot.MapStorageSnapshot{}, 1013 unittest.StateCommitmentFixture(), 1014 ) 1015 1016 committer.On("CommitView", mock.Anything, mock.Anything). 1017 Return(nil, nil, nil, snapshot, nil). 1018 Times(collectionCount + 1) 1019 1020 _, err = exe.ExecuteBlock( 1021 context.Background(), 1022 unittest.IdentifierFixture(), 1023 block, 1024 nil, 1025 derived.NewEmptyDerivedBlockData(0)) 1026 assert.ErrorContains(t, err, "boom - internal error") 1027 }) 1028 1029 } 1030 1031 func assertEventHashesMatch( 1032 t *testing.T, 1033 expectedNoOfChunks int, 1034 result *execution.ComputationResult, 1035 ) { 1036 execResSize := result.BlockExecutionResult.Size() 1037 attestResSize := result.BlockAttestationResult.Size() 1038 require.Equal(t, execResSize, expectedNoOfChunks) 1039 require.Equal(t, execResSize, attestResSize) 1040 1041 for i := 0; i < expectedNoOfChunks; i++ { 1042 events := result.CollectionExecutionResultAt(i).Events() 1043 calculatedHash, err := flow.EventsMerkleRootHash(events) 1044 require.NoError(t, err) 1045 require.Equal(t, calculatedHash, result.CollectionAttestationResultAt(i).EventCommitment()) 1046 } 1047 } 1048 1049 type testTransactionExecutor struct { 1050 executeTransaction func(runtime.Script, runtime.Context) error 1051 1052 script runtime.Script 1053 context runtime.Context 1054 } 1055 1056 func (executor *testTransactionExecutor) Preprocess() error { 1057 // Do nothing. 1058 return nil 1059 } 1060 1061 func (executor *testTransactionExecutor) Execute() error { 1062 return executor.executeTransaction(executor.script, executor.context) 1063 } 1064 1065 func (executor *testTransactionExecutor) Result() (cadence.Value, error) { 1066 panic("Result not expected") 1067 } 1068 1069 type testRuntime struct { 1070 executeScript func(runtime.Script, runtime.Context) (cadence.Value, error) 1071 executeTransaction func(runtime.Script, runtime.Context) error 1072 readStored func(common.Address, cadence.Path, runtime.Context) ( 1073 cadence.Value, 1074 error, 1075 ) 1076 } 1077 1078 var _ runtime.Runtime = &testRuntime{} 1079 1080 func (e *testRuntime) Config() runtime.Config { 1081 panic("Config not expected") 1082 } 1083 1084 func (e *testRuntime) NewScriptExecutor( 1085 script runtime.Script, 1086 c runtime.Context, 1087 ) runtime.Executor { 1088 panic("NewScriptExecutor not expected") 1089 } 1090 1091 func (e *testRuntime) NewTransactionExecutor( 1092 script runtime.Script, 1093 c runtime.Context, 1094 ) runtime.Executor { 1095 return &testTransactionExecutor{ 1096 executeTransaction: e.executeTransaction, 1097 script: script, 1098 context: c, 1099 } 1100 } 1101 1102 func (e *testRuntime) NewContractFunctionExecutor( 1103 contractLocation common.AddressLocation, 1104 functionName string, 1105 arguments []cadence.Value, 1106 argumentTypes []sema.Type, 1107 context runtime.Context, 1108 ) runtime.Executor { 1109 panic("NewContractFunctionExecutor not expected") 1110 } 1111 1112 func (e *testRuntime) SetInvalidatedResourceValidationEnabled(_ bool) { 1113 panic("SetInvalidatedResourceValidationEnabled not expected") 1114 } 1115 1116 func (e *testRuntime) SetTracingEnabled(_ bool) { 1117 panic("SetTracingEnabled not expected") 1118 } 1119 1120 func (e *testRuntime) SetResourceOwnerChangeHandlerEnabled(_ bool) { 1121 panic("SetResourceOwnerChangeHandlerEnabled not expected") 1122 } 1123 1124 func (e *testRuntime) InvokeContractFunction( 1125 _ common.AddressLocation, 1126 _ string, 1127 _ []cadence.Value, 1128 _ []sema.Type, 1129 _ runtime.Context, 1130 ) (cadence.Value, error) { 1131 panic("InvokeContractFunction not expected") 1132 } 1133 1134 func (e *testRuntime) ExecuteScript( 1135 script runtime.Script, 1136 context runtime.Context, 1137 ) (cadence.Value, error) { 1138 return e.executeScript(script, context) 1139 } 1140 1141 func (e *testRuntime) ExecuteTransaction( 1142 script runtime.Script, 1143 context runtime.Context, 1144 ) error { 1145 return e.executeTransaction(script, context) 1146 } 1147 1148 func (*testRuntime) ParseAndCheckProgram( 1149 _ []byte, 1150 _ runtime.Context, 1151 ) (*interpreter.Program, error) { 1152 panic("ParseAndCheckProgram not expected") 1153 } 1154 1155 func (*testRuntime) SetCoverageReport(_ *runtime.CoverageReport) { 1156 panic("SetCoverageReport not expected") 1157 } 1158 1159 func (*testRuntime) SetContractUpdateValidationEnabled(_ bool) { 1160 panic("SetContractUpdateValidationEnabled not expected") 1161 } 1162 1163 func (*testRuntime) SetAtreeValidationEnabled(_ bool) { 1164 panic("SetAtreeValidationEnabled not expected") 1165 } 1166 1167 func (e *testRuntime) ReadStored( 1168 a common.Address, 1169 p cadence.Path, 1170 c runtime.Context, 1171 ) (cadence.Value, error) { 1172 return e.readStored(a, p, c) 1173 } 1174 1175 func (*testRuntime) ReadLinked( 1176 _ common.Address, 1177 _ cadence.Path, 1178 _ runtime.Context, 1179 ) (cadence.Value, error) { 1180 panic("ReadLinked not expected") 1181 } 1182 1183 func (*testRuntime) SetDebugger(_ *interpreter.Debugger) { 1184 panic("SetDebugger not expected") 1185 } 1186 1187 type RandomAddressGenerator struct{} 1188 1189 func (r *RandomAddressGenerator) NextAddress() (flow.Address, error) { 1190 return flow.HexToAddress(fmt.Sprintf("0%d", rand.Intn(1000))), nil 1191 } 1192 1193 func (r *RandomAddressGenerator) CurrentAddress() flow.Address { 1194 return flow.HexToAddress(fmt.Sprintf("0%d", rand.Intn(1000))) 1195 } 1196 1197 func (r *RandomAddressGenerator) Bytes() []byte { 1198 panic("not implemented") 1199 } 1200 1201 func (r *RandomAddressGenerator) AddressCount() uint64 { 1202 panic("not implemented") 1203 } 1204 1205 func (testRuntime) Storage(runtime.Context) ( 1206 *runtime.Storage, 1207 *interpreter.Interpreter, 1208 error, 1209 ) { 1210 panic("Storage not expected") 1211 } 1212 1213 type FixedAddressGenerator struct { 1214 Address flow.Address 1215 } 1216 1217 func (f *FixedAddressGenerator) NextAddress() (flow.Address, error) { 1218 return f.Address, nil 1219 } 1220 1221 func (f *FixedAddressGenerator) CurrentAddress() flow.Address { 1222 return f.Address 1223 } 1224 1225 func (f *FixedAddressGenerator) Bytes() []byte { 1226 panic("not implemented") 1227 } 1228 1229 func (f *FixedAddressGenerator) AddressCount() uint64 { 1230 panic("not implemented") 1231 } 1232 1233 func Test_ExecutingSystemCollection(t *testing.T) { 1234 1235 execCtx := fvm.NewContext( 1236 fvm.WithChain(flow.Localnet.Chain()), 1237 fvm.WithBlocks(&environment.NoopBlockFinder{}), 1238 ) 1239 1240 vm := fvm.NewVirtualMachine() 1241 1242 rag := &RandomAddressGenerator{} 1243 1244 ledger := testutil.RootBootstrappedLedger(vm, execCtx) 1245 1246 committer := new(computermock.ViewCommitter) 1247 snapshot := storehouse.NewExecutingBlockSnapshot( 1248 snapshot.MapStorageSnapshot{}, 1249 unittest.StateCommitmentFixture(), 1250 ) 1251 1252 committer.On("CommitView", mock.Anything, mock.Anything). 1253 Return(nil, nil, nil, snapshot, nil). 1254 Times(1) // only system chunk 1255 1256 noopCollector := metrics.NewNoopCollector() 1257 1258 expectedNumberOfEvents := 3 1259 expectedEventSize := 1493 1260 // bootstrapping does not cache programs 1261 expectedCachedPrograms := 0 1262 1263 metrics := new(modulemock.ExecutionMetrics) 1264 metrics.On("ExecutionBlockExecuted", 1265 mock.Anything, // duration 1266 mock.Anything). // stats 1267 Return(nil). 1268 Times(1) 1269 1270 metrics.On("ExecutionCollectionExecuted", 1271 mock.Anything, // duration 1272 mock.Anything). // stats 1273 Return(nil). 1274 Times(1) // system collection 1275 1276 metrics.On("ExecutionTransactionExecuted", 1277 mock.Anything, // duration 1278 mock.Anything, // conflict retry count 1279 mock.Anything, // computation used 1280 mock.Anything, // memory used 1281 expectedNumberOfEvents, 1282 expectedEventSize, 1283 false). 1284 Return(nil). 1285 Times(1) // system chunk tx 1286 1287 metrics.On( 1288 "ExecutionChunkDataPackGenerated", 1289 mock.Anything, 1290 mock.Anything). 1291 Return(nil). 1292 Times(1) // system collection 1293 1294 metrics.On( 1295 "ExecutionBlockCachedPrograms", 1296 expectedCachedPrograms). 1297 Return(nil). 1298 Times(1) // block 1299 1300 metrics.On( 1301 "ExecutionBlockExecutionEffortVectorComponent", 1302 mock.Anything, 1303 mock.Anything). 1304 Return(nil) 1305 1306 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 1307 trackerStorage := mocktracker.NewMockStorage() 1308 1309 prov := provider.NewProvider( 1310 zerolog.Nop(), 1311 noopCollector, 1312 execution_data.DefaultSerializer, 1313 bservice, 1314 trackerStorage, 1315 ) 1316 1317 me := new(modulemock.Local) 1318 me.On("NodeID").Return(unittest.IdentifierFixture()) 1319 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 1320 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 1321 Return(nil, nil) 1322 1323 constRandomSource := make([]byte, 32) 1324 1325 exe, err := computer.NewBlockComputer( 1326 vm, 1327 execCtx, 1328 metrics, 1329 trace.NewNoopTracer(), 1330 zerolog.Nop(), 1331 committer, 1332 me, 1333 prov, 1334 nil, 1335 testutil.ProtocolStateWithSourceFixture(constRandomSource), 1336 testMaxConcurrency) 1337 require.NoError(t, err) 1338 1339 // create empty block, it will have system collection attached while executing 1340 block := generateBlock(0, 0, rag) 1341 1342 result, err := exe.ExecuteBlock( 1343 context.Background(), 1344 unittest.IdentifierFixture(), 1345 block, 1346 ledger, 1347 derived.NewEmptyDerivedBlockData(0)) 1348 assert.NoError(t, err) 1349 assert.Len(t, result.AllExecutionSnapshots(), 1) // +1 system chunk 1350 assert.Len(t, result.AllTransactionResults(), 1) 1351 1352 assert.Empty(t, result.AllTransactionResults()[0].ErrorMessage) 1353 1354 committer.AssertExpectations(t) 1355 } 1356 1357 func generateBlock( 1358 collectionCount, transactionCount int, 1359 addressGenerator flow.AddressGenerator, 1360 ) *entity.ExecutableBlock { 1361 return generateBlockWithVisitor(collectionCount, transactionCount, addressGenerator, nil) 1362 } 1363 1364 func generateBlockWithVisitor( 1365 collectionCount, transactionCount int, 1366 addressGenerator flow.AddressGenerator, 1367 visitor func(body *flow.TransactionBody), 1368 ) *entity.ExecutableBlock { 1369 collections := make([]*entity.CompleteCollection, collectionCount) 1370 guarantees := make([]*flow.CollectionGuarantee, collectionCount) 1371 completeCollections := make(map[flow.Identifier]*entity.CompleteCollection) 1372 1373 for i := 0; i < collectionCount; i++ { 1374 collection := generateCollection(transactionCount, addressGenerator, visitor) 1375 collections[i] = collection 1376 guarantees[i] = collection.Guarantee 1377 completeCollections[collection.Guarantee.ID()] = collection 1378 } 1379 1380 block := flow.Block{ 1381 Header: &flow.Header{ 1382 Timestamp: flow.GenesisTime, 1383 Height: 42, 1384 View: 42, 1385 }, 1386 Payload: &flow.Payload{ 1387 Guarantees: guarantees, 1388 }, 1389 } 1390 1391 return &entity.ExecutableBlock{ 1392 Block: &block, 1393 CompleteCollections: completeCollections, 1394 StartState: unittest.StateCommitmentPointerFixture(), 1395 } 1396 } 1397 1398 func generateCollection( 1399 transactionCount int, 1400 addressGenerator flow.AddressGenerator, 1401 visitor func(body *flow.TransactionBody), 1402 ) *entity.CompleteCollection { 1403 transactions := make([]*flow.TransactionBody, transactionCount) 1404 1405 for i := 0; i < transactionCount; i++ { 1406 nextAddress, err := addressGenerator.NextAddress() 1407 if err != nil { 1408 panic(fmt.Errorf("cannot generate next address in test: %w", err)) 1409 } 1410 txBody := &flow.TransactionBody{ 1411 Payer: nextAddress, // a unique payer for each tx to generate a unique id 1412 Script: []byte("transaction { execute {} }"), 1413 } 1414 if visitor != nil { 1415 visitor(txBody) 1416 } 1417 transactions[i] = txBody 1418 } 1419 1420 collection := flow.Collection{Transactions: transactions} 1421 1422 guarantee := &flow.CollectionGuarantee{CollectionID: collection.ID()} 1423 1424 return &entity.CompleteCollection{ 1425 Guarantee: guarantee, 1426 Transactions: transactions, 1427 } 1428 } 1429 1430 type noOpExecutor struct{} 1431 1432 func (noOpExecutor) Cleanup() {} 1433 1434 func (noOpExecutor) Preprocess() error { 1435 return nil 1436 } 1437 1438 func (noOpExecutor) Execute() error { 1439 return nil 1440 } 1441 1442 func (noOpExecutor) Output() fvm.ProcedureOutput { 1443 return fvm.ProcedureOutput{} 1444 } 1445 1446 type testVM struct { 1447 t *testing.T 1448 eventsPerTransaction int 1449 1450 callCount int32 // atomic variable 1451 err fvmErrors.CodedError 1452 } 1453 1454 type testExecutor struct { 1455 *testVM 1456 1457 ctx fvm.Context 1458 proc fvm.Procedure 1459 txnState storage.TransactionPreparer 1460 } 1461 1462 func (testExecutor) Cleanup() { 1463 } 1464 1465 func (testExecutor) Preprocess() error { 1466 return nil 1467 } 1468 1469 func (executor *testExecutor) Execute() error { 1470 atomic.AddInt32(&executor.callCount, 1) 1471 1472 getSetAProgram(executor.t, executor.txnState) 1473 1474 return nil 1475 } 1476 1477 func (executor *testExecutor) Output() fvm.ProcedureOutput { 1478 txn := executor.proc.(*fvm.TransactionProcedure) 1479 1480 return fvm.ProcedureOutput{ 1481 Events: generateEvents(executor.eventsPerTransaction, txn.TxIndex), 1482 Err: executor.err, 1483 } 1484 } 1485 1486 func (vm *testVM) NewExecutor( 1487 ctx fvm.Context, 1488 proc fvm.Procedure, 1489 txnState storage.TransactionPreparer, 1490 ) fvm.ProcedureExecutor { 1491 return &testExecutor{ 1492 testVM: vm, 1493 proc: proc, 1494 ctx: ctx, 1495 txnState: txnState, 1496 } 1497 } 1498 1499 func (vm *testVM) CallCount() int { 1500 return int(atomic.LoadInt32(&vm.callCount)) 1501 } 1502 1503 func (vm *testVM) Run( 1504 ctx fvm.Context, 1505 proc fvm.Procedure, 1506 storageSnapshot snapshot.StorageSnapshot, 1507 ) ( 1508 *snapshot.ExecutionSnapshot, 1509 fvm.ProcedureOutput, 1510 error, 1511 ) { 1512 database := storage.NewBlockDatabase( 1513 storageSnapshot, 1514 proc.ExecutionTime(), 1515 ctx.DerivedBlockData) 1516 1517 txn, err := database.NewTransaction( 1518 proc.ExecutionTime(), 1519 state.DefaultParameters()) 1520 require.NoError(vm.t, err) 1521 1522 executor := vm.NewExecutor(ctx, proc, txn) 1523 err = fvm.Run(executor) 1524 require.NoError(vm.t, err) 1525 1526 err = txn.Finalize() 1527 require.NoError(vm.t, err) 1528 1529 executionSnapshot, err := txn.Commit() 1530 require.NoError(vm.t, err) 1531 1532 return executionSnapshot, executor.Output(), nil 1533 } 1534 1535 func (testVM) GetAccount( 1536 _ fvm.Context, 1537 _ flow.Address, 1538 _ snapshot.StorageSnapshot, 1539 ) ( 1540 *flow.Account, 1541 error, 1542 ) { 1543 panic("not implemented") 1544 } 1545 1546 func generateEvents(eventCount int, txIndex uint32) []flow.Event { 1547 events := make([]flow.Event, eventCount) 1548 for i := 0; i < eventCount; i++ { 1549 // creating some dummy event 1550 event := flow.Event{ 1551 Type: "whatever", 1552 EventIndex: uint32(i), 1553 TransactionIndex: txIndex, 1554 } 1555 events[i] = event 1556 } 1557 return events 1558 } 1559 1560 type errorVM struct { 1561 errorAt logical.Time 1562 } 1563 1564 type errorExecutor struct { 1565 err error 1566 } 1567 1568 func (errorExecutor) Cleanup() {} 1569 1570 func (errorExecutor) Preprocess() error { 1571 return nil 1572 } 1573 1574 func (e errorExecutor) Execute() error { 1575 return e.err 1576 } 1577 1578 func (errorExecutor) Output() fvm.ProcedureOutput { 1579 return fvm.ProcedureOutput{} 1580 } 1581 1582 func (vm errorVM) NewExecutor( 1583 ctx fvm.Context, 1584 proc fvm.Procedure, 1585 txn storage.TransactionPreparer, 1586 ) fvm.ProcedureExecutor { 1587 var err error 1588 if proc.ExecutionTime() == vm.errorAt { 1589 err = fmt.Errorf("boom - internal error") 1590 } 1591 1592 return errorExecutor{err: err} 1593 } 1594 1595 func (vm errorVM) Run( 1596 ctx fvm.Context, 1597 proc fvm.Procedure, 1598 storageSnapshot snapshot.StorageSnapshot, 1599 ) ( 1600 *snapshot.ExecutionSnapshot, 1601 fvm.ProcedureOutput, 1602 error, 1603 ) { 1604 var err error 1605 if proc.ExecutionTime() == vm.errorAt { 1606 err = fmt.Errorf("boom - internal error") 1607 } 1608 return &snapshot.ExecutionSnapshot{}, fvm.ProcedureOutput{}, err 1609 } 1610 1611 func (errorVM) GetAccount( 1612 ctx fvm.Context, 1613 addr flow.Address, 1614 storageSnapshot snapshot.StorageSnapshot, 1615 ) ( 1616 *flow.Account, 1617 error, 1618 ) { 1619 panic("not implemented") 1620 } 1621 1622 func getSetAProgram( 1623 t *testing.T, 1624 txnState storage.TransactionPreparer, 1625 ) { 1626 loc := common.AddressLocation{ 1627 Name: "SomeContract", 1628 Address: common.MustBytesToAddress([]byte{0x1}), 1629 } 1630 _, err := txnState.GetOrComputeProgram( 1631 txnState, 1632 loc, 1633 &programLoader{ 1634 load: func() (*derived.Program, error) { 1635 return &derived.Program{}, nil 1636 }, 1637 }, 1638 ) 1639 require.NoError(t, err) 1640 } 1641 1642 type programLoader struct { 1643 load func() (*derived.Program, error) 1644 } 1645 1646 func (p *programLoader) Compute( 1647 _ state.NestedTransactionPreparer, 1648 _ common.AddressLocation, 1649 ) ( 1650 *derived.Program, 1651 error, 1652 ) { 1653 return p.load() 1654 }