github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/boxo/blockstore" 19 "github.com/ipfs/go-datastore" 20 dssync "github.com/ipfs/go-datastore/sync" 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.WithAuthorizationChecksEnabled(false), 592 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 593 ) 594 595 collectionCount := 2 596 transactionsPerCollection := 2 597 598 // create a block with 2 collections with 2 transactions each 599 block := generateBlock(collectionCount, transactionsPerCollection, rag) 600 601 serviceEvents := systemcontracts.ServiceEventsForChain(execCtx.Chain.ChainID()) 602 603 randomSource := unittest.EpochSetupRandomSourceFixture() 604 payload, err := ccf.Decode(nil, unittest.EpochSetupFixtureCCF(randomSource)) 605 require.NoError(t, err) 606 607 serviceEventA, ok := payload.(cadence.Event) 608 require.True(t, ok) 609 610 serviceEventA.EventType.Location = common.AddressLocation{ 611 Address: common.Address(serviceEvents.EpochSetup.Address), 612 } 613 serviceEventA.EventType.QualifiedIdentifier = serviceEvents.EpochSetup.QualifiedIdentifier() 614 615 payload, err = ccf.Decode(nil, unittest.EpochCommitFixtureCCF) 616 require.NoError(t, err) 617 618 serviceEventB, ok := payload.(cadence.Event) 619 require.True(t, ok) 620 621 serviceEventB.EventType.Location = common.AddressLocation{ 622 Address: common.Address(serviceEvents.EpochCommit.Address), 623 } 624 serviceEventB.EventType.QualifiedIdentifier = serviceEvents.EpochCommit.QualifiedIdentifier() 625 626 payload, err = ccf.Decode(nil, unittest.VersionBeaconFixtureCCF) 627 require.NoError(t, err) 628 629 serviceEventC, ok := payload.(cadence.Event) 630 require.True(t, ok) 631 632 serviceEventC.EventType.Location = common.AddressLocation{ 633 Address: common.Address(serviceEvents.VersionBeacon.Address), 634 } 635 serviceEventC.EventType.QualifiedIdentifier = serviceEvents.VersionBeacon.QualifiedIdentifier() 636 637 transactions := []*flow.TransactionBody{} 638 for _, col := range block.Collections() { 639 transactions = append(transactions, col.Transactions...) 640 } 641 642 // events to emit for each iteration/transaction 643 events := map[common.Location][]cadence.Event{ 644 common.TransactionLocation(transactions[0].ID()): nil, 645 common.TransactionLocation(transactions[1].ID()): { 646 serviceEventA, 647 { 648 EventType: &cadence.EventType{ 649 Location: stdlib.FlowLocation{}, 650 QualifiedIdentifier: "what.ever", 651 }, 652 }, 653 }, 654 common.TransactionLocation(transactions[2].ID()): { 655 { 656 EventType: &cadence.EventType{ 657 Location: stdlib.FlowLocation{}, 658 QualifiedIdentifier: "what.ever", 659 }, 660 }, 661 }, 662 common.TransactionLocation(transactions[3].ID()): nil, 663 } 664 665 systemTransactionEvents := []cadence.Event{ 666 serviceEventB, 667 serviceEventC, 668 } 669 670 emittingRuntime := &testRuntime{ 671 executeTransaction: func( 672 script runtime.Script, 673 context runtime.Context, 674 ) error { 675 scriptEvents, ok := events[context.Location] 676 if !ok { 677 scriptEvents = systemTransactionEvents 678 } 679 680 for _, e := range scriptEvents { 681 err := context.Interface.EmitEvent(e) 682 if err != nil { 683 return err 684 } 685 } 686 return nil 687 }, 688 readStored: func( 689 address common.Address, 690 path cadence.Path, 691 r runtime.Context, 692 ) (cadence.Value, error) { 693 return nil, nil 694 }, 695 } 696 697 execCtx = fvm.NewContextFromParent( 698 execCtx, 699 fvm.WithReusableCadenceRuntimePool( 700 reusableRuntime.NewCustomReusableCadenceRuntimePool( 701 0, 702 runtime.Config{}, 703 func(_ runtime.Config) runtime.Runtime { 704 return emittingRuntime 705 }, 706 ), 707 ), 708 ) 709 710 vm := fvm.NewVirtualMachine() 711 712 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 713 trackerStorage := mocktracker.NewMockStorage() 714 715 prov := provider.NewProvider( 716 zerolog.Nop(), 717 metrics.NewNoopCollector(), 718 execution_data.DefaultSerializer, 719 bservice, 720 trackerStorage, 721 ) 722 723 exe, err := computer.NewBlockComputer( 724 vm, 725 execCtx, 726 metrics.NewNoopCollector(), 727 trace.NewNoopTracer(), 728 zerolog.Nop(), 729 committer.NewNoopViewCommitter(), 730 me, 731 prov, 732 nil, 733 testutil.ProtocolStateWithSourceFixture(nil), 734 testMaxConcurrency) 735 require.NoError(t, err) 736 737 result, err := exe.ExecuteBlock( 738 context.Background(), 739 unittest.IdentifierFixture(), 740 block, 741 nil, 742 derived.NewEmptyDerivedBlockData(0), 743 ) 744 require.NoError(t, err) 745 746 // make sure event index sequence are valid 747 for i := 0; i < result.BlockExecutionResult.Size(); i++ { 748 collectionResult := result.CollectionExecutionResultAt(i) 749 unittest.EnsureEventsIndexSeq(t, collectionResult.Events(), execCtx.Chain.ChainID()) 750 } 751 752 sEvents := result.AllServiceEvents() // all events should have been collected 753 require.Len(t, sEvents, 3) 754 755 // events are ordered 756 require.Equal( 757 t, 758 serviceEventA.EventType.ID(), 759 string(sEvents[0].Type), 760 ) 761 require.Equal( 762 t, 763 serviceEventB.EventType.ID(), 764 string(sEvents[1].Type), 765 ) 766 767 require.Equal( 768 t, 769 serviceEventC.EventType.ID(), 770 string(sEvents[2].Type), 771 ) 772 773 assertEventHashesMatch(t, collectionCount+1, result) 774 }, 775 ) 776 777 t.Run("succeeding transactions store programs", func(t *testing.T) { 778 779 execCtx := fvm.NewContext() 780 781 address := common.Address{0x1} 782 contractLocation := common.AddressLocation{ 783 Address: address, 784 Name: "Test", 785 } 786 787 contractProgram := &interpreter.Program{} 788 789 rt := &testRuntime{ 790 executeTransaction: func(script runtime.Script, r runtime.Context) error { 791 792 _, err := r.Interface.GetOrLoadProgram( 793 contractLocation, 794 func() (*interpreter.Program, error) { 795 return contractProgram, nil 796 }, 797 ) 798 require.NoError(t, err) 799 800 return nil 801 }, 802 readStored: func( 803 address common.Address, 804 path cadence.Path, 805 r runtime.Context, 806 ) (cadence.Value, error) { 807 return nil, nil 808 }, 809 } 810 811 execCtx = fvm.NewContextFromParent( 812 execCtx, 813 fvm.WithReusableCadenceRuntimePool( 814 reusableRuntime.NewCustomReusableCadenceRuntimePool( 815 0, 816 runtime.Config{}, 817 func(_ runtime.Config) runtime.Runtime { 818 return rt 819 }))) 820 821 vm := fvm.NewVirtualMachine() 822 823 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 824 trackerStorage := mocktracker.NewMockStorage() 825 826 prov := provider.NewProvider( 827 zerolog.Nop(), 828 metrics.NewNoopCollector(), 829 execution_data.DefaultSerializer, 830 bservice, 831 trackerStorage, 832 ) 833 834 exe, err := computer.NewBlockComputer( 835 vm, 836 execCtx, 837 metrics.NewNoopCollector(), 838 trace.NewNoopTracer(), 839 zerolog.Nop(), 840 committer.NewNoopViewCommitter(), 841 me, 842 prov, 843 nil, 844 testutil.ProtocolStateWithSourceFixture(nil), 845 testMaxConcurrency) 846 require.NoError(t, err) 847 848 const collectionCount = 2 849 const transactionCount = 2 850 block := generateBlock(collectionCount, transactionCount, rag) 851 852 key := flow.AccountStatusRegisterID( 853 flow.BytesToAddress(address.Bytes())) 854 value := environment.NewAccountStatus().ToBytes() 855 856 result, err := exe.ExecuteBlock( 857 context.Background(), 858 unittest.IdentifierFixture(), 859 block, 860 snapshot.MapStorageSnapshot{key: value}, 861 derived.NewEmptyDerivedBlockData(0)) 862 assert.NoError(t, err) 863 assert.Len(t, result.AllExecutionSnapshots(), collectionCount+1) // +1 system chunk 864 }) 865 866 t.Run("failing transactions do not store programs", func(t *testing.T) { 867 execCtx := fvm.NewContext( 868 fvm.WithAuthorizationChecksEnabled(false), 869 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 870 ) 871 872 address := common.Address{0x1} 873 874 contractLocation := common.AddressLocation{ 875 Address: address, 876 Name: "Test", 877 } 878 879 contractProgram := &interpreter.Program{} 880 881 const collectionCount = 2 882 const transactionCount = 2 883 block := generateBlock(collectionCount, transactionCount, rag) 884 885 normalTransactions := map[common.Location]struct{}{} 886 for _, col := range block.Collections() { 887 for _, txn := range col.Transactions { 888 loc := common.TransactionLocation(txn.ID()) 889 normalTransactions[loc] = struct{}{} 890 } 891 } 892 893 rt := &testRuntime{ 894 executeTransaction: func(script runtime.Script, r runtime.Context) error { 895 896 // NOTE: set a program and revert all transactions but the 897 // system chunk transaction 898 _, err := r.Interface.GetOrLoadProgram( 899 contractLocation, 900 func() (*interpreter.Program, error) { 901 return contractProgram, nil 902 }, 903 ) 904 require.NoError(t, err) 905 906 _, ok := normalTransactions[r.Location] 907 if ok { 908 return runtime.Error{ 909 Err: fmt.Errorf("TX reverted"), 910 } 911 } 912 913 return nil 914 }, 915 readStored: func( 916 address common.Address, 917 path cadence.Path, 918 r runtime.Context, 919 ) (cadence.Value, error) { 920 return nil, nil 921 }, 922 } 923 924 execCtx = fvm.NewContextFromParent( 925 execCtx, 926 fvm.WithReusableCadenceRuntimePool( 927 reusableRuntime.NewCustomReusableCadenceRuntimePool( 928 0, 929 runtime.Config{}, 930 func(_ runtime.Config) runtime.Runtime { 931 return rt 932 }))) 933 934 vm := fvm.NewVirtualMachine() 935 936 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 937 trackerStorage := mocktracker.NewMockStorage() 938 939 prov := provider.NewProvider( 940 zerolog.Nop(), 941 metrics.NewNoopCollector(), 942 execution_data.DefaultSerializer, 943 bservice, 944 trackerStorage, 945 ) 946 947 exe, err := computer.NewBlockComputer( 948 vm, 949 execCtx, 950 metrics.NewNoopCollector(), 951 trace.NewNoopTracer(), 952 zerolog.Nop(), 953 committer.NewNoopViewCommitter(), 954 me, 955 prov, 956 nil, 957 testutil.ProtocolStateWithSourceFixture(nil), 958 testMaxConcurrency) 959 require.NoError(t, err) 960 961 key := flow.AccountStatusRegisterID( 962 flow.BytesToAddress(address.Bytes())) 963 value := environment.NewAccountStatus().ToBytes() 964 965 result, err := exe.ExecuteBlock( 966 context.Background(), 967 unittest.IdentifierFixture(), 968 block, 969 snapshot.MapStorageSnapshot{key: value}, 970 derived.NewEmptyDerivedBlockData(0)) 971 require.NoError(t, err) 972 assert.Len(t, result.AllExecutionSnapshots(), collectionCount+1) // +1 system chunk 973 }) 974 975 t.Run("internal error", func(t *testing.T) { 976 execCtx := fvm.NewContext() 977 978 committer := new(computermock.ViewCommitter) 979 980 bservice := requesterunit.MockBlobService( 981 blockstore.NewBlockstore( 982 dssync.MutexWrap(datastore.NewMapDatastore()))) 983 trackerStorage := mocktracker.NewMockStorage() 984 985 prov := provider.NewProvider( 986 zerolog.Nop(), 987 metrics.NewNoopCollector(), 988 execution_data.DefaultSerializer, 989 bservice, 990 trackerStorage) 991 992 exe, err := computer.NewBlockComputer( 993 errorVM{errorAt: 5}, 994 execCtx, 995 metrics.NewNoopCollector(), 996 trace.NewNoopTracer(), 997 zerolog.Nop(), 998 committer, 999 me, 1000 prov, 1001 nil, 1002 testutil.ProtocolStateWithSourceFixture(nil), 1003 testMaxConcurrency) 1004 require.NoError(t, err) 1005 1006 collectionCount := 5 1007 transactionsPerCollection := 3 1008 block := generateBlock(collectionCount, transactionsPerCollection, rag) 1009 1010 snapshot := storehouse.NewExecutingBlockSnapshot( 1011 snapshot.MapStorageSnapshot{}, 1012 unittest.StateCommitmentFixture(), 1013 ) 1014 1015 committer.On("CommitView", mock.Anything, mock.Anything). 1016 Return(nil, nil, nil, snapshot, nil). 1017 Times(collectionCount + 1) 1018 1019 _, err = exe.ExecuteBlock( 1020 context.Background(), 1021 unittest.IdentifierFixture(), 1022 block, 1023 nil, 1024 derived.NewEmptyDerivedBlockData(0)) 1025 assert.ErrorContains(t, err, "boom - internal error") 1026 }) 1027 1028 } 1029 1030 func assertEventHashesMatch( 1031 t *testing.T, 1032 expectedNoOfChunks int, 1033 result *execution.ComputationResult, 1034 ) { 1035 execResSize := result.BlockExecutionResult.Size() 1036 attestResSize := result.BlockAttestationResult.Size() 1037 require.Equal(t, execResSize, expectedNoOfChunks) 1038 require.Equal(t, execResSize, attestResSize) 1039 1040 for i := 0; i < expectedNoOfChunks; i++ { 1041 events := result.CollectionExecutionResultAt(i).Events() 1042 calculatedHash, err := flow.EventsMerkleRootHash(events) 1043 require.NoError(t, err) 1044 require.Equal(t, calculatedHash, result.CollectionAttestationResultAt(i).EventCommitment()) 1045 } 1046 } 1047 1048 type testTransactionExecutor struct { 1049 executeTransaction func(runtime.Script, runtime.Context) error 1050 1051 script runtime.Script 1052 context runtime.Context 1053 } 1054 1055 func (executor *testTransactionExecutor) Preprocess() error { 1056 // Do nothing. 1057 return nil 1058 } 1059 1060 func (executor *testTransactionExecutor) Execute() error { 1061 return executor.executeTransaction(executor.script, executor.context) 1062 } 1063 1064 func (executor *testTransactionExecutor) Result() (cadence.Value, error) { 1065 panic("Result not expected") 1066 } 1067 1068 type testRuntime struct { 1069 executeScript func(runtime.Script, runtime.Context) (cadence.Value, error) 1070 executeTransaction func(runtime.Script, runtime.Context) error 1071 readStored func(common.Address, cadence.Path, runtime.Context) ( 1072 cadence.Value, 1073 error, 1074 ) 1075 } 1076 1077 var _ runtime.Runtime = &testRuntime{} 1078 1079 func (e *testRuntime) Config() runtime.Config { 1080 panic("Config not expected") 1081 } 1082 1083 func (e *testRuntime) NewScriptExecutor( 1084 script runtime.Script, 1085 c runtime.Context, 1086 ) runtime.Executor { 1087 panic("NewScriptExecutor not expected") 1088 } 1089 1090 func (e *testRuntime) NewTransactionExecutor( 1091 script runtime.Script, 1092 c runtime.Context, 1093 ) runtime.Executor { 1094 return &testTransactionExecutor{ 1095 executeTransaction: e.executeTransaction, 1096 script: script, 1097 context: c, 1098 } 1099 } 1100 1101 func (e *testRuntime) NewContractFunctionExecutor( 1102 contractLocation common.AddressLocation, 1103 functionName string, 1104 arguments []cadence.Value, 1105 argumentTypes []sema.Type, 1106 context runtime.Context, 1107 ) runtime.Executor { 1108 panic("NewContractFunctionExecutor not expected") 1109 } 1110 1111 func (e *testRuntime) SetInvalidatedResourceValidationEnabled(_ bool) { 1112 panic("SetInvalidatedResourceValidationEnabled not expected") 1113 } 1114 1115 func (e *testRuntime) SetTracingEnabled(_ bool) { 1116 panic("SetTracingEnabled not expected") 1117 } 1118 1119 func (e *testRuntime) SetResourceOwnerChangeHandlerEnabled(_ bool) { 1120 panic("SetResourceOwnerChangeHandlerEnabled not expected") 1121 } 1122 1123 func (e *testRuntime) InvokeContractFunction( 1124 _ common.AddressLocation, 1125 _ string, 1126 _ []cadence.Value, 1127 _ []sema.Type, 1128 _ runtime.Context, 1129 ) (cadence.Value, error) { 1130 panic("InvokeContractFunction not expected") 1131 } 1132 1133 func (e *testRuntime) ExecuteScript( 1134 script runtime.Script, 1135 context runtime.Context, 1136 ) (cadence.Value, error) { 1137 return e.executeScript(script, context) 1138 } 1139 1140 func (e *testRuntime) ExecuteTransaction( 1141 script runtime.Script, 1142 context runtime.Context, 1143 ) error { 1144 return e.executeTransaction(script, context) 1145 } 1146 1147 func (*testRuntime) ParseAndCheckProgram( 1148 _ []byte, 1149 _ runtime.Context, 1150 ) (*interpreter.Program, error) { 1151 panic("ParseAndCheckProgram not expected") 1152 } 1153 1154 func (*testRuntime) SetCoverageReport(_ *runtime.CoverageReport) { 1155 panic("SetCoverageReport not expected") 1156 } 1157 1158 func (*testRuntime) SetContractUpdateValidationEnabled(_ bool) { 1159 panic("SetContractUpdateValidationEnabled not expected") 1160 } 1161 1162 func (*testRuntime) SetAtreeValidationEnabled(_ bool) { 1163 panic("SetAtreeValidationEnabled not expected") 1164 } 1165 1166 func (e *testRuntime) ReadStored( 1167 a common.Address, 1168 p cadence.Path, 1169 c runtime.Context, 1170 ) (cadence.Value, error) { 1171 return e.readStored(a, p, c) 1172 } 1173 1174 func (*testRuntime) SetDebugger(_ *interpreter.Debugger) { 1175 panic("SetDebugger not expected") 1176 } 1177 1178 type RandomAddressGenerator struct{} 1179 1180 func (r *RandomAddressGenerator) NextAddress() (flow.Address, error) { 1181 return flow.HexToAddress(fmt.Sprintf("0%d", rand.Intn(1000))), nil 1182 } 1183 1184 func (r *RandomAddressGenerator) CurrentAddress() flow.Address { 1185 return flow.HexToAddress(fmt.Sprintf("0%d", rand.Intn(1000))) 1186 } 1187 1188 func (r *RandomAddressGenerator) Bytes() []byte { 1189 panic("not implemented") 1190 } 1191 1192 func (r *RandomAddressGenerator) AddressCount() uint64 { 1193 panic("not implemented") 1194 } 1195 1196 func (testRuntime) Storage(runtime.Context) ( 1197 *runtime.Storage, 1198 *interpreter.Interpreter, 1199 error, 1200 ) { 1201 panic("Storage not expected") 1202 } 1203 1204 type FixedAddressGenerator struct { 1205 Address flow.Address 1206 } 1207 1208 func (f *FixedAddressGenerator) NextAddress() (flow.Address, error) { 1209 return f.Address, nil 1210 } 1211 1212 func (f *FixedAddressGenerator) CurrentAddress() flow.Address { 1213 return f.Address 1214 } 1215 1216 func (f *FixedAddressGenerator) Bytes() []byte { 1217 panic("not implemented") 1218 } 1219 1220 func (f *FixedAddressGenerator) AddressCount() uint64 { 1221 panic("not implemented") 1222 } 1223 1224 func Test_ExecutingSystemCollection(t *testing.T) { 1225 1226 execCtx := fvm.NewContext( 1227 fvm.WithChain(flow.Localnet.Chain()), 1228 fvm.WithBlocks(&environment.NoopBlockFinder{}), 1229 ) 1230 1231 vm := fvm.NewVirtualMachine() 1232 1233 rag := &RandomAddressGenerator{} 1234 1235 ledger := testutil.RootBootstrappedLedger(vm, execCtx) 1236 1237 committer := new(computermock.ViewCommitter) 1238 snapshot := storehouse.NewExecutingBlockSnapshot( 1239 snapshot.MapStorageSnapshot{}, 1240 unittest.StateCommitmentFixture(), 1241 ) 1242 1243 committer.On("CommitView", mock.Anything, mock.Anything). 1244 Return(nil, nil, nil, snapshot, nil). 1245 Times(1) // only system chunk 1246 1247 noopCollector := metrics.NewNoopCollector() 1248 1249 expectedNumberOfEvents := 3 1250 expectedEventSize := 1497 1251 1252 // bootstrapping does not cache programs 1253 expectedCachedPrograms := 0 1254 1255 metrics := new(modulemock.ExecutionMetrics) 1256 metrics.On("ExecutionBlockExecuted", 1257 mock.Anything, // duration 1258 mock.Anything). // stats 1259 Return(nil). 1260 Times(1) 1261 1262 metrics.On("ExecutionCollectionExecuted", 1263 mock.Anything, // duration 1264 mock.Anything). // stats 1265 Return(nil). 1266 Times(1) // system collection 1267 1268 metrics.On("ExecutionTransactionExecuted", 1269 mock.Anything, // duration 1270 mock.Anything, // conflict retry count 1271 mock.Anything, // computation used 1272 mock.Anything, // memory used 1273 expectedNumberOfEvents, 1274 expectedEventSize, 1275 false). 1276 Return(nil). 1277 Times(1) // system chunk tx 1278 1279 metrics.On( 1280 "ExecutionChunkDataPackGenerated", 1281 mock.Anything, 1282 mock.Anything). 1283 Return(nil). 1284 Times(1) // system collection 1285 1286 metrics.On( 1287 "ExecutionBlockCachedPrograms", 1288 expectedCachedPrograms). 1289 Return(nil). 1290 Times(1) // block 1291 1292 metrics.On( 1293 "ExecutionBlockExecutionEffortVectorComponent", 1294 mock.Anything, 1295 mock.Anything). 1296 Return(nil) 1297 1298 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 1299 trackerStorage := mocktracker.NewMockStorage() 1300 1301 prov := provider.NewProvider( 1302 zerolog.Nop(), 1303 noopCollector, 1304 execution_data.DefaultSerializer, 1305 bservice, 1306 trackerStorage, 1307 ) 1308 1309 me := new(modulemock.Local) 1310 me.On("NodeID").Return(unittest.IdentifierFixture()) 1311 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 1312 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 1313 Return(nil, nil) 1314 1315 constRandomSource := make([]byte, 32) 1316 1317 exe, err := computer.NewBlockComputer( 1318 vm, 1319 execCtx, 1320 metrics, 1321 trace.NewNoopTracer(), 1322 zerolog.Nop(), 1323 committer, 1324 me, 1325 prov, 1326 nil, 1327 testutil.ProtocolStateWithSourceFixture(constRandomSource), 1328 testMaxConcurrency) 1329 require.NoError(t, err) 1330 1331 // create empty block, it will have system collection attached while executing 1332 block := generateBlock(0, 0, rag) 1333 1334 result, err := exe.ExecuteBlock( 1335 context.Background(), 1336 unittest.IdentifierFixture(), 1337 block, 1338 ledger, 1339 derived.NewEmptyDerivedBlockData(0)) 1340 assert.NoError(t, err) 1341 assert.Len(t, result.AllExecutionSnapshots(), 1) // +1 system chunk 1342 assert.Len(t, result.AllTransactionResults(), 1) 1343 1344 assert.Empty(t, result.AllTransactionResults()[0].ErrorMessage) 1345 1346 committer.AssertExpectations(t) 1347 } 1348 1349 func generateBlock( 1350 collectionCount, transactionCount int, 1351 addressGenerator flow.AddressGenerator, 1352 ) *entity.ExecutableBlock { 1353 return generateBlockWithVisitor(collectionCount, transactionCount, addressGenerator, nil) 1354 } 1355 1356 func generateBlockWithVisitor( 1357 collectionCount, transactionCount int, 1358 addressGenerator flow.AddressGenerator, 1359 visitor func(body *flow.TransactionBody), 1360 ) *entity.ExecutableBlock { 1361 collections := make([]*entity.CompleteCollection, collectionCount) 1362 guarantees := make([]*flow.CollectionGuarantee, collectionCount) 1363 completeCollections := make(map[flow.Identifier]*entity.CompleteCollection) 1364 1365 for i := 0; i < collectionCount; i++ { 1366 collection := generateCollection(transactionCount, addressGenerator, visitor) 1367 collections[i] = collection 1368 guarantees[i] = collection.Guarantee 1369 completeCollections[collection.Guarantee.ID()] = collection 1370 } 1371 1372 block := flow.Block{ 1373 Header: &flow.Header{ 1374 Timestamp: flow.GenesisTime, 1375 Height: 42, 1376 View: 42, 1377 }, 1378 Payload: &flow.Payload{ 1379 Guarantees: guarantees, 1380 }, 1381 } 1382 1383 return &entity.ExecutableBlock{ 1384 Block: &block, 1385 CompleteCollections: completeCollections, 1386 StartState: unittest.StateCommitmentPointerFixture(), 1387 } 1388 } 1389 1390 func generateCollection( 1391 transactionCount int, 1392 addressGenerator flow.AddressGenerator, 1393 visitor func(body *flow.TransactionBody), 1394 ) *entity.CompleteCollection { 1395 transactions := make([]*flow.TransactionBody, transactionCount) 1396 1397 for i := 0; i < transactionCount; i++ { 1398 nextAddress, err := addressGenerator.NextAddress() 1399 if err != nil { 1400 panic(fmt.Errorf("cannot generate next address in test: %w", err)) 1401 } 1402 txBody := &flow.TransactionBody{ 1403 Payer: nextAddress, // a unique payer for each tx to generate a unique id 1404 Script: []byte("transaction { execute {} }"), 1405 } 1406 if visitor != nil { 1407 visitor(txBody) 1408 } 1409 transactions[i] = txBody 1410 } 1411 1412 collection := flow.Collection{Transactions: transactions} 1413 1414 guarantee := &flow.CollectionGuarantee{CollectionID: collection.ID()} 1415 1416 return &entity.CompleteCollection{ 1417 Guarantee: guarantee, 1418 Transactions: transactions, 1419 } 1420 } 1421 1422 type noOpExecutor struct{} 1423 1424 func (noOpExecutor) Cleanup() {} 1425 1426 func (noOpExecutor) Preprocess() error { 1427 return nil 1428 } 1429 1430 func (noOpExecutor) Execute() error { 1431 return nil 1432 } 1433 1434 func (noOpExecutor) Output() fvm.ProcedureOutput { 1435 return fvm.ProcedureOutput{} 1436 } 1437 1438 type testVM struct { 1439 t *testing.T 1440 eventsPerTransaction int 1441 1442 callCount int32 // atomic variable 1443 err fvmErrors.CodedError 1444 } 1445 1446 type testExecutor struct { 1447 *testVM 1448 1449 ctx fvm.Context 1450 proc fvm.Procedure 1451 txnState storage.TransactionPreparer 1452 } 1453 1454 func (testExecutor) Cleanup() { 1455 } 1456 1457 func (testExecutor) Preprocess() error { 1458 return nil 1459 } 1460 1461 func (executor *testExecutor) Execute() error { 1462 atomic.AddInt32(&executor.callCount, 1) 1463 1464 getSetAProgram(executor.t, executor.txnState) 1465 1466 return nil 1467 } 1468 1469 func (executor *testExecutor) Output() fvm.ProcedureOutput { 1470 txn := executor.proc.(*fvm.TransactionProcedure) 1471 1472 return fvm.ProcedureOutput{ 1473 Events: generateEvents(executor.eventsPerTransaction, txn.TxIndex), 1474 Err: executor.err, 1475 } 1476 } 1477 1478 func (vm *testVM) NewExecutor( 1479 ctx fvm.Context, 1480 proc fvm.Procedure, 1481 txnState storage.TransactionPreparer, 1482 ) fvm.ProcedureExecutor { 1483 return &testExecutor{ 1484 testVM: vm, 1485 proc: proc, 1486 ctx: ctx, 1487 txnState: txnState, 1488 } 1489 } 1490 1491 func (vm *testVM) CallCount() int { 1492 return int(atomic.LoadInt32(&vm.callCount)) 1493 } 1494 1495 func (vm *testVM) Run( 1496 ctx fvm.Context, 1497 proc fvm.Procedure, 1498 storageSnapshot snapshot.StorageSnapshot, 1499 ) ( 1500 *snapshot.ExecutionSnapshot, 1501 fvm.ProcedureOutput, 1502 error, 1503 ) { 1504 database := storage.NewBlockDatabase( 1505 storageSnapshot, 1506 proc.ExecutionTime(), 1507 ctx.DerivedBlockData) 1508 1509 txn, err := database.NewTransaction( 1510 proc.ExecutionTime(), 1511 state.DefaultParameters()) 1512 require.NoError(vm.t, err) 1513 1514 executor := vm.NewExecutor(ctx, proc, txn) 1515 err = fvm.Run(executor) 1516 require.NoError(vm.t, err) 1517 1518 err = txn.Finalize() 1519 require.NoError(vm.t, err) 1520 1521 executionSnapshot, err := txn.Commit() 1522 require.NoError(vm.t, err) 1523 1524 return executionSnapshot, executor.Output(), nil 1525 } 1526 1527 func (testVM) GetAccount( 1528 _ fvm.Context, 1529 _ flow.Address, 1530 _ snapshot.StorageSnapshot, 1531 ) ( 1532 *flow.Account, 1533 error, 1534 ) { 1535 panic("not implemented") 1536 } 1537 1538 func generateEvents(eventCount int, txIndex uint32) []flow.Event { 1539 events := make([]flow.Event, eventCount) 1540 for i := 0; i < eventCount; i++ { 1541 // creating some dummy event 1542 event := flow.Event{ 1543 Type: "whatever", 1544 EventIndex: uint32(i), 1545 TransactionIndex: txIndex, 1546 } 1547 events[i] = event 1548 } 1549 return events 1550 } 1551 1552 type errorVM struct { 1553 errorAt logical.Time 1554 } 1555 1556 type errorExecutor struct { 1557 err error 1558 } 1559 1560 func (errorExecutor) Cleanup() {} 1561 1562 func (errorExecutor) Preprocess() error { 1563 return nil 1564 } 1565 1566 func (e errorExecutor) Execute() error { 1567 return e.err 1568 } 1569 1570 func (errorExecutor) Output() fvm.ProcedureOutput { 1571 return fvm.ProcedureOutput{} 1572 } 1573 1574 func (vm errorVM) NewExecutor( 1575 ctx fvm.Context, 1576 proc fvm.Procedure, 1577 txn storage.TransactionPreparer, 1578 ) fvm.ProcedureExecutor { 1579 var err error 1580 if proc.ExecutionTime() == vm.errorAt { 1581 err = fmt.Errorf("boom - internal error") 1582 } 1583 1584 return errorExecutor{err: err} 1585 } 1586 1587 func (vm errorVM) Run( 1588 ctx fvm.Context, 1589 proc fvm.Procedure, 1590 storageSnapshot snapshot.StorageSnapshot, 1591 ) ( 1592 *snapshot.ExecutionSnapshot, 1593 fvm.ProcedureOutput, 1594 error, 1595 ) { 1596 var err error 1597 if proc.ExecutionTime() == vm.errorAt { 1598 err = fmt.Errorf("boom - internal error") 1599 } 1600 return &snapshot.ExecutionSnapshot{}, fvm.ProcedureOutput{}, err 1601 } 1602 1603 func (errorVM) GetAccount( 1604 ctx fvm.Context, 1605 addr flow.Address, 1606 storageSnapshot snapshot.StorageSnapshot, 1607 ) ( 1608 *flow.Account, 1609 error, 1610 ) { 1611 panic("not implemented") 1612 } 1613 1614 func getSetAProgram( 1615 t *testing.T, 1616 txnState storage.TransactionPreparer, 1617 ) { 1618 loc := common.AddressLocation{ 1619 Name: "SomeContract", 1620 Address: common.MustBytesToAddress([]byte{0x1}), 1621 } 1622 _, err := txnState.GetOrComputeProgram( 1623 txnState, 1624 loc, 1625 &programLoader{ 1626 load: func() (*derived.Program, error) { 1627 return &derived.Program{}, nil 1628 }, 1629 }, 1630 ) 1631 require.NoError(t, err) 1632 } 1633 1634 type programLoader struct { 1635 load func() (*derived.Program, error) 1636 } 1637 1638 func (p *programLoader) Compute( 1639 _ state.NestedTransactionPreparer, 1640 _ common.AddressLocation, 1641 ) ( 1642 *derived.Program, 1643 error, 1644 ) { 1645 return p.load() 1646 }