github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/manager_test.go (about) 1 package computation 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "math" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/ipfs/boxo/blockstore" 13 "github.com/ipfs/go-datastore" 14 dssync "github.com/ipfs/go-datastore/sync" 15 "github.com/onflow/cadence" 16 jsoncdc "github.com/onflow/cadence/encoding/json" 17 "github.com/onflow/cadence/runtime/common" 18 "github.com/rs/zerolog" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/mock" 21 "github.com/stretchr/testify/require" 22 23 "github.com/onflow/flow-go/engine/execution" 24 "github.com/onflow/flow-go/engine/execution/computation/committer" 25 "github.com/onflow/flow-go/engine/execution/computation/computer" 26 "github.com/onflow/flow-go/engine/execution/computation/query" 27 state2 "github.com/onflow/flow-go/engine/execution/state" 28 unittest2 "github.com/onflow/flow-go/engine/execution/state/unittest" 29 "github.com/onflow/flow-go/engine/execution/testutil" 30 "github.com/onflow/flow-go/fvm" 31 "github.com/onflow/flow-go/fvm/environment" 32 fvmErrors "github.com/onflow/flow-go/fvm/errors" 33 "github.com/onflow/flow-go/fvm/storage" 34 "github.com/onflow/flow-go/fvm/storage/derived" 35 "github.com/onflow/flow-go/fvm/storage/snapshot" 36 "github.com/onflow/flow-go/fvm/systemcontracts" 37 "github.com/onflow/flow-go/ledger/complete" 38 "github.com/onflow/flow-go/ledger/complete/wal/fixtures" 39 "github.com/onflow/flow-go/model/flow" 40 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 41 "github.com/onflow/flow-go/module/executiondatasync/provider" 42 mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock" 43 "github.com/onflow/flow-go/module/mempool/entity" 44 "github.com/onflow/flow-go/module/metrics" 45 module "github.com/onflow/flow-go/module/mock" 46 requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest" 47 "github.com/onflow/flow-go/module/trace" 48 "github.com/onflow/flow-go/utils/unittest" 49 ) 50 51 var scriptLogThreshold = 1 * time.Second 52 53 func TestComputeBlockWithStorage(t *testing.T) { 54 chain := flow.Mainnet.Chain() 55 56 vm := fvm.NewVirtualMachine() 57 execCtx := fvm.NewContext(fvm.WithChain(chain)) 58 59 privateKeys, err := testutil.GenerateAccountPrivateKeys(2) 60 require.NoError(t, err) 61 62 snapshotTree, accounts, err := testutil.CreateAccounts( 63 vm, 64 testutil.RootBootstrappedLedger(vm, execCtx), 65 privateKeys, 66 chain) 67 require.NoError(t, err) 68 69 tx1 := testutil.DeployCounterContractTransaction(accounts[0], chain) 70 tx1.SetProposalKey(chain.ServiceAddress(), 0, 0). 71 SetComputeLimit(1000). 72 SetPayer(chain.ServiceAddress()) 73 74 err = testutil.SignPayload(tx1, accounts[0], privateKeys[0]) 75 require.NoError(t, err) 76 77 err = testutil.SignEnvelope(tx1, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 78 require.NoError(t, err) 79 80 tx2 := testutil.CreateCounterTransaction(accounts[0], accounts[1]) 81 tx2.SetProposalKey(chain.ServiceAddress(), 0, 0). 82 SetComputeLimit(1000). 83 SetPayer(chain.ServiceAddress()) 84 85 err = testutil.SignPayload(tx2, accounts[1], privateKeys[1]) 86 require.NoError(t, err) 87 88 err = testutil.SignEnvelope(tx2, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 89 require.NoError(t, err) 90 91 transactions := []*flow.TransactionBody{tx1, tx2} 92 93 col := flow.Collection{Transactions: transactions} 94 95 guarantee := flow.CollectionGuarantee{ 96 CollectionID: col.ID(), 97 Signature: nil, 98 } 99 100 block := flow.Block{ 101 Header: &flow.Header{ 102 View: 42, 103 }, 104 Payload: &flow.Payload{ 105 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 106 }, 107 } 108 109 executableBlock := &entity.ExecutableBlock{ 110 Block: &block, 111 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 112 guarantee.ID(): { 113 Guarantee: &guarantee, 114 Transactions: transactions, 115 }, 116 }, 117 StartState: unittest.StateCommitmentPointerFixture(), 118 } 119 120 me := new(module.Local) 121 me.On("NodeID").Return(unittest.IdentifierFixture()) 122 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 123 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 124 Return(nil, nil) 125 126 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 127 trackerStorage := mocktracker.NewMockStorage() 128 129 prov := provider.NewProvider( 130 zerolog.Nop(), 131 metrics.NewNoopCollector(), 132 execution_data.DefaultSerializer, 133 bservice, 134 trackerStorage, 135 ) 136 137 blockComputer, err := computer.NewBlockComputer( 138 vm, 139 execCtx, 140 metrics.NewNoopCollector(), 141 trace.NewNoopTracer(), 142 zerolog.Nop(), 143 committer.NewNoopViewCommitter(), 144 me, 145 prov, 146 nil, 147 testutil.ProtocolStateWithSourceFixture(nil), 148 testMaxConcurrency) 149 require.NoError(t, err) 150 151 derivedChainData, err := derived.NewDerivedChainData(10) 152 require.NoError(t, err) 153 154 engine := &Manager{ 155 blockComputer: blockComputer, 156 derivedChainData: derivedChainData, 157 } 158 159 returnedComputationResult, err := engine.ComputeBlock( 160 context.Background(), 161 unittest.IdentifierFixture(), 162 executableBlock, 163 snapshotTree) 164 require.NoError(t, err) 165 166 hasUpdates := false 167 for _, snapshot := range returnedComputationResult.AllExecutionSnapshots() { 168 if len(snapshot.WriteSet) > 0 { 169 hasUpdates = true 170 break 171 } 172 } 173 require.True(t, hasUpdates) 174 require.Equal(t, returnedComputationResult.BlockExecutionResult.Size(), 1+1) // 1 coll + 1 system chunk 175 assert.NotEmpty(t, returnedComputationResult.AllExecutionSnapshots()[0].UpdatedRegisters()) 176 } 177 178 func TestComputeBlock_Uploader(t *testing.T) { 179 180 noopCollector := &metrics.NoopCollector{} 181 182 ledger, err := complete.NewLedger(&fixtures.NoopWAL{}, 10, noopCollector, zerolog.Nop(), complete.DefaultPathFinderVersion) 183 require.NoError(t, err) 184 185 compactor := fixtures.NewNoopCompactor(ledger) 186 <-compactor.Ready() 187 defer func() { 188 <-ledger.Done() 189 <-compactor.Done() 190 }() 191 192 me := new(module.Local) 193 me.On("NodeID").Return(unittest.IdentifierFixture()) 194 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 195 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 196 Return(nil, nil) 197 198 computationResult := unittest2.ComputationResultFixture( 199 t, 200 unittest.IdentifierFixture(), 201 [][]flow.Identifier{ 202 {unittest.IdentifierFixture()}, 203 {unittest.IdentifierFixture()}, 204 }) 205 206 blockComputer := &FakeBlockComputer{ 207 computationResult: computationResult, 208 } 209 210 derivedChainData, err := derived.NewDerivedChainData(10) 211 require.NoError(t, err) 212 213 manager := &Manager{ 214 blockComputer: blockComputer, 215 derivedChainData: derivedChainData, 216 } 217 218 _, err = manager.ComputeBlock( 219 context.Background(), 220 unittest.IdentifierFixture(), 221 computationResult.ExecutableBlock, 222 state2.NewLedgerStorageSnapshot( 223 ledger, 224 flow.StateCommitment(ledger.InitialState()))) 225 require.NoError(t, err) 226 } 227 228 func TestExecuteScript(t *testing.T) { 229 230 logger := zerolog.Nop() 231 232 execCtx := fvm.NewContext(fvm.WithLogger(logger)) 233 234 me := new(module.Local) 235 me.On("NodeID").Return(unittest.IdentifierFixture()) 236 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 237 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 238 Return(nil, nil) 239 240 vm := fvm.NewVirtualMachine() 241 242 ledger := testutil.RootBootstrappedLedger(vm, execCtx, fvm.WithExecutionMemoryLimit(math.MaxUint64)) 243 244 sc := systemcontracts.SystemContractsForChain(execCtx.Chain.ChainID()) 245 246 script := []byte(fmt.Sprintf( 247 ` 248 import FungibleToken from %s 249 250 access(all) fun main() {} 251 `, 252 sc.FungibleToken.Address.HexWithPrefix(), 253 )) 254 255 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 256 trackerStorage := mocktracker.NewMockStorage() 257 258 prov := provider.NewProvider( 259 zerolog.Nop(), 260 metrics.NewNoopCollector(), 261 execution_data.DefaultSerializer, 262 bservice, 263 trackerStorage, 264 ) 265 266 engine, err := New(logger, 267 metrics.NewNoopCollector(), 268 trace.NewNoopTracer(), 269 me, 270 testutil.ProtocolStateWithSourceFixture(nil), 271 execCtx, 272 committer.NewNoopViewCommitter(), 273 prov, 274 ComputationConfig{ 275 QueryConfig: query.NewDefaultConfig(), 276 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 277 MaxConcurrency: 1, 278 }, 279 ) 280 require.NoError(t, err) 281 282 header := unittest.BlockHeaderFixture() 283 _, _, err = engine.ExecuteScript( 284 context.Background(), 285 script, 286 nil, 287 header, 288 ledger) 289 require.NoError(t, err) 290 } 291 292 // Balance script used to swallow errors, which meant that even if the view was empty, a script that did nothing but get 293 // the balance of an account would succeed and return 0. 294 func TestExecuteScript_BalanceScriptFailsIfViewIsEmpty(t *testing.T) { 295 296 logger := zerolog.Nop() 297 298 execCtx := fvm.NewContext(fvm.WithLogger(logger)) 299 300 me := new(module.Local) 301 me.On("NodeID").Return(unittest.IdentifierFixture()) 302 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 303 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 304 Return(nil, nil) 305 306 snapshot := snapshot.NewReadFuncStorageSnapshot( 307 func(id flow.RegisterID) (flow.RegisterValue, error) { 308 return nil, fmt.Errorf("error getting register") 309 }) 310 311 sc := systemcontracts.SystemContractsForChain(execCtx.Chain.ChainID()) 312 313 script := []byte(fmt.Sprintf( 314 ` 315 access(all) fun main(): UFix64 { 316 return getAccount(%s).balance 317 } 318 `, 319 sc.FungibleToken.Address.HexWithPrefix(), 320 )) 321 322 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 323 trackerStorage := mocktracker.NewMockStorage() 324 325 prov := provider.NewProvider( 326 zerolog.Nop(), 327 metrics.NewNoopCollector(), 328 execution_data.DefaultSerializer, 329 bservice, 330 trackerStorage, 331 ) 332 333 engine, err := New(logger, 334 metrics.NewNoopCollector(), 335 trace.NewNoopTracer(), 336 me, 337 testutil.ProtocolStateWithSourceFixture(nil), 338 execCtx, 339 committer.NewNoopViewCommitter(), 340 prov, 341 ComputationConfig{ 342 QueryConfig: query.NewDefaultConfig(), 343 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 344 MaxConcurrency: 1, 345 }, 346 ) 347 require.NoError(t, err) 348 349 header := unittest.BlockHeaderFixture() 350 _, _, err = engine.ExecuteScript( 351 context.Background(), 352 script, 353 nil, 354 header, 355 snapshot) 356 require.ErrorContains(t, err, "error getting register") 357 } 358 359 func TestExecuteScripPanicsAreHandled(t *testing.T) { 360 361 ctx := fvm.NewContext() 362 363 buffer := &bytes.Buffer{} 364 log := zerolog.New(buffer) 365 366 header := unittest.BlockHeaderFixture() 367 368 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 369 trackerStorage := mocktracker.NewMockStorage() 370 371 prov := provider.NewProvider( 372 zerolog.Nop(), 373 metrics.NewNoopCollector(), 374 execution_data.DefaultSerializer, 375 bservice, 376 trackerStorage, 377 ) 378 379 manager, err := New(log, 380 metrics.NewNoopCollector(), 381 trace.NewNoopTracer(), 382 nil, 383 testutil.ProtocolStateWithSourceFixture(nil), 384 ctx, 385 committer.NewNoopViewCommitter(), 386 prov, 387 ComputationConfig{ 388 QueryConfig: query.NewDefaultConfig(), 389 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 390 MaxConcurrency: 1, 391 NewCustomVirtualMachine: func() fvm.VM { 392 return &PanickingVM{} 393 }, 394 }, 395 ) 396 require.NoError(t, err) 397 398 _, _, err = manager.ExecuteScript( 399 context.Background(), 400 []byte("whatever"), 401 nil, 402 header, 403 nil) 404 405 require.Error(t, err) 406 require.Contains(t, buffer.String(), "Verunsicherung") 407 } 408 409 func TestExecuteScript_LongScriptsAreLogged(t *testing.T) { 410 411 ctx := fvm.NewContext() 412 413 buffer := &bytes.Buffer{} 414 log := zerolog.New(buffer) 415 416 header := unittest.BlockHeaderFixture() 417 418 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 419 trackerStorage := mocktracker.NewMockStorage() 420 421 prov := provider.NewProvider( 422 zerolog.Nop(), 423 metrics.NewNoopCollector(), 424 execution_data.DefaultSerializer, 425 bservice, 426 trackerStorage, 427 ) 428 429 manager, err := New(log, 430 metrics.NewNoopCollector(), 431 trace.NewNoopTracer(), 432 nil, 433 testutil.ProtocolStateWithSourceFixture(nil), 434 ctx, 435 committer.NewNoopViewCommitter(), 436 prov, 437 ComputationConfig{ 438 QueryConfig: query.QueryConfig{ 439 LogTimeThreshold: 1 * time.Millisecond, 440 ExecutionTimeLimit: query.DefaultExecutionTimeLimit, 441 }, 442 DerivedDataCacheSize: 10, 443 MaxConcurrency: 1, 444 NewCustomVirtualMachine: func() fvm.VM { 445 return &LongRunningVM{duration: 2 * time.Millisecond} 446 }, 447 }, 448 ) 449 require.NoError(t, err) 450 451 _, _, err = manager.ExecuteScript( 452 context.Background(), 453 []byte("whatever"), 454 nil, 455 header, 456 nil) 457 458 require.NoError(t, err) 459 require.Contains(t, buffer.String(), "exceeded threshold") 460 } 461 462 func TestExecuteScript_ShortScriptsAreNotLogged(t *testing.T) { 463 464 ctx := fvm.NewContext() 465 466 buffer := &bytes.Buffer{} 467 log := zerolog.New(buffer) 468 469 header := unittest.BlockHeaderFixture() 470 471 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 472 trackerStorage := mocktracker.NewMockStorage() 473 474 prov := provider.NewProvider( 475 zerolog.Nop(), 476 metrics.NewNoopCollector(), 477 execution_data.DefaultSerializer, 478 bservice, 479 trackerStorage, 480 ) 481 482 manager, err := New(log, 483 metrics.NewNoopCollector(), 484 trace.NewNoopTracer(), 485 nil, 486 testutil.ProtocolStateWithSourceFixture(nil), 487 ctx, 488 committer.NewNoopViewCommitter(), 489 prov, 490 ComputationConfig{ 491 QueryConfig: query.QueryConfig{ 492 LogTimeThreshold: 1 * time.Second, 493 ExecutionTimeLimit: query.DefaultExecutionTimeLimit, 494 }, 495 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 496 MaxConcurrency: 1, 497 NewCustomVirtualMachine: func() fvm.VM { 498 return &LongRunningVM{duration: 0} 499 }, 500 }, 501 ) 502 require.NoError(t, err) 503 504 _, _, err = manager.ExecuteScript( 505 context.Background(), 506 []byte("whatever"), 507 nil, 508 header, 509 nil) 510 511 require.NoError(t, err) 512 require.NotContains(t, buffer.String(), "exceeded threshold") 513 } 514 515 type PanickingExecutor struct{} 516 517 func (PanickingExecutor) Cleanup() {} 518 519 func (PanickingExecutor) Preprocess() error { 520 return nil 521 } 522 523 func (PanickingExecutor) Execute() error { 524 panic("panic, but expected with sentinel for test: Verunsicherung ") 525 } 526 527 func (PanickingExecutor) Output() fvm.ProcedureOutput { 528 return fvm.ProcedureOutput{} 529 } 530 531 type PanickingVM struct{} 532 533 func (p *PanickingVM) NewExecutor( 534 f fvm.Context, 535 procedure fvm.Procedure, 536 txn storage.TransactionPreparer, 537 ) fvm.ProcedureExecutor { 538 return PanickingExecutor{} 539 } 540 541 func (p *PanickingVM) Run( 542 f fvm.Context, 543 procedure fvm.Procedure, 544 storageSnapshot snapshot.StorageSnapshot, 545 ) ( 546 *snapshot.ExecutionSnapshot, 547 fvm.ProcedureOutput, 548 error, 549 ) { 550 panic("panic, but expected with sentinel for test: Verunsicherung ") 551 } 552 553 func (p *PanickingVM) GetAccount( 554 ctx fvm.Context, 555 address flow.Address, 556 storageSnapshot snapshot.StorageSnapshot, 557 ) ( 558 *flow.Account, 559 error, 560 ) { 561 panic("not expected") 562 } 563 564 type LongRunningExecutor struct { 565 duration time.Duration 566 } 567 568 func (LongRunningExecutor) Cleanup() {} 569 570 func (LongRunningExecutor) Preprocess() error { 571 return nil 572 } 573 574 func (l LongRunningExecutor) Execute() error { 575 time.Sleep(l.duration) 576 return nil 577 } 578 579 func (LongRunningExecutor) Output() fvm.ProcedureOutput { 580 return fvm.ProcedureOutput{ 581 Value: cadence.NewVoid(), 582 } 583 } 584 585 type LongRunningVM struct { 586 duration time.Duration 587 } 588 589 func (l *LongRunningVM) NewExecutor( 590 f fvm.Context, 591 procedure fvm.Procedure, 592 txn storage.TransactionPreparer, 593 ) fvm.ProcedureExecutor { 594 return LongRunningExecutor{ 595 duration: l.duration, 596 } 597 } 598 599 func (l *LongRunningVM) Run( 600 f fvm.Context, 601 procedure fvm.Procedure, 602 storageSnapshot snapshot.StorageSnapshot, 603 ) ( 604 *snapshot.ExecutionSnapshot, 605 fvm.ProcedureOutput, 606 error, 607 ) { 608 time.Sleep(l.duration) 609 610 snapshot := &snapshot.ExecutionSnapshot{} 611 output := fvm.ProcedureOutput{ 612 Value: cadence.NewVoid(), 613 } 614 return snapshot, output, nil 615 } 616 617 func (l *LongRunningVM) GetAccount( 618 ctx fvm.Context, 619 address flow.Address, 620 storageSnapshot snapshot.StorageSnapshot, 621 ) ( 622 *flow.Account, 623 error, 624 ) { 625 panic("not expected") 626 } 627 628 type FakeBlockComputer struct { 629 computationResult *execution.ComputationResult 630 } 631 632 func (f *FakeBlockComputer) ExecuteBlock( 633 context.Context, 634 flow.Identifier, 635 *entity.ExecutableBlock, 636 snapshot.StorageSnapshot, 637 *derived.DerivedBlockData, 638 ) ( 639 *execution.ComputationResult, 640 error, 641 ) { 642 return f.computationResult, nil 643 } 644 645 func TestExecuteScriptTimeout(t *testing.T) { 646 647 timeout := 1 * time.Millisecond 648 manager, err := New( 649 zerolog.Nop(), 650 metrics.NewNoopCollector(), 651 trace.NewNoopTracer(), 652 nil, 653 testutil.ProtocolStateWithSourceFixture(nil), 654 fvm.NewContext(), 655 committer.NewNoopViewCommitter(), 656 nil, 657 ComputationConfig{ 658 QueryConfig: query.QueryConfig{ 659 LogTimeThreshold: query.DefaultLogTimeThreshold, 660 ExecutionTimeLimit: timeout, 661 }, 662 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 663 MaxConcurrency: 1, 664 }, 665 ) 666 667 require.NoError(t, err) 668 669 script := []byte(` 670 access(all) fun main(): Int { 671 var i = 0 672 while i < 10000 { 673 i = i + 1 674 } 675 return i 676 } 677 `) 678 679 header := unittest.BlockHeaderFixture() 680 value, _, err := manager.ExecuteScript( 681 context.Background(), 682 script, 683 nil, 684 header, 685 nil) 686 687 require.Error(t, err) 688 require.Nil(t, value) 689 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionTimedOutError.String()) 690 } 691 692 func TestExecuteScriptCancelled(t *testing.T) { 693 694 timeout := 30 * time.Second 695 manager, err := New( 696 zerolog.Nop(), 697 metrics.NewNoopCollector(), 698 trace.NewNoopTracer(), 699 nil, 700 testutil.ProtocolStateWithSourceFixture(nil), 701 fvm.NewContext(), 702 committer.NewNoopViewCommitter(), 703 nil, 704 ComputationConfig{ 705 QueryConfig: query.QueryConfig{ 706 LogTimeThreshold: query.DefaultLogTimeThreshold, 707 ExecutionTimeLimit: timeout, 708 }, 709 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 710 MaxConcurrency: 1, 711 }, 712 ) 713 714 require.NoError(t, err) 715 716 script := []byte(` 717 access(all) fun main(): Int { 718 var i = 0 719 var j = 0 720 while i < 10000000 { 721 i = i + 1 722 j = i + j 723 } 724 return i 725 } 726 `) 727 728 var value []byte 729 var wg sync.WaitGroup 730 reqCtx, cancel := context.WithCancel(context.Background()) 731 wg.Add(1) 732 go func() { 733 header := unittest.BlockHeaderFixture() 734 value, _, err = manager.ExecuteScript( 735 reqCtx, 736 script, 737 nil, 738 header, 739 nil) 740 wg.Done() 741 }() 742 cancel() 743 wg.Wait() 744 require.Nil(t, value) 745 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionCancelledError.String()) 746 } 747 748 func Test_EventEncodingFailsOnlyTxAndCarriesOn(t *testing.T) { 749 750 chain := flow.Mainnet.Chain() 751 vm := fvm.NewVirtualMachine() 752 753 eventEncoder := &testingEventEncoder{ 754 realEncoder: environment.NewCadenceEventEncoder(), 755 } 756 757 execCtx := fvm.NewContext( 758 fvm.WithChain(chain), 759 fvm.WithEventEncoder(eventEncoder), 760 ) 761 762 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 763 require.NoError(t, err) 764 snapshotTree, accounts, err := testutil.CreateAccounts( 765 vm, 766 testutil.RootBootstrappedLedger(vm, execCtx), 767 privateKeys, 768 chain) 769 require.NoError(t, err) 770 771 // setup transactions 772 account := accounts[0] 773 privKey := privateKeys[0] 774 // tx1 deploys contract version 1 775 tx1 := testutil.DeployEventContractTransaction(account, chain, 1) 776 prepareTx(t, tx1, account, privKey, 0, chain) 777 778 // tx2 emits event which will fail encoding 779 tx2 := testutil.CreateEmitEventTransaction(account, account) 780 prepareTx(t, tx2, account, privKey, 1, chain) 781 782 // tx3 emits event that will work fine 783 tx3 := testutil.CreateEmitEventTransaction(account, account) 784 prepareTx(t, tx3, account, privKey, 2, chain) 785 786 transactions := []*flow.TransactionBody{tx1, tx2, tx3} 787 788 col := flow.Collection{Transactions: transactions} 789 790 guarantee := flow.CollectionGuarantee{ 791 CollectionID: col.ID(), 792 Signature: nil, 793 } 794 795 block := flow.Block{ 796 Header: &flow.Header{ 797 View: 26, 798 }, 799 Payload: &flow.Payload{ 800 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 801 }, 802 } 803 804 executableBlock := &entity.ExecutableBlock{ 805 Block: &block, 806 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 807 guarantee.ID(): { 808 Guarantee: &guarantee, 809 Transactions: transactions, 810 }, 811 }, 812 StartState: unittest.StateCommitmentPointerFixture(), 813 } 814 815 me := new(module.Local) 816 me.On("NodeID").Return(unittest.IdentifierFixture()) 817 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 818 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 819 Return(nil, nil) 820 821 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 822 trackerStorage := mocktracker.NewMockStorage() 823 824 prov := provider.NewProvider( 825 zerolog.Nop(), 826 metrics.NewNoopCollector(), 827 execution_data.DefaultSerializer, 828 bservice, 829 trackerStorage, 830 ) 831 832 blockComputer, err := computer.NewBlockComputer( 833 vm, 834 execCtx, 835 metrics.NewNoopCollector(), 836 trace.NewNoopTracer(), 837 zerolog.Nop(), 838 committer.NewNoopViewCommitter(), 839 me, 840 prov, 841 nil, 842 testutil.ProtocolStateWithSourceFixture(nil), 843 testMaxConcurrency) 844 require.NoError(t, err) 845 846 derivedChainData, err := derived.NewDerivedChainData(10) 847 require.NoError(t, err) 848 849 engine := &Manager{ 850 blockComputer: blockComputer, 851 derivedChainData: derivedChainData, 852 } 853 854 eventEncoder.enabled = true 855 856 returnedComputationResult, err := engine.ComputeBlock( 857 context.Background(), 858 unittest.IdentifierFixture(), 859 executableBlock, 860 snapshotTree) 861 require.NoError(t, err) 862 863 txResults := returnedComputationResult.AllTransactionResults() 864 require.Len(t, txResults, 4) // 2 txs + 1 system tx 865 866 require.Empty(t, txResults[0].ErrorMessage) 867 require.Contains(t, txResults[1].ErrorMessage, "I failed encoding") 868 require.Empty(t, txResults[2].ErrorMessage) 869 870 colRes := returnedComputationResult.CollectionExecutionResultAt(0) 871 events := colRes.Events() 872 require.Len(t, events, 2) // 1 collection + 1 system chunk 873 874 // first event should be contract deployed 875 assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type) 876 877 // second event should come from tx3 (index 2) as tx2 (index 1) should fail encoding 878 hasValidEventValue(t, events[1], 1) 879 assert.Equal(t, events[1].TransactionIndex, uint32(2)) 880 } 881 882 type testingEventEncoder struct { 883 realEncoder *environment.CadenceEventEncoder 884 calls int 885 enabled bool 886 } 887 888 func (e *testingEventEncoder) Encode(event cadence.Event) ([]byte, error) { 889 defer func() { 890 if e.enabled { 891 e.calls++ 892 } 893 }() 894 895 if e.calls == 1 && e.enabled { 896 return nil, fmt.Errorf("I failed encoding") 897 } 898 return e.realEncoder.Encode(event) 899 } 900 901 func TestScriptStorageMutationsDiscarded(t *testing.T) { 902 903 timeout := 10 * time.Second 904 chain := flow.Mainnet.Chain() 905 ctx := fvm.NewContext(fvm.WithChain(chain)) 906 manager, _ := New( 907 zerolog.Nop(), 908 metrics.NewExecutionCollector(ctx.Tracer), 909 trace.NewNoopTracer(), 910 nil, 911 testutil.ProtocolStateWithSourceFixture(nil), 912 ctx, 913 committer.NewNoopViewCommitter(), 914 nil, 915 ComputationConfig{ 916 QueryConfig: query.QueryConfig{ 917 LogTimeThreshold: query.DefaultLogTimeThreshold, 918 ExecutionTimeLimit: timeout, 919 }, 920 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 921 MaxConcurrency: 1, 922 }, 923 ) 924 vm := manager.vm 925 926 // Create an account private key. 927 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 928 require.NoError(t, err) 929 930 // Bootstrap a ledger, creating accounts with the provided private keys 931 // and the root account. 932 snapshotTree, accounts, err := testutil.CreateAccounts( 933 vm, 934 testutil.RootBootstrappedLedger(vm, ctx), 935 privateKeys, 936 chain) 937 require.NoError(t, err) 938 account := accounts[0] 939 address := cadence.NewAddress(account) 940 commonAddress, _ := common.HexToAddress(address.Hex()) 941 942 script := []byte(` 943 access(all) fun main(account: Address) { 944 let acc = getAuthAccount<auth(SaveValue) &Account>(account) 945 acc.storage.save(3, to: /storage/x) 946 } 947 `) 948 949 header := unittest.BlockHeaderFixture() 950 _, compUsed, err := manager.ExecuteScript( 951 context.Background(), 952 script, 953 [][]byte{jsoncdc.MustEncode(address)}, 954 header, 955 snapshotTree) 956 957 require.NoError(t, err) 958 require.Greater(t, compUsed, uint64(0)) 959 960 env := environment.NewScriptEnvironmentFromStorageSnapshot( 961 ctx.EnvironmentParams, 962 snapshotTree) 963 964 rt := env.BorrowCadenceRuntime() 965 defer env.ReturnCadenceRuntime(rt) 966 967 path, err := cadence.NewPath(common.PathDomainStorage, "x") 968 require.NoError(t, err) 969 970 v, err := rt.ReadStored(commonAddress, path) 971 // the save should not update account storage by writing the updates 972 // back to the snapshotTree 973 require.NoError(t, err) 974 require.Equal(t, nil, v) 975 }