github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/engine/execution" 24 "github.com/koko1123/flow-go-1/engine/execution/computation/committer" 25 "github.com/koko1123/flow-go-1/engine/execution/computation/computer" 26 state2 "github.com/koko1123/flow-go-1/engine/execution/state" 27 "github.com/koko1123/flow-go-1/engine/execution/state/delta" 28 unittest2 "github.com/koko1123/flow-go-1/engine/execution/state/unittest" 29 "github.com/koko1123/flow-go-1/engine/execution/testutil" 30 "github.com/koko1123/flow-go-1/fvm" 31 "github.com/koko1123/flow-go-1/fvm/derived" 32 "github.com/koko1123/flow-go-1/fvm/environment" 33 fvmErrors "github.com/koko1123/flow-go-1/fvm/errors" 34 "github.com/koko1123/flow-go-1/fvm/state" 35 "github.com/koko1123/flow-go-1/ledger/complete" 36 "github.com/koko1123/flow-go-1/ledger/complete/wal/fixtures" 37 "github.com/koko1123/flow-go-1/model/flow" 38 "github.com/koko1123/flow-go-1/module/executiondatasync/execution_data" 39 "github.com/koko1123/flow-go-1/module/executiondatasync/provider" 40 mocktracker "github.com/koko1123/flow-go-1/module/executiondatasync/tracker/mock" 41 "github.com/koko1123/flow-go-1/module/mempool/entity" 42 "github.com/koko1123/flow-go-1/module/metrics" 43 module "github.com/koko1123/flow-go-1/module/mock" 44 requesterunit "github.com/koko1123/flow-go-1/module/state_synchronization/requester/unittest" 45 "github.com/koko1123/flow-go-1/module/trace" 46 "github.com/koko1123/flow-go-1/utils/unittest" 47 ) 48 49 var scriptLogThreshold = 1 * time.Second 50 51 func TestComputeBlockWithStorage(t *testing.T) { 52 chain := flow.Mainnet.Chain() 53 54 vm := fvm.NewVirtualMachine() 55 execCtx := fvm.NewContext(fvm.WithChain(chain)) 56 57 privateKeys, err := testutil.GenerateAccountPrivateKeys(2) 58 require.NoError(t, err) 59 60 ledger := testutil.RootBootstrappedLedger(vm, execCtx) 61 accounts, err := testutil.CreateAccounts(vm, ledger, derived.NewEmptyDerivedBlockData(), privateKeys, chain) 62 require.NoError(t, err) 63 64 tx1 := testutil.DeployCounterContractTransaction(accounts[0], chain) 65 tx1.SetProposalKey(chain.ServiceAddress(), 0, 0). 66 SetGasLimit(1000). 67 SetPayer(chain.ServiceAddress()) 68 69 err = testutil.SignPayload(tx1, accounts[0], privateKeys[0]) 70 require.NoError(t, err) 71 72 err = testutil.SignEnvelope(tx1, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 73 require.NoError(t, err) 74 75 tx2 := testutil.CreateCounterTransaction(accounts[0], accounts[1]) 76 tx2.SetProposalKey(chain.ServiceAddress(), 0, 0). 77 SetGasLimit(1000). 78 SetPayer(chain.ServiceAddress()) 79 80 err = testutil.SignPayload(tx2, accounts[1], privateKeys[1]) 81 require.NoError(t, err) 82 83 err = testutil.SignEnvelope(tx2, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 84 require.NoError(t, err) 85 86 transactions := []*flow.TransactionBody{tx1, tx2} 87 88 col := flow.Collection{Transactions: transactions} 89 90 guarantee := flow.CollectionGuarantee{ 91 CollectionID: col.ID(), 92 Signature: nil, 93 } 94 95 block := flow.Block{ 96 Header: &flow.Header{ 97 View: 42, 98 }, 99 Payload: &flow.Payload{ 100 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 101 }, 102 } 103 104 executableBlock := &entity.ExecutableBlock{ 105 Block: &block, 106 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 107 guarantee.ID(): { 108 Guarantee: &guarantee, 109 Transactions: transactions, 110 }, 111 }, 112 StartState: unittest.StateCommitmentPointerFixture(), 113 } 114 115 me := new(module.Local) 116 me.On("NodeID").Return(flow.ZeroID) 117 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 118 Return(nil, nil) 119 120 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 121 trackerStorage := mocktracker.NewMockStorage() 122 123 prov := provider.NewProvider( 124 zerolog.Nop(), 125 metrics.NewNoopCollector(), 126 execution_data.DefaultSerializer, 127 bservice, 128 trackerStorage, 129 ) 130 131 blockComputer, err := computer.NewBlockComputer( 132 vm, 133 execCtx, 134 metrics.NewNoopCollector(), 135 trace.NewNoopTracer(), 136 zerolog.Nop(), 137 committer.NewNoopViewCommitter(), 138 me, 139 prov) 140 require.NoError(t, err) 141 142 derivedChainData, err := derived.NewDerivedChainData(10) 143 require.NoError(t, err) 144 145 engine := &Manager{ 146 blockComputer: blockComputer, 147 me: me, 148 derivedChainData: derivedChainData, 149 tracer: trace.NewNoopTracer(), 150 } 151 152 view := delta.NewView(ledger.Get) 153 blockView := view.NewChild() 154 155 returnedComputationResult, err := engine.ComputeBlock(context.Background(), executableBlock, blockView) 156 require.NoError(t, err) 157 158 require.NotEmpty(t, blockView.(*delta.View).Delta()) 159 require.Len(t, returnedComputationResult.StateSnapshots, 1+1) // 1 coll + 1 system chunk 160 assert.NotEmpty(t, returnedComputationResult.StateSnapshots[0].Delta) 161 stats := returnedComputationResult.BlockStats() 162 assert.True(t, stats.ComputationUsed > 0) 163 assert.True(t, stats.MemoryUsed > 0) 164 } 165 166 func TestComputeBlock_Uploader(t *testing.T) { 167 168 noopCollector := &metrics.NoopCollector{} 169 170 ledger, err := complete.NewLedger(&fixtures.NoopWAL{}, 10, noopCollector, zerolog.Nop(), complete.DefaultPathFinderVersion) 171 require.NoError(t, err) 172 173 compactor := fixtures.NewNoopCompactor(ledger) 174 <-compactor.Ready() 175 defer func() { 176 <-ledger.Done() 177 <-compactor.Done() 178 }() 179 180 me := new(module.Local) 181 me.On("NodeID").Return(flow.ZeroID) 182 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 183 Return(nil, nil) 184 185 computationResult := unittest2.ComputationResultFixture([][]flow.Identifier{ 186 {unittest.IdentifierFixture()}, 187 {unittest.IdentifierFixture()}, 188 }) 189 190 blockComputer := &FakeBlockComputer{ 191 computationResult: computationResult, 192 } 193 194 derivedChainData, err := derived.NewDerivedChainData(10) 195 require.NoError(t, err) 196 197 manager := &Manager{ 198 blockComputer: blockComputer, 199 me: me, 200 derivedChainData: derivedChainData, 201 tracer: trace.NewNoopTracer(), 202 } 203 204 view := delta.NewView(state2.LedgerGetRegister(ledger, flow.StateCommitment(ledger.InitialState()))) 205 blockView := view.NewChild() 206 207 _, err = manager.ComputeBlock(context.Background(), computationResult.ExecutableBlock, blockView) 208 require.NoError(t, err) 209 } 210 211 func TestExecuteScript(t *testing.T) { 212 213 logger := zerolog.Nop() 214 215 execCtx := fvm.NewContext(fvm.WithLogger(logger)) 216 217 me := new(module.Local) 218 me.On("NodeID").Return(flow.ZeroID) 219 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 220 Return(nil, nil) 221 222 vm := fvm.NewVirtualMachine() 223 224 ledger := testutil.RootBootstrappedLedger(vm, execCtx, fvm.WithExecutionMemoryLimit(math.MaxUint64)) 225 226 view := delta.NewView(ledger.Get) 227 228 scriptView := view.NewChild() 229 230 script := []byte(fmt.Sprintf( 231 ` 232 import FungibleToken from %s 233 234 pub fun main() {} 235 `, 236 fvm.FungibleTokenAddress(execCtx.Chain).HexWithPrefix(), 237 )) 238 239 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 240 trackerStorage := mocktracker.NewMockStorage() 241 242 prov := provider.NewProvider( 243 zerolog.Nop(), 244 metrics.NewNoopCollector(), 245 execution_data.DefaultSerializer, 246 bservice, 247 trackerStorage, 248 ) 249 250 engine, err := New(logger, 251 metrics.NewNoopCollector(), 252 trace.NewNoopTracer(), 253 me, 254 nil, 255 execCtx, 256 committer.NewNoopViewCommitter(), 257 prov, 258 ComputationConfig{ 259 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 260 ScriptLogThreshold: scriptLogThreshold, 261 ScriptExecutionTimeLimit: DefaultScriptExecutionTimeLimit, 262 }, 263 ) 264 require.NoError(t, err) 265 266 header := unittest.BlockHeaderFixture() 267 _, err = engine.ExecuteScript(context.Background(), script, nil, header, scriptView) 268 require.NoError(t, err) 269 } 270 271 // Balance script used to swallow errors, which meant that even if the view was empty, a script that did nothing but get 272 // the balance of an account would succeed and return 0. 273 func TestExecuteScript_BalanceScriptFailsIfViewIsEmpty(t *testing.T) { 274 275 logger := zerolog.Nop() 276 277 execCtx := fvm.NewContext(fvm.WithLogger(logger)) 278 279 me := new(module.Local) 280 me.On("NodeID").Return(flow.ZeroID) 281 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 282 Return(nil, nil) 283 284 view := delta.NewView(func(owner, key string) (flow.RegisterValue, error) { 285 return nil, fmt.Errorf("error getting register") 286 }) 287 288 scriptView := view.NewChild() 289 290 script := []byte(fmt.Sprintf( 291 ` 292 pub fun main(): UFix64 { 293 return getAccount(%s).balance 294 } 295 `, 296 fvm.FungibleTokenAddress(execCtx.Chain).HexWithPrefix(), 297 )) 298 299 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 300 trackerStorage := mocktracker.NewMockStorage() 301 302 prov := provider.NewProvider( 303 zerolog.Nop(), 304 metrics.NewNoopCollector(), 305 execution_data.DefaultSerializer, 306 bservice, 307 trackerStorage, 308 ) 309 310 engine, err := New(logger, 311 metrics.NewNoopCollector(), 312 trace.NewNoopTracer(), 313 me, 314 nil, 315 execCtx, 316 committer.NewNoopViewCommitter(), 317 prov, 318 ComputationConfig{ 319 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 320 ScriptLogThreshold: scriptLogThreshold, 321 ScriptExecutionTimeLimit: DefaultScriptExecutionTimeLimit, 322 }, 323 ) 324 require.NoError(t, err) 325 326 header := unittest.BlockHeaderFixture() 327 _, err = engine.ExecuteScript(context.Background(), script, nil, header, scriptView) 328 require.ErrorContains(t, err, "error getting register") 329 } 330 331 func TestExecuteScripPanicsAreHandled(t *testing.T) { 332 333 ctx := fvm.NewContext() 334 335 buffer := &bytes.Buffer{} 336 log := zerolog.New(buffer) 337 338 header := unittest.BlockHeaderFixture() 339 340 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 341 trackerStorage := mocktracker.NewMockStorage() 342 343 prov := provider.NewProvider( 344 zerolog.Nop(), 345 metrics.NewNoopCollector(), 346 execution_data.DefaultSerializer, 347 bservice, 348 trackerStorage, 349 ) 350 351 manager, err := New(log, 352 metrics.NewNoopCollector(), 353 trace.NewNoopTracer(), 354 nil, 355 nil, 356 ctx, 357 committer.NewNoopViewCommitter(), 358 prov, 359 ComputationConfig{ 360 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 361 ScriptLogThreshold: scriptLogThreshold, 362 ScriptExecutionTimeLimit: DefaultScriptExecutionTimeLimit, 363 NewCustomVirtualMachine: func() fvm.VM { 364 return &PanickingVM{} 365 }, 366 }, 367 ) 368 require.NoError(t, err) 369 370 _, err = manager.ExecuteScript(context.Background(), []byte("whatever"), nil, header, noopView()) 371 372 require.Error(t, err) 373 374 require.Contains(t, buffer.String(), "Verunsicherung") 375 } 376 377 func TestExecuteScript_LongScriptsAreLogged(t *testing.T) { 378 379 ctx := fvm.NewContext() 380 381 buffer := &bytes.Buffer{} 382 log := zerolog.New(buffer) 383 384 header := unittest.BlockHeaderFixture() 385 386 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 387 trackerStorage := mocktracker.NewMockStorage() 388 389 prov := provider.NewProvider( 390 zerolog.Nop(), 391 metrics.NewNoopCollector(), 392 execution_data.DefaultSerializer, 393 bservice, 394 trackerStorage, 395 ) 396 397 manager, err := New(log, 398 metrics.NewNoopCollector(), 399 trace.NewNoopTracer(), 400 nil, 401 nil, 402 ctx, 403 committer.NewNoopViewCommitter(), 404 prov, 405 ComputationConfig{ 406 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 407 ScriptLogThreshold: 1 * time.Millisecond, 408 ScriptExecutionTimeLimit: DefaultScriptExecutionTimeLimit, 409 NewCustomVirtualMachine: func() fvm.VM { 410 return &LongRunningVM{duration: 2 * time.Millisecond} 411 }, 412 }, 413 ) 414 require.NoError(t, err) 415 416 _, err = manager.ExecuteScript(context.Background(), []byte("whatever"), nil, header, noopView()) 417 418 require.NoError(t, err) 419 420 require.Contains(t, buffer.String(), "exceeded threshold") 421 } 422 423 func TestExecuteScript_ShortScriptsAreNotLogged(t *testing.T) { 424 425 ctx := fvm.NewContext() 426 427 buffer := &bytes.Buffer{} 428 log := zerolog.New(buffer) 429 430 header := unittest.BlockHeaderFixture() 431 432 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 433 trackerStorage := mocktracker.NewMockStorage() 434 435 prov := provider.NewProvider( 436 zerolog.Nop(), 437 metrics.NewNoopCollector(), 438 execution_data.DefaultSerializer, 439 bservice, 440 trackerStorage, 441 ) 442 443 manager, err := New(log, 444 metrics.NewNoopCollector(), 445 trace.NewNoopTracer(), 446 nil, 447 nil, 448 ctx, 449 committer.NewNoopViewCommitter(), 450 prov, 451 ComputationConfig{ 452 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 453 ScriptLogThreshold: 1 * time.Second, 454 ScriptExecutionTimeLimit: DefaultScriptExecutionTimeLimit, 455 NewCustomVirtualMachine: func() fvm.VM { 456 return &LongRunningVM{duration: 0} 457 }, 458 }, 459 ) 460 require.NoError(t, err) 461 462 _, err = manager.ExecuteScript(context.Background(), []byte("whatever"), nil, header, noopView()) 463 464 require.NoError(t, err) 465 466 require.NotContains(t, buffer.String(), "exceeded threshold") 467 } 468 469 type PanickingVM struct{} 470 471 func (p *PanickingVM) Run(f fvm.Context, procedure fvm.Procedure, view state.View) error { 472 panic("panic, but expected with sentinel for test: Verunsicherung ") 473 } 474 475 func (p *PanickingVM) GetAccount(f fvm.Context, address flow.Address, view state.View) (*flow.Account, error) { 476 panic("not expected") 477 } 478 479 type LongRunningVM struct { 480 duration time.Duration 481 } 482 483 func (l *LongRunningVM) Run(f fvm.Context, procedure fvm.Procedure, view state.View) error { 484 time.Sleep(l.duration) 485 // satisfy value marshaller 486 if scriptProcedure, is := procedure.(*fvm.ScriptProcedure); is { 487 scriptProcedure.Value = cadence.NewVoid() 488 } 489 490 return nil 491 } 492 493 func (l *LongRunningVM) GetAccount(f fvm.Context, address flow.Address, view state.View) (*flow.Account, error) { 494 panic("not expected") 495 } 496 497 type FakeBlockComputer struct { 498 computationResult *execution.ComputationResult 499 } 500 501 func (f *FakeBlockComputer) ExecuteBlock(context.Context, *entity.ExecutableBlock, state.View, *derived.DerivedBlockData) (*execution.ComputationResult, error) { 502 return f.computationResult, nil 503 } 504 505 func noopView() *delta.View { 506 return delta.NewView(func(_, _ string) (flow.RegisterValue, error) { 507 return nil, nil 508 }) 509 } 510 511 func TestExecuteScriptTimeout(t *testing.T) { 512 513 timeout := 1 * time.Millisecond 514 manager, err := New( 515 zerolog.Nop(), 516 metrics.NewNoopCollector(), 517 trace.NewNoopTracer(), 518 nil, 519 nil, 520 fvm.NewContext(), 521 committer.NewNoopViewCommitter(), 522 nil, 523 ComputationConfig{ 524 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 525 ScriptLogThreshold: DefaultScriptLogThreshold, 526 ScriptExecutionTimeLimit: timeout, 527 }, 528 ) 529 530 require.NoError(t, err) 531 532 script := []byte(` 533 pub fun main(): Int { 534 var i = 0 535 while i < 10000 { 536 i = i + 1 537 } 538 return i 539 } 540 `) 541 542 header := unittest.BlockHeaderFixture() 543 value, err := manager.ExecuteScript(context.Background(), script, nil, header, noopView()) 544 545 require.Error(t, err) 546 require.Nil(t, value) 547 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionTimedOutError.String()) 548 } 549 550 func TestExecuteScriptCancelled(t *testing.T) { 551 552 timeout := 30 * time.Second 553 manager, err := New( 554 zerolog.Nop(), 555 metrics.NewNoopCollector(), 556 trace.NewNoopTracer(), 557 nil, 558 nil, 559 fvm.NewContext(), 560 committer.NewNoopViewCommitter(), 561 nil, 562 ComputationConfig{ 563 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 564 ScriptLogThreshold: DefaultScriptLogThreshold, 565 ScriptExecutionTimeLimit: timeout, 566 }, 567 ) 568 569 require.NoError(t, err) 570 571 script := []byte(` 572 pub fun main(): Int { 573 var i = 0 574 var j = 0 575 while i < 10000000 { 576 i = i + 1 577 j = i + j 578 } 579 return i 580 } 581 `) 582 583 var value []byte 584 var wg sync.WaitGroup 585 reqCtx, cancel := context.WithCancel(context.Background()) 586 wg.Add(1) 587 go func() { 588 header := unittest.BlockHeaderFixture() 589 value, err = manager.ExecuteScript(reqCtx, script, nil, header, noopView()) 590 wg.Done() 591 }() 592 cancel() 593 wg.Wait() 594 require.Nil(t, value) 595 require.Contains(t, err.Error(), fvmErrors.ErrCodeScriptExecutionCancelledError.String()) 596 } 597 598 func Test_EventEncodingFailsOnlyTxAndCarriesOn(t *testing.T) { 599 600 chain := flow.Mainnet.Chain() 601 vm := fvm.NewVirtualMachine() 602 603 eventEncoder := &testingEventEncoder{ 604 realEncoder: environment.NewCadenceEventEncoder(), 605 } 606 607 execCtx := fvm.NewContext( 608 fvm.WithChain(chain), 609 fvm.WithEventEncoder(eventEncoder), 610 ) 611 612 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 613 require.NoError(t, err) 614 ledger := testutil.RootBootstrappedLedger(vm, execCtx) 615 accounts, err := testutil.CreateAccounts(vm, ledger, derived.NewEmptyDerivedBlockData(), privateKeys, chain) 616 require.NoError(t, err) 617 618 // setup transactions 619 account := accounts[0] 620 privKey := privateKeys[0] 621 // tx1 deploys contract version 1 622 tx1 := testutil.DeployEventContractTransaction(account, chain, 1) 623 prepareTx(t, tx1, account, privKey, 0, chain) 624 625 // tx2 emits event which will fail encoding 626 tx2 := testutil.CreateEmitEventTransaction(account, account) 627 prepareTx(t, tx2, account, privKey, 1, chain) 628 629 // tx3 emits event that will work fine 630 tx3 := testutil.CreateEmitEventTransaction(account, account) 631 prepareTx(t, tx3, account, privKey, 2, chain) 632 633 transactions := []*flow.TransactionBody{tx1, tx2, tx3} 634 635 col := flow.Collection{Transactions: transactions} 636 637 guarantee := flow.CollectionGuarantee{ 638 CollectionID: col.ID(), 639 Signature: nil, 640 } 641 642 block := flow.Block{ 643 Header: &flow.Header{ 644 View: 26, 645 }, 646 Payload: &flow.Payload{ 647 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 648 }, 649 } 650 651 executableBlock := &entity.ExecutableBlock{ 652 Block: &block, 653 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 654 guarantee.ID(): { 655 Guarantee: &guarantee, 656 Transactions: transactions, 657 }, 658 }, 659 StartState: unittest.StateCommitmentPointerFixture(), 660 } 661 662 me := new(module.Local) 663 me.On("NodeID").Return(flow.ZeroID) 664 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 665 Return(nil, nil) 666 667 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 668 trackerStorage := mocktracker.NewMockStorage() 669 670 prov := provider.NewProvider( 671 zerolog.Nop(), 672 metrics.NewNoopCollector(), 673 execution_data.DefaultSerializer, 674 bservice, 675 trackerStorage, 676 ) 677 678 blockComputer, err := computer.NewBlockComputer( 679 vm, 680 execCtx, 681 metrics.NewNoopCollector(), 682 trace.NewNoopTracer(), 683 zerolog.Nop(), 684 committer.NewNoopViewCommitter(), 685 me, 686 prov, 687 ) 688 require.NoError(t, err) 689 690 derivedChainData, err := derived.NewDerivedChainData(10) 691 require.NoError(t, err) 692 693 engine := &Manager{ 694 blockComputer: blockComputer, 695 me: me, 696 derivedChainData: derivedChainData, 697 tracer: trace.NewNoopTracer(), 698 } 699 700 view := delta.NewView(ledger.Get) 701 blockView := view.NewChild() 702 703 eventEncoder.enabled = true 704 705 returnedComputationResult, err := engine.ComputeBlock(context.Background(), executableBlock, blockView) 706 require.NoError(t, err) 707 708 require.Len(t, returnedComputationResult.Events, 2) // 1 collection + 1 system chunk 709 require.Len(t, returnedComputationResult.TransactionResults, 4) // 2 txs + 1 system tx 710 711 require.Empty(t, returnedComputationResult.TransactionResults[0].ErrorMessage) 712 require.Contains(t, returnedComputationResult.TransactionResults[1].ErrorMessage, "I failed encoding") 713 require.Empty(t, returnedComputationResult.TransactionResults[2].ErrorMessage) 714 715 // first event should be contract deployed 716 assert.EqualValues(t, "flow.AccountContractAdded", returnedComputationResult.Events[0][0].Type) 717 718 // second event should come from tx3 (index 2) as tx2 (index 1) should fail encoding 719 hasValidEventValue(t, returnedComputationResult.Events[0][1], 1) 720 assert.Equal(t, returnedComputationResult.Events[0][1].TransactionIndex, uint32(2)) 721 } 722 723 type testingEventEncoder struct { 724 realEncoder *environment.CadenceEventEncoder 725 calls int 726 enabled bool 727 } 728 729 func (e *testingEventEncoder) Encode(event cadence.Event) ([]byte, error) { 730 defer func() { 731 if e.enabled { 732 e.calls++ 733 } 734 }() 735 736 if e.calls == 1 && e.enabled { 737 return nil, fmt.Errorf("I failed encoding") 738 } 739 return e.realEncoder.Encode(event) 740 } 741 742 func TestScriptStorageMutationsDiscarded(t *testing.T) { 743 744 timeout := 10 * time.Second 745 chain := flow.Mainnet.Chain() 746 ctx := fvm.NewContext(fvm.WithChain(chain)) 747 manager, _ := New( 748 zerolog.Nop(), 749 metrics.NewExecutionCollector(ctx.Tracer), 750 trace.NewNoopTracer(), 751 nil, 752 nil, 753 ctx, 754 committer.NewNoopViewCommitter(), 755 nil, 756 ComputationConfig{ 757 DerivedDataCacheSize: derived.DefaultDerivedDataCacheSize, 758 ScriptLogThreshold: DefaultScriptLogThreshold, 759 ScriptExecutionTimeLimit: timeout, 760 }, 761 ) 762 vm := manager.vm 763 view := testutil.RootBootstrappedLedger(vm, ctx) 764 765 derivedBlockData := derived.NewEmptyDerivedBlockData() 766 derivedTxnData, err := derivedBlockData.NewDerivedTransactionData(0, 0) 767 require.NoError(t, err) 768 769 txnState := state.NewTransactionState(view, state.DefaultParameters()) 770 771 // Create an account private key. 772 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 773 require.NoError(t, err) 774 775 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 776 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 777 require.NoError(t, err) 778 account := accounts[0] 779 address := cadence.NewAddress(account) 780 commonAddress, _ := common.HexToAddress(address.Hex()) 781 782 script := []byte(` 783 pub fun main(account: Address) { 784 let acc = getAuthAccount(account) 785 acc.save(3, to: /storage/x) 786 } 787 `) 788 789 header := unittest.BlockHeaderFixture() 790 scriptView := view.NewChild() 791 _, err = manager.ExecuteScript(context.Background(), script, [][]byte{jsoncdc.MustEncode(address)}, header, scriptView) 792 793 require.NoError(t, err) 794 795 env := environment.NewScriptEnvironment( 796 context.Background(), 797 ctx.TracerSpan, 798 ctx.EnvironmentParams, 799 txnState, 800 derivedTxnData) 801 802 rt := env.BorrowCadenceRuntime() 803 defer env.ReturnCadenceRuntime(rt) 804 805 v, err := rt.ReadStored( 806 commonAddress, 807 cadence.NewPath("storage", "x"), 808 ) 809 810 // the save should not update account storage by writing the delta from the child view back to the parent 811 require.NoError(t, err) 812 require.Equal(t, nil, v) 813 }