github.com/onflow/flow-go@v0.33.17/engine/execution/computation/programs_test.go (about) 1 package computation 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/ipfs/go-datastore" 10 dssync "github.com/ipfs/go-datastore/sync" 11 blockstore "github.com/ipfs/go-ipfs-blockstore" 12 "github.com/onflow/cadence" 13 "github.com/onflow/cadence/encoding/ccf" 14 "github.com/rs/zerolog" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 19 "github.com/onflow/flow-go/engine/execution" 20 "github.com/onflow/flow-go/engine/execution/computation/committer" 21 "github.com/onflow/flow-go/engine/execution/computation/computer" 22 "github.com/onflow/flow-go/engine/execution/testutil" 23 "github.com/onflow/flow-go/fvm" 24 "github.com/onflow/flow-go/fvm/storage/derived" 25 "github.com/onflow/flow-go/fvm/storage/snapshot" 26 "github.com/onflow/flow-go/model/flow" 27 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 28 "github.com/onflow/flow-go/module/executiondatasync/provider" 29 mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock" 30 "github.com/onflow/flow-go/module/mempool/entity" 31 "github.com/onflow/flow-go/module/metrics" 32 module "github.com/onflow/flow-go/module/mock" 33 requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest" 34 "github.com/onflow/flow-go/module/trace" 35 "github.com/onflow/flow-go/utils/unittest" 36 ) 37 38 const ( 39 testMaxConcurrency = 2 40 ) 41 42 func TestPrograms_TestContractUpdates(t *testing.T) { 43 chain := flow.Mainnet.Chain() 44 vm := fvm.NewVirtualMachine() 45 execCtx := fvm.NewContext(fvm.WithChain(chain)) 46 47 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 48 require.NoError(t, err) 49 snapshotTree, accounts, err := testutil.CreateAccounts( 50 vm, 51 testutil.RootBootstrappedLedger(vm, execCtx), 52 privateKeys, 53 chain) 54 require.NoError(t, err) 55 56 // setup transactions 57 account := accounts[0] 58 privKey := privateKeys[0] 59 // tx1 deploys contract version 1 60 tx1 := testutil.DeployEventContractTransaction(account, chain, 1) 61 prepareTx(t, tx1, account, privKey, 0, chain) 62 63 // tx2 calls the method of the contract (version 1) 64 tx2 := testutil.CreateEmitEventTransaction(account, account) 65 prepareTx(t, tx2, account, privKey, 1, chain) 66 67 // tx3 updates the contract to version 2 68 tx3 := testutil.UpdateEventContractTransaction(account, chain, 2) 69 prepareTx(t, tx3, account, privKey, 2, chain) 70 71 // tx4 calls the method of the contract (version 2) 72 tx4 := testutil.CreateEmitEventTransaction(account, account) 73 prepareTx(t, tx4, account, privKey, 3, chain) 74 75 // tx5 updates the contract to version 3 but fails (no env signature of service account) 76 tx5 := testutil.UnauthorizedDeployEventContractTransaction(account, chain, 3) 77 tx5.SetProposalKey(account, 0, 4).SetPayer(account) 78 err = testutil.SignEnvelope(tx5, account, privKey) 79 require.NoError(t, err) 80 81 // tx6 calls the method of the contract (version 2 expected) 82 tx6 := testutil.CreateEmitEventTransaction(account, account) 83 prepareTx(t, tx6, account, privKey, 5, chain) 84 85 transactions := []*flow.TransactionBody{tx1, tx2, tx3, tx4, tx5, tx6} 86 87 col := flow.Collection{Transactions: transactions} 88 89 guarantee := flow.CollectionGuarantee{ 90 CollectionID: col.ID(), 91 Signature: nil, 92 } 93 94 block := flow.Block{ 95 Header: &flow.Header{ 96 View: 26, 97 }, 98 Payload: &flow.Payload{ 99 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 100 }, 101 } 102 103 executableBlock := &entity.ExecutableBlock{ 104 Block: &block, 105 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 106 guarantee.ID(): { 107 Guarantee: &guarantee, 108 Transactions: transactions, 109 }, 110 }, 111 StartState: unittest.StateCommitmentPointerFixture(), 112 } 113 114 me := new(module.Local) 115 me.On("NodeID").Return(unittest.IdentifierFixture()) 116 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 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 nil, 141 testutil.ProtocolStateWithSourceFixture(nil), 142 testMaxConcurrency) 143 require.NoError(t, err) 144 145 derivedChainData, err := derived.NewDerivedChainData(10) 146 require.NoError(t, err) 147 148 engine := &Manager{ 149 blockComputer: blockComputer, 150 derivedChainData: derivedChainData, 151 } 152 153 returnedComputationResult, err := engine.ComputeBlock( 154 context.Background(), 155 unittest.IdentifierFixture(), 156 executableBlock, 157 snapshotTree) 158 require.NoError(t, err) 159 160 events := returnedComputationResult.AllEvents() 161 162 // first event should be contract deployed 163 assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type) 164 165 // second event should have a value of 1 (since is calling version 1 of contract) 166 hasValidEventValue(t, events[1], 1) 167 168 // third event should be contract updated 169 assert.EqualValues(t, "flow.AccountContractUpdated", events[2].Type) 170 171 // 4th event should have a value of 2 (since is calling version 2 of contract) 172 hasValidEventValue(t, events[3], 2) 173 174 // 5th event should have a value of 2 (since is calling version 2 of contract) 175 hasValidEventValue(t, events[4], 2) 176 } 177 178 type blockProvider struct { 179 blocks map[uint64]*flow.Block 180 } 181 182 func (b blockProvider) ByHeightFrom(height uint64, _ *flow.Header) (*flow.Header, error) { 183 block, has := b.blocks[height] 184 if has { 185 return block.Header, nil 186 } 187 return nil, fmt.Errorf("block for height (%d) is not available", height) 188 } 189 190 // TestPrograms_TestBlockForks tests the functionality of 191 // derivedChainData under contract deployment and contract updates on 192 // different block forks 193 // 194 // block structure and operations 195 // Block1 (empty block) 196 // 197 // -> Block11 (deploy contract v1) 198 // -> Block111 (emit event - version should be 1) and (update contract to v3) 199 // -> Block1111 (emit event - version should be 3) 200 // -> Block112 (emit event - version should be 1) and (update contract to v4) 201 // -> Block1121 (emit event - version should be 4) 202 // -> Block12 (deploy contract v2) 203 // -> Block121 (emit event - version should be 2) 204 // -> Block1211 (emit event - version should be 2) 205 func TestPrograms_TestBlockForks(t *testing.T) { 206 block := unittest.BlockFixture() 207 chain := flow.Emulator.Chain() 208 vm := fvm.NewVirtualMachine() 209 execCtx := fvm.NewContext( 210 fvm.WithBlockHeader(block.Header), 211 fvm.WithBlocks(blockProvider{map[uint64]*flow.Block{0: &block}}), 212 fvm.WithChain(chain)) 213 214 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 215 require.NoError(t, err) 216 snapshotTree, accounts, err := testutil.CreateAccounts( 217 vm, 218 testutil.RootBootstrappedLedger(vm, execCtx), 219 privateKeys, 220 chain) 221 require.NoError(t, err) 222 223 account := accounts[0] 224 privKey := privateKeys[0] 225 226 me := new(module.Local) 227 me.On("NodeID").Return(unittest.IdentifierFixture()) 228 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 229 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 230 Return(nil, nil) 231 232 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 233 trackerStorage := mocktracker.NewMockStorage() 234 235 prov := provider.NewProvider( 236 zerolog.Nop(), 237 metrics.NewNoopCollector(), 238 execution_data.DefaultSerializer, 239 bservice, 240 trackerStorage, 241 ) 242 243 blockComputer, err := computer.NewBlockComputer( 244 vm, 245 execCtx, 246 metrics.NewNoopCollector(), 247 trace.NewNoopTracer(), 248 zerolog.Nop(), 249 committer.NewNoopViewCommitter(), 250 me, 251 prov, 252 nil, 253 testutil.ProtocolStateWithSourceFixture(nil), 254 testMaxConcurrency) 255 require.NoError(t, err) 256 257 derivedChainData, err := derived.NewDerivedChainData(10) 258 require.NoError(t, err) 259 260 engine := &Manager{ 261 blockComputer: blockComputer, 262 derivedChainData: derivedChainData, 263 } 264 265 var ( 266 res *execution.ComputationResult 267 268 block1, block11, block111, block112, block1121, 269 block1111, block12, block121, block1211 *flow.Block 270 271 block1Snapshot, block11Snapshot, block111Snapshot, block112Snapshot, 272 block12Snapshot, block121Snapshot snapshot.SnapshotTree 273 ) 274 275 t.Run("executing block1 (no collection)", func(t *testing.T) { 276 block1 = &flow.Block{ 277 Header: &flow.Header{ 278 View: 1, 279 }, 280 Payload: &flow.Payload{ 281 Guarantees: []*flow.CollectionGuarantee{}, 282 }, 283 } 284 block1Snapshot = snapshotTree 285 executableBlock := &entity.ExecutableBlock{ 286 Block: block1, 287 StartState: unittest.StateCommitmentPointerFixture(), 288 } 289 _, err := engine.ComputeBlock( 290 context.Background(), 291 unittest.IdentifierFixture(), 292 executableBlock, 293 block1Snapshot) 294 require.NoError(t, err) 295 }) 296 297 t.Run("executing block11 (deploys contract version 1)", func(t *testing.T) { 298 block11tx1 := testutil.DeployEventContractTransaction(account, chain, 1) 299 prepareTx(t, block11tx1, account, privKey, 0, chain) 300 301 txs11 := []*flow.TransactionBody{block11tx1} 302 col11 := flow.Collection{Transactions: txs11} 303 block11, res, block11Snapshot = createTestBlockAndRun( 304 t, 305 engine, 306 block1, 307 col11, 308 block1Snapshot) 309 // cache should include value for this block 310 require.NotNil(t, derivedChainData.Get(block11.ID())) 311 // 1st event should be contract deployed 312 313 assert.EqualValues(t, "flow.AccountContractAdded", res.AllEvents()[0].Type) 314 }) 315 316 t.Run("executing block111 (emit event (expected v1), update contract to v3)", func(t *testing.T) { 317 block111ExpectedValue := 1 318 // emit event 319 block111tx1 := testutil.CreateEmitEventTransaction(account, account) 320 prepareTx(t, block111tx1, account, privKey, 1, chain) 321 322 // update contract version 3 323 block111tx2 := testutil.UpdateEventContractTransaction(account, chain, 3) 324 prepareTx(t, block111tx2, account, privKey, 2, chain) 325 326 col111 := flow.Collection{Transactions: []*flow.TransactionBody{block111tx1, block111tx2}} 327 block111, res, block111Snapshot = createTestBlockAndRun( 328 t, 329 engine, 330 block11, 331 col111, 332 block11Snapshot) 333 // cache should include a program for this block 334 require.NotNil(t, derivedChainData.Get(block111.ID())) 335 336 events := res.AllEvents() 337 require.Equal(t, res.BlockExecutionResult.Size(), 2) 338 339 // 1st event 340 hasValidEventValue(t, events[0], block111ExpectedValue) 341 // second event should be contract deployed 342 assert.EqualValues(t, "flow.AccountContractUpdated", events[1].Type) 343 }) 344 345 t.Run("executing block1111 (emit event (expected v3))", func(t *testing.T) { 346 block1111ExpectedValue := 3 347 block1111tx1 := testutil.CreateEmitEventTransaction(account, account) 348 prepareTx(t, block1111tx1, account, privKey, 3, chain) 349 350 col1111 := flow.Collection{Transactions: []*flow.TransactionBody{block1111tx1}} 351 block1111, res, _ = createTestBlockAndRun( 352 t, 353 engine, 354 block111, 355 col1111, 356 block111Snapshot) 357 // cache should include a program for this block 358 require.NotNil(t, derivedChainData.Get(block1111.ID())) 359 360 events := res.AllEvents() 361 require.Equal(t, res.BlockExecutionResult.Size(), 2) 362 363 // 1st event 364 hasValidEventValue(t, events[0], block1111ExpectedValue) 365 }) 366 367 t.Run("executing block112 (emit event (expected v1))", func(t *testing.T) { 368 block112ExpectedValue := 1 369 block112tx1 := testutil.CreateEmitEventTransaction(account, account) 370 prepareTx(t, block112tx1, account, privKey, 1, chain) 371 372 // update contract version 4 373 block112tx2 := testutil.UpdateEventContractTransaction(account, chain, 4) 374 prepareTx(t, block112tx2, account, privKey, 2, chain) 375 376 col112 := flow.Collection{Transactions: []*flow.TransactionBody{block112tx1, block112tx2}} 377 block112, res, block112Snapshot = createTestBlockAndRun( 378 t, 379 engine, 380 block11, 381 col112, 382 block11Snapshot) 383 // cache should include a program for this block 384 require.NotNil(t, derivedChainData.Get(block112.ID())) 385 386 events := res.AllEvents() 387 require.Equal(t, res.BlockExecutionResult.Size(), 2) 388 389 // 1st event 390 hasValidEventValue(t, events[0], block112ExpectedValue) 391 // second event should be contract deployed 392 assert.EqualValues(t, "flow.AccountContractUpdated", events[1].Type) 393 394 }) 395 t.Run("executing block1121 (emit event (expected v4))", func(t *testing.T) { 396 block1121ExpectedValue := 4 397 block1121tx1 := testutil.CreateEmitEventTransaction(account, account) 398 prepareTx(t, block1121tx1, account, privKey, 3, chain) 399 400 col1121 := flow.Collection{Transactions: []*flow.TransactionBody{block1121tx1}} 401 block1121, res, _ = createTestBlockAndRun( 402 t, 403 engine, 404 block112, 405 col1121, 406 block112Snapshot) 407 // cache should include a program for this block 408 require.NotNil(t, derivedChainData.Get(block1121.ID())) 409 410 events := res.AllEvents() 411 require.Equal(t, res.BlockExecutionResult.Size(), 2) 412 413 // 1st event 414 hasValidEventValue(t, events[0], block1121ExpectedValue) 415 416 }) 417 t.Run("executing block12 (deploys contract V2)", func(t *testing.T) { 418 419 block12tx1 := testutil.DeployEventContractTransaction(account, chain, 2) 420 prepareTx(t, block12tx1, account, privKey, 0, chain) 421 422 col12 := flow.Collection{Transactions: []*flow.TransactionBody{block12tx1}} 423 block12, res, block12Snapshot = createTestBlockAndRun( 424 t, 425 engine, 426 block1, 427 col12, 428 block1Snapshot) 429 // cache should include a program for this block 430 require.NotNil(t, derivedChainData.Get(block12.ID())) 431 432 events := res.AllEvents() 433 require.Equal(t, res.BlockExecutionResult.Size(), 2) 434 435 assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type) 436 }) 437 t.Run("executing block121 (emit event (expected V2)", func(t *testing.T) { 438 block121ExpectedValue := 2 439 block121tx1 := testutil.CreateEmitEventTransaction(account, account) 440 prepareTx(t, block121tx1, account, privKey, 1, chain) 441 442 col121 := flow.Collection{Transactions: []*flow.TransactionBody{block121tx1}} 443 block121, res, block121Snapshot = createTestBlockAndRun( 444 t, 445 engine, 446 block12, 447 col121, 448 block12Snapshot) 449 // cache should include a program for this block 450 require.NotNil(t, derivedChainData.Get(block121.ID())) 451 452 events := res.AllEvents() 453 require.Equal(t, res.BlockExecutionResult.Size(), 2) 454 455 // 1st event 456 hasValidEventValue(t, events[0], block121ExpectedValue) 457 }) 458 t.Run("executing Block1211 (emit event (expected V2)", func(t *testing.T) { 459 block1211ExpectedValue := 2 460 block1211tx1 := testutil.CreateEmitEventTransaction(account, account) 461 prepareTx(t, block1211tx1, account, privKey, 2, chain) 462 463 col1211 := flow.Collection{Transactions: []*flow.TransactionBody{block1211tx1}} 464 block1211, res, _ = createTestBlockAndRun( 465 t, 466 engine, 467 block121, 468 col1211, 469 block121Snapshot) 470 // cache should include a program for this block 471 require.NotNil(t, derivedChainData.Get(block1211.ID())) 472 // had no change so cache should be equal to parent 473 require.Equal(t, derivedChainData.Get(block121.ID()), derivedChainData.Get(block1211.ID())) 474 475 events := res.AllEvents() 476 require.Equal(t, res.BlockExecutionResult.Size(), 2) 477 478 // 1st event 479 hasValidEventValue(t, events[0], block1211ExpectedValue) 480 }) 481 482 } 483 484 func createTestBlockAndRun( 485 t *testing.T, 486 engine *Manager, 487 parentBlock *flow.Block, 488 col flow.Collection, 489 snapshotTree snapshot.SnapshotTree, 490 ) ( 491 *flow.Block, 492 *execution.ComputationResult, 493 snapshot.SnapshotTree, 494 ) { 495 guarantee := flow.CollectionGuarantee{ 496 CollectionID: col.ID(), 497 Signature: nil, 498 } 499 500 block := &flow.Block{ 501 Header: &flow.Header{ 502 ParentID: parentBlock.ID(), 503 View: parentBlock.Header.Height + 1, 504 Timestamp: time.Now(), 505 }, 506 Payload: &flow.Payload{ 507 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 508 }, 509 } 510 511 executableBlock := &entity.ExecutableBlock{ 512 Block: block, 513 CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{ 514 guarantee.ID(): { 515 Guarantee: &guarantee, 516 Transactions: col.Transactions, 517 }, 518 }, 519 StartState: unittest.StateCommitmentPointerFixture(), 520 } 521 returnedComputationResult, err := engine.ComputeBlock( 522 context.Background(), 523 unittest.IdentifierFixture(), 524 executableBlock, 525 snapshotTree) 526 require.NoError(t, err) 527 528 for _, txResult := range returnedComputationResult.AllTransactionResults() { 529 require.Empty(t, txResult.ErrorMessage) 530 } 531 532 for _, snapshot := range returnedComputationResult.AllExecutionSnapshots() { 533 snapshotTree = snapshotTree.Append(snapshot) 534 } 535 536 return block, returnedComputationResult, snapshotTree 537 } 538 539 func prepareTx(t *testing.T, 540 tx *flow.TransactionBody, 541 account flow.Address, 542 privKey flow.AccountPrivateKey, 543 seqNumber uint64, 544 chain flow.Chain) { 545 tx.SetProposalKey(account, 0, seqNumber). 546 SetPayer(chain.ServiceAddress()) 547 err := testutil.SignPayload(tx, account, privKey) 548 require.NoError(t, err) 549 err = testutil.SignEnvelope(tx, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 550 require.NoError(t, err) 551 } 552 553 func hasValidEventValue(t *testing.T, event flow.Event, value int) { 554 data, err := ccf.Decode(nil, event.Payload) 555 require.NoError(t, err) 556 assert.Equal(t, int16(value), data.(cadence.Event).Fields[0].ToGoValue()) 557 }