github.com/onflow/flow-go@v0.33.17/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/go-datastore" 13 dssync "github.com/ipfs/go-datastore/sync" 14 blockstore "github.com/ipfs/go-ipfs-blockstore" 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 SetGasLimit(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 SetGasLimit(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 pub 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 pub 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 407 require.Contains(t, buffer.String(), "Verunsicherung") 408 } 409 410 func TestExecuteScript_LongScriptsAreLogged(t *testing.T) { 411 412 ctx := fvm.NewContext() 413 414 buffer := &bytes.Buffer{} 415 log := zerolog.New(buffer) 416 417 header := unittest.BlockHeaderFixture() 418 419 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 420 trackerStorage := mocktracker.NewMockStorage() 421 422 prov := provider.NewProvider( 423 zerolog.Nop(), 424 metrics.NewNoopCollector(), 425 execution_data.DefaultSerializer, 426 bservice, 427 trackerStorage, 428 ) 429 430 manager, err := New(log, 431 metrics.NewNoopCollector(), 432 trace.NewNoopTracer(), 433 nil, 434 testutil.ProtocolStateWithSourceFixture(nil), 435 ctx, 436 committer.NewNoopViewCommitter(), 437 prov, 438 ComputationConfig{ 439 QueryConfig: query.QueryConfig{ 440 LogTimeThreshold: 1 * time.Millisecond, 441 ExecutionTimeLimit: query.DefaultExecutionTimeLimit, 442 }, 443 DerivedDataCacheSize: 10, 444 MaxConcurrency: 1, 445 NewCustomVirtualMachine: func() fvm.VM { 446 return &LongRunningVM{duration: 2 * time.Millisecond} 447 }, 448 }, 449 ) 450 require.NoError(t, err) 451 452 _, err = manager.ExecuteScript( 453 context.Background(), 454 []byte("whatever"), 455 nil, 456 header, 457 nil) 458 459 require.NoError(t, err) 460 461 require.Contains(t, buffer.String(), "exceeded threshold") 462 } 463 464 func TestExecuteScript_ShortScriptsAreNotLogged(t *testing.T) { 465 466 ctx := fvm.NewContext() 467 468 buffer := &bytes.Buffer{} 469 log := zerolog.New(buffer) 470 471 header := unittest.BlockHeaderFixture() 472 473 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 474 trackerStorage := mocktracker.NewMockStorage() 475 476 prov := provider.NewProvider( 477 zerolog.Nop(), 478 metrics.NewNoopCollector(), 479 execution_data.DefaultSerializer, 480 bservice, 481 trackerStorage, 482 ) 483 484 manager, err := New(log, 485 metrics.NewNoopCollector(), 486 trace.NewNoopTracer(), 487 nil, 488 testutil.ProtocolStateWithSourceFixture(nil), 489 ctx, 490 committer.NewNoopViewCommitter(), 491 prov, 492 ComputationConfig{ 493 QueryConfig: query.QueryConfig{ 494 LogTimeThreshold: 1 * time.Second, 495 ExecutionTimeLimit: query.DefaultExecutionTimeLimit, 496 }, 497 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 498 MaxConcurrency: 1, 499 NewCustomVirtualMachine: func() fvm.VM { 500 return &LongRunningVM{duration: 0} 501 }, 502 }, 503 ) 504 require.NoError(t, err) 505 506 _, err = manager.ExecuteScript( 507 context.Background(), 508 []byte("whatever"), 509 nil, 510 header, 511 nil) 512 513 require.NoError(t, err) 514 515 require.NotContains(t, buffer.String(), "exceeded threshold") 516 } 517 518 type PanickingExecutor struct{} 519 520 func (PanickingExecutor) Cleanup() {} 521 522 func (PanickingExecutor) Preprocess() error { 523 return nil 524 } 525 526 func (PanickingExecutor) Execute() error { 527 panic("panic, but expected with sentinel for test: Verunsicherung ") 528 } 529 530 func (PanickingExecutor) Output() fvm.ProcedureOutput { 531 return fvm.ProcedureOutput{} 532 } 533 534 type PanickingVM struct{} 535 536 func (p *PanickingVM) NewExecutor( 537 f fvm.Context, 538 procedure fvm.Procedure, 539 txn storage.TransactionPreparer, 540 ) fvm.ProcedureExecutor { 541 return PanickingExecutor{} 542 } 543 544 func (p *PanickingVM) Run( 545 f fvm.Context, 546 procedure fvm.Procedure, 547 storageSnapshot snapshot.StorageSnapshot, 548 ) ( 549 *snapshot.ExecutionSnapshot, 550 fvm.ProcedureOutput, 551 error, 552 ) { 553 panic("panic, but expected with sentinel for test: Verunsicherung ") 554 } 555 556 func (p *PanickingVM) GetAccount( 557 ctx fvm.Context, 558 address flow.Address, 559 storageSnapshot snapshot.StorageSnapshot, 560 ) ( 561 *flow.Account, 562 error, 563 ) { 564 panic("not expected") 565 } 566 567 type LongRunningExecutor struct { 568 duration time.Duration 569 } 570 571 func (LongRunningExecutor) Cleanup() {} 572 573 func (LongRunningExecutor) Preprocess() error { 574 return nil 575 } 576 577 func (l LongRunningExecutor) Execute() error { 578 time.Sleep(l.duration) 579 return nil 580 } 581 582 func (LongRunningExecutor) Output() fvm.ProcedureOutput { 583 return fvm.ProcedureOutput{ 584 Value: cadence.NewVoid(), 585 } 586 } 587 588 type LongRunningVM struct { 589 duration time.Duration 590 } 591 592 func (l *LongRunningVM) NewExecutor( 593 f fvm.Context, 594 procedure fvm.Procedure, 595 txn storage.TransactionPreparer, 596 ) fvm.ProcedureExecutor { 597 return LongRunningExecutor{ 598 duration: l.duration, 599 } 600 } 601 602 func (l *LongRunningVM) Run( 603 f fvm.Context, 604 procedure fvm.Procedure, 605 storageSnapshot snapshot.StorageSnapshot, 606 ) ( 607 *snapshot.ExecutionSnapshot, 608 fvm.ProcedureOutput, 609 error, 610 ) { 611 time.Sleep(l.duration) 612 613 snapshot := &snapshot.ExecutionSnapshot{} 614 output := fvm.ProcedureOutput{ 615 Value: cadence.NewVoid(), 616 } 617 return snapshot, output, nil 618 } 619 620 func (l *LongRunningVM) GetAccount( 621 ctx fvm.Context, 622 address flow.Address, 623 storageSnapshot snapshot.StorageSnapshot, 624 ) ( 625 *flow.Account, 626 error, 627 ) { 628 panic("not expected") 629 } 630 631 type FakeBlockComputer struct { 632 computationResult *execution.ComputationResult 633 } 634 635 func (f *FakeBlockComputer) ExecuteBlock( 636 context.Context, 637 flow.Identifier, 638 *entity.ExecutableBlock, 639 snapshot.StorageSnapshot, 640 *derived.DerivedBlockData, 641 ) ( 642 *execution.ComputationResult, 643 error, 644 ) { 645 return f.computationResult, nil 646 } 647 648 func TestExecuteScriptTimeout(t *testing.T) { 649 650 timeout := 1 * time.Millisecond 651 manager, err := New( 652 zerolog.Nop(), 653 metrics.NewNoopCollector(), 654 trace.NewNoopTracer(), 655 nil, 656 testutil.ProtocolStateWithSourceFixture(nil), 657 fvm.NewContext(), 658 committer.NewNoopViewCommitter(), 659 nil, 660 ComputationConfig{ 661 QueryConfig: query.QueryConfig{ 662 LogTimeThreshold: query.DefaultLogTimeThreshold, 663 ExecutionTimeLimit: timeout, 664 }, 665 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 666 MaxConcurrency: 1, 667 }, 668 ) 669 670 require.NoError(t, err) 671 672 script := []byte(` 673 pub fun main(): Int { 674 var i = 0 675 while i < 10000 { 676 i = i + 1 677 } 678 return i 679 } 680 `) 681 682 header := unittest.BlockHeaderFixture() 683 value, err := manager.ExecuteScript( 684 context.Background(), 685 script, 686 nil, 687 header, 688 nil) 689 690 require.Error(t, err) 691 require.Nil(t, value) 692 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionTimedOutError.String()) 693 } 694 695 func TestExecuteScriptCancelled(t *testing.T) { 696 697 timeout := 30 * time.Second 698 manager, err := New( 699 zerolog.Nop(), 700 metrics.NewNoopCollector(), 701 trace.NewNoopTracer(), 702 nil, 703 testutil.ProtocolStateWithSourceFixture(nil), 704 fvm.NewContext(), 705 committer.NewNoopViewCommitter(), 706 nil, 707 ComputationConfig{ 708 QueryConfig: query.QueryConfig{ 709 LogTimeThreshold: query.DefaultLogTimeThreshold, 710 ExecutionTimeLimit: timeout, 711 }, 712 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 713 MaxConcurrency: 1, 714 }, 715 ) 716 717 require.NoError(t, err) 718 719 script := []byte(` 720 pub fun main(): Int { 721 var i = 0 722 var j = 0 723 while i < 10000000 { 724 i = i + 1 725 j = i + j 726 } 727 return i 728 } 729 `) 730 731 var value []byte 732 var wg sync.WaitGroup 733 reqCtx, cancel := context.WithCancel(context.Background()) 734 wg.Add(1) 735 go func() { 736 header := unittest.BlockHeaderFixture() 737 value, err = manager.ExecuteScript( 738 reqCtx, 739 script, 740 nil, 741 header, 742 nil) 743 wg.Done() 744 }() 745 cancel() 746 wg.Wait() 747 require.Nil(t, value) 748 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionCancelledError.String()) 749 } 750 751 func Test_EventEncodingFailsOnlyTxAndCarriesOn(t *testing.T) { 752 753 chain := flow.Mainnet.Chain() 754 vm := fvm.NewVirtualMachine() 755 756 eventEncoder := &testingEventEncoder{ 757 realEncoder: environment.NewCadenceEventEncoder(), 758 } 759 760 execCtx := fvm.NewContext( 761 fvm.WithChain(chain), 762 fvm.WithEventEncoder(eventEncoder), 763 ) 764 765 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 766 require.NoError(t, err) 767 snapshotTree, accounts, err := testutil.CreateAccounts( 768 vm, 769 testutil.RootBootstrappedLedger(vm, execCtx), 770 privateKeys, 771 chain) 772 require.NoError(t, err) 773 774 // setup transactions 775 account := accounts[0] 776 privKey := privateKeys[0] 777 // tx1 deploys contract version 1 778 tx1 := testutil.DeployEventContractTransaction(account, chain, 1) 779 prepareTx(t, tx1, account, privKey, 0, chain) 780 781 // tx2 emits event which will fail encoding 782 tx2 := testutil.CreateEmitEventTransaction(account, account) 783 prepareTx(t, tx2, account, privKey, 1, chain) 784 785 // tx3 emits event that will work fine 786 tx3 := testutil.CreateEmitEventTransaction(account, account) 787 prepareTx(t, tx3, account, privKey, 2, chain) 788 789 transactions := []*flow.TransactionBody{tx1, tx2, tx3} 790 791 col := flow.Collection{Transactions: transactions} 792 793 guarantee := flow.CollectionGuarantee{ 794 CollectionID: col.ID(), 795 Signature: nil, 796 } 797 798 block := flow.Block{ 799 Header: &flow.Header{ 800 View: 26, 801 }, 802 Payload: &flow.Payload{ 803 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 804 }, 805 } 806 807 executableBlock := &entity.ExecutableBlock{ 808 Block: &block, 809 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 810 guarantee.ID(): { 811 Guarantee: &guarantee, 812 Transactions: transactions, 813 }, 814 }, 815 StartState: unittest.StateCommitmentPointerFixture(), 816 } 817 818 me := new(module.Local) 819 me.On("NodeID").Return(unittest.IdentifierFixture()) 820 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 821 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 822 Return(nil, nil) 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 blockComputer, 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 derivedChainData, err := derived.NewDerivedChainData(10) 850 require.NoError(t, err) 851 852 engine := &Manager{ 853 blockComputer: blockComputer, 854 derivedChainData: derivedChainData, 855 } 856 857 eventEncoder.enabled = true 858 859 returnedComputationResult, err := engine.ComputeBlock( 860 context.Background(), 861 unittest.IdentifierFixture(), 862 executableBlock, 863 snapshotTree) 864 require.NoError(t, err) 865 866 txResults := returnedComputationResult.AllTransactionResults() 867 require.Len(t, txResults, 4) // 2 txs + 1 system tx 868 869 require.Empty(t, txResults[0].ErrorMessage) 870 require.Contains(t, txResults[1].ErrorMessage, "I failed encoding") 871 require.Empty(t, txResults[2].ErrorMessage) 872 873 colRes := returnedComputationResult.CollectionExecutionResultAt(0) 874 events := colRes.Events() 875 require.Len(t, events, 2) // 1 collection + 1 system chunk 876 877 // first event should be contract deployed 878 assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type) 879 880 // second event should come from tx3 (index 2) as tx2 (index 1) should fail encoding 881 hasValidEventValue(t, events[1], 1) 882 assert.Equal(t, events[1].TransactionIndex, uint32(2)) 883 } 884 885 type testingEventEncoder struct { 886 realEncoder *environment.CadenceEventEncoder 887 calls int 888 enabled bool 889 } 890 891 func (e *testingEventEncoder) Encode(event cadence.Event) ([]byte, error) { 892 defer func() { 893 if e.enabled { 894 e.calls++ 895 } 896 }() 897 898 if e.calls == 1 && e.enabled { 899 return nil, fmt.Errorf("I failed encoding") 900 } 901 return e.realEncoder.Encode(event) 902 } 903 904 func TestScriptStorageMutationsDiscarded(t *testing.T) { 905 906 timeout := 10 * time.Second 907 chain := flow.Mainnet.Chain() 908 ctx := fvm.NewContext(fvm.WithChain(chain)) 909 manager, _ := New( 910 zerolog.Nop(), 911 metrics.NewExecutionCollector(ctx.Tracer), 912 trace.NewNoopTracer(), 913 nil, 914 testutil.ProtocolStateWithSourceFixture(nil), 915 ctx, 916 committer.NewNoopViewCommitter(), 917 nil, 918 ComputationConfig{ 919 QueryConfig: query.QueryConfig{ 920 LogTimeThreshold: query.DefaultLogTimeThreshold, 921 ExecutionTimeLimit: timeout, 922 }, 923 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 924 MaxConcurrency: 1, 925 }, 926 ) 927 vm := manager.vm 928 929 // Create an account private key. 930 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 931 require.NoError(t, err) 932 933 // Bootstrap a ledger, creating accounts with the provided private keys 934 // and the root account. 935 snapshotTree, accounts, err := testutil.CreateAccounts( 936 vm, 937 testutil.RootBootstrappedLedger(vm, ctx), 938 privateKeys, 939 chain) 940 require.NoError(t, err) 941 account := accounts[0] 942 address := cadence.NewAddress(account) 943 commonAddress, _ := common.HexToAddress(address.Hex()) 944 945 script := []byte(` 946 pub fun main(account: Address) { 947 let acc = getAuthAccount(account) 948 acc.save(3, to: /storage/x) 949 } 950 `) 951 952 header := unittest.BlockHeaderFixture() 953 _, err = manager.ExecuteScript( 954 context.Background(), 955 script, 956 [][]byte{jsoncdc.MustEncode(address)}, 957 header, 958 snapshotTree) 959 960 require.NoError(t, err) 961 962 env := environment.NewScriptEnvironmentFromStorageSnapshot( 963 ctx.EnvironmentParams, 964 snapshotTree) 965 966 rt := env.BorrowCadenceRuntime() 967 defer env.ReturnCadenceRuntime(rt) 968 969 path, err := cadence.NewPath(common.PathDomainStorage, "x") 970 require.NoError(t, err) 971 972 v, err := rt.ReadStored(commonAddress, path) 973 // the save should not update account storage by writing the updates 974 // back to the snapshotTree 975 require.NoError(t, err) 976 require.Equal(t, nil, v) 977 }