From 2a77bc42b0f829d82d9f0ab34082efffec682c26 Mon Sep 17 00:00:00 2001 From: Deniz Mert Edincik Date: Sat, 19 Oct 2024 17:59:06 +0200 Subject: [PATCH 1/9] wip --- integration/benchmark/load/load_type_test.go | 2 +- integration/dkg/dkg_client_test.go | 36 +- integration/dkg/dkg_emulator_suite.go | 44 +- integration/emulator/adapters/access.go | 660 +++++ integration/emulator/adapters/sdk.go | 549 +++++ .../emulator/adapters/tests/access_test.go | 785 ++++++ integration/emulator/blockTicker.go | 56 + integration/emulator/blockchain.go | 1080 +++++++++ integration/emulator/config.go | 271 +++ integration/emulator/convert.go | 374 +++ integration/emulator/emulator.go | 171 ++ integration/emulator/errors.go | 274 +++ integration/emulator/ledger.go | 141 ++ integration/emulator/log.go | 22 + integration/emulator/logging.go | 93 + integration/emulator/memstore.go | 395 +++ integration/emulator/mocks/emulator.go | 468 ++++ integration/emulator/mocks/emulatorStorage.go | 323 +++ integration/emulator/pendingBlock.go | 236 ++ integration/emulator/result.go | 101 + integration/emulator/store.go | 96 + .../systemChunkTransactionTemplate.cdc | 16 + integration/emulator/tests/accounts_test.go | 1218 ++++++++++ .../emulator/tests/attachments_test.go | 49 + integration/emulator/tests/block_info_test.go | 114 + integration/emulator/tests/block_test.go | 178 ++ integration/emulator/tests/blockchain_test.go | 153 ++ integration/emulator/tests/capcons_test.go | 43 + integration/emulator/tests/collection_test.go | 117 + integration/emulator/tests/events_test.go | 202 ++ integration/emulator/tests/logs_test.go | 45 + integration/emulator/tests/memstore_test.go | 117 + .../emulator/tests/pendingBlock_test.go | 459 ++++ integration/emulator/tests/result_test.go | 85 + integration/emulator/tests/script_test.go | 315 +++ integration/emulator/tests/store_test.go | 480 ++++ integration/emulator/tests/temp_dep_test.go | 25 + .../emulator/tests/transaction_test.go | 2143 +++++++++++++++++ integration/emulator/tests/vm_test.go | 81 + .../emulator/utils/unittest/fixtures.go | 59 + integration/epochs/cluster_epoch_test.go | 36 +- integration/go.mod | 16 +- integration/go.sum | 19 - integration/utils/emulator_client.go | 4 +- storage/operation/reads_bench_test.txt | 63 + 45 files changed, 12121 insertions(+), 93 deletions(-) create mode 100644 integration/emulator/adapters/access.go create mode 100644 integration/emulator/adapters/sdk.go create mode 100644 integration/emulator/adapters/tests/access_test.go create mode 100644 integration/emulator/blockTicker.go create mode 100644 integration/emulator/blockchain.go create mode 100644 integration/emulator/config.go create mode 100644 integration/emulator/convert.go create mode 100644 integration/emulator/emulator.go create mode 100644 integration/emulator/errors.go create mode 100644 integration/emulator/ledger.go create mode 100644 integration/emulator/log.go create mode 100644 integration/emulator/logging.go create mode 100644 integration/emulator/memstore.go create mode 100644 integration/emulator/mocks/emulator.go create mode 100644 integration/emulator/mocks/emulatorStorage.go create mode 100644 integration/emulator/pendingBlock.go create mode 100644 integration/emulator/result.go create mode 100644 integration/emulator/store.go create mode 100644 integration/emulator/templates/systemChunkTransactionTemplate.cdc create mode 100644 integration/emulator/tests/accounts_test.go create mode 100644 integration/emulator/tests/attachments_test.go create mode 100644 integration/emulator/tests/block_info_test.go create mode 100644 integration/emulator/tests/block_test.go create mode 100644 integration/emulator/tests/blockchain_test.go create mode 100644 integration/emulator/tests/capcons_test.go create mode 100644 integration/emulator/tests/collection_test.go create mode 100644 integration/emulator/tests/events_test.go create mode 100644 integration/emulator/tests/logs_test.go create mode 100644 integration/emulator/tests/memstore_test.go create mode 100644 integration/emulator/tests/pendingBlock_test.go create mode 100644 integration/emulator/tests/result_test.go create mode 100644 integration/emulator/tests/script_test.go create mode 100644 integration/emulator/tests/store_test.go create mode 100644 integration/emulator/tests/temp_dep_test.go create mode 100644 integration/emulator/tests/transaction_test.go create mode 100644 integration/emulator/tests/vm_test.go create mode 100644 integration/emulator/utils/unittest/fixtures.go create mode 100644 storage/operation/reads_bench_test.txt diff --git a/integration/benchmark/load/load_type_test.go b/integration/benchmark/load/load_type_test.go index b63a9c18903..0119a1b577e 100644 --- a/integration/benchmark/load/load_type_test.go +++ b/integration/benchmark/load/load_type_test.go @@ -8,7 +8,7 @@ import ( "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" - convert2 "github.com/onflow/flow-emulator/convert" + convert2 "github.com/onflow/flow-go/integration/convert" "github.com/rs/zerolog" "github.com/stretchr/testify/require" diff --git a/integration/dkg/dkg_client_test.go b/integration/dkg/dkg_client_test.go index 2399c1401e5..3c842d7d721 100644 --- a/integration/dkg/dkg_client_test.go +++ b/integration/dkg/dkg_client_test.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" - emulator "github.com/onflow/flow-emulator/emulator" + emulator "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/contracts" @@ -34,13 +34,13 @@ type ClientSuite struct { contractClient *dkg.Client - env templates.Environment - blockchain emulator.Emulator - emulatorClient *utils.EmulatorClient - - dkgAddress sdk.Address - dkgAccountKey *sdk.AccountKey - dkgSigner sdkcrypto.Signer + env templates.Environment + blockchain emulator.Emulator + emulatorClient *utils.EmulatorClient + serviceAccountAddress sdk.Address + dkgAddress sdk.Address + dkgAccountKey *sdk.AccountKey + dkgSigner sdkcrypto.Signer } func TestDKGClient(t *testing.T) { @@ -57,7 +57,7 @@ func (s *ClientSuite) SetupTest() { s.blockchain = blockchain s.emulatorClient = utils.NewEmulatorClient(blockchain) - + s.serviceAccountAddress = sdk.Address(s.blockchain.ServiceKey().Address) // deploy contract s.deployDKGContract() @@ -234,16 +234,16 @@ func (s *ClientSuite) setUpAdmin() { setUpAdminTx := sdk.NewTransaction(). SetScript(templates.GeneratePublishDKGParticipantScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.dkgAddress) signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) s.signAndSubmit(setUpAdminTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.dkgAddress}, + []sdk.Address{s.serviceAccountAddress, s.dkgAddress}, []sdkcrypto.Signer{signer, s.dkgSigner}, ) } @@ -262,9 +262,9 @@ func (s *ClientSuite) startDKGWithParticipants(nodeIDs []flow.Identifier) { startDKGTx := sdk.NewTransaction(). SetScript(templates.GenerateStartDKGScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.dkgAddress) err := startDKGTx.AddArgument(cadence.NewArray(valueNodeIDs)) @@ -274,7 +274,7 @@ func (s *ClientSuite) startDKGWithParticipants(nodeIDs []flow.Identifier) { require.NoError(s.T(), err) s.signAndSubmit(startDKGTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.dkgAddress}, + []sdk.Address{s.serviceAccountAddress, s.dkgAddress}, []sdkcrypto.Signer{signer, s.dkgSigner}, ) @@ -293,9 +293,9 @@ func (s *ClientSuite) createParticipant(nodeID flow.Identifier, authoriser sdk.A createParticipantTx := sdk.NewTransaction(). SetScript(templates.GenerateCreateDKGParticipantScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(authoriser) err := createParticipantTx.AddArgument(cadence.NewAddress(s.dkgAddress)) @@ -310,7 +310,7 @@ func (s *ClientSuite) createParticipant(nodeID flow.Identifier, authoriser sdk.A require.NoError(s.T(), err) s.signAndSubmit(createParticipantTx, - []sdk.Address{s.blockchain.ServiceKey().Address, authoriser}, + []sdk.Address{s.serviceAccountAddress, authoriser}, []sdkcrypto.Signer{s2, signer}, ) diff --git a/integration/dkg/dkg_emulator_suite.go b/integration/dkg/dkg_emulator_suite.go index ee66e7e594c..8768dcddfa7 100644 --- a/integration/dkg/dkg_emulator_suite.go +++ b/integration/dkg/dkg_emulator_suite.go @@ -14,7 +14,7 @@ import ( jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" - emulator "github.com/onflow/flow-emulator/emulator" + emulator "github.com/onflow/flow-go/integration/emulator" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" @@ -54,10 +54,10 @@ type EmulatorSuite struct { dkgAccountKey *sdk.AccountKey dkgSigner sdkcrypto.Signer checkDKGUnhappy bool // activate log hook for DKGBroker to check if the DKG core is flagging misbehaviours - - netIDs flow.IdentityList - nodeAccounts []*nodeAccount - nodes []*node + serviceAccountAddress sdk.Address + netIDs flow.IdentityList + nodeAccounts []*nodeAccount + nodes []*node } func (s *EmulatorSuite) SetupTest() { @@ -119,7 +119,7 @@ func (s *EmulatorSuite) initEmulator() { s.Require().NoError(err) s.blockchain = blockchain - + s.serviceAccountAddress = sdk.Address(s.blockchain.ServiceKey().Address) s.adminEmulatorClient = utils.NewEmulatorClient(blockchain) s.hub = stub.NewNetworkHub() @@ -163,15 +163,15 @@ func (s *EmulatorSuite) setupDKGAdmin() { SetScript(templates.GeneratePublishDKGParticipantScript(s.env)). SetComputeLimit(9999). SetProposalKey( - s.blockchain.ServiceKey().Address, + s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.dkgAddress) signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) _, err = s.prepareAndSubmit(setUpAdminTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.dkgAddress}, + []sdk.Address{s.serviceAccountAddress, s.dkgAddress}, []sdkcrypto.Signer{signer, s.dkgSigner}, ) require.NoError(s.T(), err) @@ -229,13 +229,13 @@ func (s *EmulatorSuite) createAndFundAccount(netID bootstrap.NodeInfo) *nodeAcco sc.FungibleToken.Address.Hex(), sc.FlowToken.Address.Hex(), ))). - AddAuthorizer(s.blockchain.ServiceKey().Address). + AddAuthorizer(s.serviceAccountAddress). SetProposalKey( - s.blockchain.ServiceKey().Address, + s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber, ). - SetPayer(s.blockchain.ServiceKey().Address) + SetPayer(s.serviceAccountAddress) err = fundAccountTx.AddArgument(cadence.UFix64(1_000_000)) require.NoError(s.T(), err) @@ -244,7 +244,7 @@ func (s *EmulatorSuite) createAndFundAccount(netID bootstrap.NodeInfo) *nodeAcco signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) _, err = s.prepareAndSubmit(fundAccountTx, - []sdk.Address{s.blockchain.ServiceKey().Address}, + []sdk.Address{s.serviceAccountAddress}, []sdkcrypto.Signer{signer}, ) require.NoError(s.T(), err) @@ -307,10 +307,10 @@ func (s *EmulatorSuite) startDKGWithParticipants(accounts []*nodeAccount) { SetScript(templates.GenerateStartDKGScript(s.env)). SetComputeLimit(9999). SetProposalKey( - s.blockchain.ServiceKey().Address, + s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.dkgAddress) err := startDKGTx.AddArgument(cadence.NewArray(valueNodeIDs)) @@ -318,7 +318,7 @@ func (s *EmulatorSuite) startDKGWithParticipants(accounts []*nodeAccount) { signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) _, err = s.prepareAndSubmit(startDKGTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.dkgAddress}, + []sdk.Address{s.serviceAccountAddress, s.dkgAddress}, []sdkcrypto.Signer{signer, s.dkgSigner}, ) require.NoError(s.T(), err) @@ -334,7 +334,7 @@ func (s *EmulatorSuite) claimDKGParticipant(node *node) { SetScript(templates.GenerateCreateDKGParticipantScript(s.env)). SetComputeLimit(9999). SetProposalKey( - s.blockchain.ServiceKey().Address, + s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber, ). @@ -350,7 +350,7 @@ func (s *EmulatorSuite) claimDKGParticipant(node *node) { signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) _, err = s.prepareAndSubmit(createParticipantTx, - []sdk.Address{node.account.accountAddress, s.blockchain.ServiceKey().Address, s.dkgAddress}, + []sdk.Address{node.account.accountAddress, s.serviceAccountAddress, s.dkgAddress}, []sdkcrypto.Signer{node.account.accountSigner, signer, s.dkgSigner}, ) require.NoError(s.T(), err) @@ -371,21 +371,21 @@ func (s *EmulatorSuite) sendDummyTx() (*flow.Block, error) { createAccountTx, err := sdktemplates.CreateAccount( []*sdk.AccountKey{test.AccountKeyGenerator().New()}, []sdktemplates.Contract{}, - s.blockchain.ServiceKey().Address) + s.serviceAccountAddress) if err != nil { return nil, err } createAccountTx. SetProposalKey( - s.blockchain.ServiceKey().Address, + s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address) + SetPayer(s.serviceAccountAddress) signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) block, err := s.prepareAndSubmit(createAccountTx, - []sdk.Address{s.blockchain.ServiceKey().Address}, + []sdk.Address{s.serviceAccountAddress}, []sdkcrypto.Signer{signer}, ) return block, err diff --git a/integration/emulator/adapters/access.go b/integration/emulator/adapters/access.go new file mode 100644 index 00000000000..e706968a2b4 --- /dev/null +++ b/integration/emulator/adapters/access.go @@ -0,0 +1,660 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package adapters + +import ( + "context" + jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/subscription" + "github.com/onflow/flow-go/engine/common/rpc/convert" + "github.com/onflow/flow-go/integration/emulator" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/rs/zerolog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ access.API = &AccessAdapter{} + +// AccessAdapter wraps the emulator adapters to be compatible with access.API. +type AccessAdapter struct { + logger *zerolog.Logger + emulator emulator.Emulator +} + +// NewAccessAdapter returns a new AccessAdapter. +func NewAccessAdapter(logger *zerolog.Logger, emulator emulator.Emulator) *AccessAdapter { + return &AccessAdapter{ + logger: logger, + emulator: emulator, + } +} + +func convertError(err error, defaultStatusCode codes.Code) error { + if err != nil { + switch err.(type) { + case emulator.InvalidArgumentError: + return status.Error(codes.InvalidArgument, err.Error()) + case emulator.NotFoundError: + return status.Error(codes.NotFound, err.Error()) + default: + return status.Error(defaultStatusCode, err.Error()) + } + } + return nil +} + +func (a *AccessAdapter) Ping(_ context.Context) error { + return convertError(a.emulator.Ping(), codes.Internal) +} + +func (a *AccessAdapter) GetNetworkParameters(_ context.Context) access.NetworkParameters { + return a.emulator.GetNetworkParameters() +} + +func (a *AccessAdapter) GetLatestBlockHeader(_ context.Context, _ bool) (*flowgo.Header, flowgo.BlockStatus, error) { + block, err := a.emulator.GetLatestBlock() + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetLatestBlockHeader called") + + return block.Header, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetBlockHeaderByHeight(_ context.Context, height uint64) (*flowgo.Header, flowgo.BlockStatus, error) { + block, err := a.emulator.GetBlockByHeight(height) + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetBlockHeaderByHeight called") + + return block.Header, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetBlockHeaderByID(_ context.Context, id flowgo.Identifier) (*flowgo.Header, flowgo.BlockStatus, error) { + block, err := a.emulator.GetBlockByID(id) + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetBlockHeaderByID called") + + return block.Header, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetLatestBlock(_ context.Context, _ bool) (*flowgo.Block, flowgo.BlockStatus, error) { + block, err := a.emulator.GetLatestBlock() + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetLatestBlock called") + + return block, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetBlockByHeight(_ context.Context, height uint64) (*flowgo.Block, flowgo.BlockStatus, error) { + block, err := a.emulator.GetBlockByHeight(height) + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetBlockByHeight called") + + return block, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetBlockByID(_ context.Context, id flowgo.Identifier) (*flowgo.Block, flowgo.BlockStatus, error) { + block, err := a.emulator.GetBlockByID(id) + if err != nil { + return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) + } + + a.logger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": block.ID().String(), + }).Msg("🎁 GetBlockByID called") + + return block, flowgo.BlockStatusSealed, nil +} + +func (a *AccessAdapter) GetCollectionByID(_ context.Context, id flowgo.Identifier) (*flowgo.LightCollection, error) { + collection, err := a.emulator.GetCollectionByID(id) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Str("colID", id.String()). + Msg("📚 GetCollectionByID called") + + return collection, nil +} + +func (a *AccessAdapter) GetFullCollectionByID(_ context.Context, id flowgo.Identifier) (*flowgo.Collection, error) { + collection, err := a.emulator.GetFullCollectionByID(id) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Str("colID", id.String()). + Msg("📚 GetFullCollectionByID called") + + return collection, nil + +} + +func (a *AccessAdapter) GetTransaction(_ context.Context, id flowgo.Identifier) (*flowgo.TransactionBody, error) { + tx, err := a.emulator.GetTransaction(id) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Str("txID", id.String()). + Msg("💵 GetTransaction called") + + return tx, nil +} + +func (a *AccessAdapter) GetTransactionResult( + _ context.Context, + id flowgo.Identifier, + _ flowgo.Identifier, + _ flowgo.Identifier, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ( + *access.TransactionResult, + error, +) { + result, err := a.emulator.GetTransactionResult(id) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + // Convert CCF events to JSON events, else return CCF encoded version + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + result.Events, err = ConvertCCFEventsToJsonEvents(result.Events) + if err != nil { + return nil, convertError(err, codes.Internal) + } + } + a.logger.Debug(). + Str("txID", id.String()). + Msg("📝 GetTransactionResult called") + + return result, nil +} + +func (a *AccessAdapter) GetAccount(_ context.Context, address flowgo.Address) (*flowgo.Account, error) { + account, err := a.emulator.GetAccount(address) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Msg("👤 GetAccount called") + + return account, nil +} + +func (a *AccessAdapter) GetAccountAtLatestBlock(ctx context.Context, address flowgo.Address) (*flowgo.Account, error) { + account, err := a.GetAccount(ctx, address) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Msg("👤 GetAccountAtLatestBlock called") + + return account, nil +} + +func (a *AccessAdapter) GetAccountAtBlockHeight( + _ context.Context, + address flowgo.Address, + height uint64, +) (*flowgo.Account, error) { + + a.logger.Debug(). + Stringer("address", address). + Uint64("height", height). + Msg("👤 GetAccountAtBlockHeight called") + + account, err := a.emulator.GetAccountAtBlockHeight(address, height) + if err != nil { + return nil, convertError(err, codes.Internal) + } + return account, nil +} + +func (a *AccessAdapter) ConvertScriptResult(result *emulator.ScriptResult, err error) ([]byte, error) { + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + if !result.Succeeded() { + return nil, status.Error(codes.InvalidArgument, result.Error.Error()) + } + + valueBytes, err := jsoncdc.Encode(result.Value) + if err != nil { + return nil, convertError(err, codes.InvalidArgument) + } + + return valueBytes, nil +} + +func (a *AccessAdapter) ExecuteScriptAtLatestBlock( + _ context.Context, + script []byte, + arguments [][]byte, +) ([]byte, error) { + latestBlock, err := a.emulator.GetLatestBlock() + if err != nil { + return nil, err + } + a.logger.Debug(). + Uint64("blockHeight", latestBlock.Header.Height). + Msg("👤 ExecuteScriptAtLatestBlock called") + + result, err := a.emulator.ExecuteScript(script, arguments) + if err == nil { + emulator.PrintScriptResult(a.logger, result) + } + return a.ConvertScriptResult(result, err) +} + +func (a *AccessAdapter) ExecuteScriptAtBlockHeight( + _ context.Context, + blockHeight uint64, + script []byte, + arguments [][]byte, +) ([]byte, error) { + + a.logger.Debug(). + Uint64("blockHeight", blockHeight). + Msg("👤 ExecuteScriptAtBlockHeight called") + + result, err := a.emulator.ExecuteScriptAtBlockHeight(script, arguments, blockHeight) + if err == nil { + emulator.PrintScriptResult(a.logger, result) + } + return a.ConvertScriptResult(result, err) +} + +func (a *AccessAdapter) ExecuteScriptAtBlockID( + _ context.Context, + blockID flowgo.Identifier, + script []byte, + arguments [][]byte, +) ([]byte, error) { + + a.logger.Debug(). + Stringer("blockID", blockID). + Msg("👤 ExecuteScriptAtBlockID called") + + result, err := a.emulator.ExecuteScriptAtBlockID(script, arguments, blockID) + if err == nil { + emulator.PrintScriptResult(a.logger, result) + } + return a.ConvertScriptResult(result, err) +} + +func (a *AccessAdapter) GetEventsForHeightRange( + _ context.Context, + eventType string, + startHeight, endHeight uint64, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ([]flowgo.BlockEvents, error) { + events, err := a.emulator.GetEventsForHeightRange(eventType, startHeight, endHeight) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + eventCount := 0 + + // Convert CCF events to JSON events, else return CCF encoded version + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + for i := range events { + events[i].Events, err = ConvertCCFEventsToJsonEvents(events[i].Events) + eventCount = eventCount + len(events[i].Events) + if err != nil { + return nil, convertError(err, codes.Internal) + } + } + } + + a.logger.Debug().Fields(map[string]any{ + "eventType": eventType, + "startHeight": startHeight, + "endHeight": endHeight, + "eventCount": eventCount, + }).Msg("🎁 GetEventsForHeightRange called") + + return events, nil +} + +func (a *AccessAdapter) GetEventsForBlockIDs( + _ context.Context, + eventType string, + blockIDs []flowgo.Identifier, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ([]flowgo.BlockEvents, error) { + events, err := a.emulator.GetEventsForBlockIDs(eventType, blockIDs) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + eventCount := 0 + + // Convert CCF events to JSON events, else return CCF encoded version + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + for i := range events { + events[i].Events, err = ConvertCCFEventsToJsonEvents(events[i].Events) + eventCount = eventCount + len(events[i].Events) + if err != nil { + return nil, convertError(err, codes.Internal) + } + } + } + + a.logger.Debug().Fields(map[string]any{ + "eventType": eventType, + "eventCount": eventCount, + }).Msg("🎁 GetEventsForBlockIDs called") + + return events, nil +} + +func (a *AccessAdapter) GetLatestProtocolStateSnapshot(_ context.Context) ([]byte, error) { + return nil, nil +} + +func (a *AccessAdapter) GetProtocolStateSnapshotByBlockID(_ context.Context, _ flowgo.Identifier) ([]byte, error) { + return nil, nil +} + +func (a *AccessAdapter) GetProtocolStateSnapshotByHeight(_ context.Context, _ uint64) ([]byte, error) { + return nil, nil +} + +func (a *AccessAdapter) GetExecutionResultForBlockID(_ context.Context, _ flowgo.Identifier) (*flowgo.ExecutionResult, error) { + return nil, nil +} + +func (a *AccessAdapter) GetExecutionResultByID(_ context.Context, _ flowgo.Identifier) (*flowgo.ExecutionResult, error) { + return nil, nil +} + +func (a *AccessAdapter) GetSystemTransaction(_ context.Context, _ flowgo.Identifier) (*flowgo.TransactionBody, error) { + return nil, nil +} + +func (a *AccessAdapter) GetSystemTransactionResult(_ context.Context, _ flowgo.Identifier, _ entities.EventEncodingVersion) (*access.TransactionResult, error) { + return nil, nil +} + +func (a *AccessAdapter) GetAccountBalanceAtLatestBlock(_ context.Context, address flowgo.Address) (uint64, error) { + + account, err := a.emulator.GetAccount(address) + if err != nil { + return 0, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Msg("👤 GetAccountBalanceAtLatestBlock called") + + return account.Balance, nil +} + +func (a *AccessAdapter) GetAccountBalanceAtBlockHeight(ctx context.Context, address flowgo.Address, height uint64) (uint64, error) { + account, err := a.emulator.GetAccountAtBlockHeight(address, height) + if err != nil { + return 0, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Uint64("height", height). + Msg("👤 GetAccountBalanceAtBlockHeight called") + + return account.Balance, nil +} + +func (a *AccessAdapter) GetAccountKeyAtLatestBlock(_ context.Context, address flowgo.Address, keyIndex uint32) (*flowgo.AccountPublicKey, error) { + account, err := a.emulator.GetAccount(address) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + for _, key := range account.Keys { + if key.Index == keyIndex { + return &key, nil + } + } + + a.logger.Debug(). + Stringer("address", address). + Uint32("keyIndex", keyIndex). + Msg("👤 GetAccountKeyAtLatestBlock called") + + return nil, status.Errorf(codes.NotFound, "failed to get account key by index: %d", keyIndex) +} + +func (a *AccessAdapter) GetAccountKeyAtBlockHeight(_ context.Context, address flowgo.Address, keyIndex uint32, height uint64) (*flowgo.AccountPublicKey, error) { + account, err := a.emulator.GetAccountAtBlockHeight(address, height) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + for _, key := range account.Keys { + if key.Index == keyIndex { + return &key, nil + } + } + + a.logger.Debug(). + Stringer("address", address). + Uint32("keyIndex", keyIndex). + Uint64("height", height). + Msg("👤 GetAccountKeyAtBlockHeight called") + + return nil, status.Errorf(codes.NotFound, "failed to get account key by index: %d", keyIndex) +} + +func (a *AccessAdapter) GetAccountKeysAtLatestBlock(_ context.Context, address flowgo.Address) ([]flowgo.AccountPublicKey, error) { + account, err := a.emulator.GetAccount(address) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Msg("👤 GetAccountKeysAtLatestBlock called") + + return account.Keys, nil +} + +func (a *AccessAdapter) GetAccountKeysAtBlockHeight(_ context.Context, address flowgo.Address, height uint64) ([]flowgo.AccountPublicKey, error) { + account, err := a.emulator.GetAccountAtBlockHeight(address, height) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Stringer("address", address). + Uint64("height", height). + Msg("👤 GetAccountKeysAtBlockHeight called") + + return account.Keys, nil +} + +func (a *AccessAdapter) GetTransactionResultByIndex( + _ context.Context, + blockID flowgo.Identifier, + index uint32, + requiredEventEncodingVersion entities.EventEncodingVersion, +) (*access.TransactionResult, error) { + results, err := a.emulator.GetTransactionResultsByBlockID(blockID) + if err != nil { + return nil, convertError(err, codes.Internal) + } + if len(results) <= int(index) { + return nil, convertError(&emulator.TransactionNotFoundError{ID: flowgo.Identifier{}}, codes.Internal) + } + + // Convert CCF events to JSON events, else return CCF encoded version + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + for i := range results { + results[i].Events, err = ConvertCCFEventsToJsonEvents(results[i].Events) + if err != nil { + return nil, convertError(err, codes.Internal) + } + } + } + + return results[index], nil +} + +func (a *AccessAdapter) GetTransactionsByBlockID(_ context.Context, blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) { + result, err := a.emulator.GetTransactionsByBlockID(blockID) + if err != nil { + return nil, convertError(err, codes.Internal) + } + return result, nil +} + +func (a *AccessAdapter) GetTransactionResultsByBlockID( + _ context.Context, + blockID flowgo.Identifier, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ([]*access.TransactionResult, error) { + result, err := a.emulator.GetTransactionResultsByBlockID(blockID) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + // Convert CCF events to JSON events, else return CCF encoded version + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + for i := range result { + result[i].Events, err = ConvertCCFEventsToJsonEvents(result[i].Events) + if err != nil { + return nil, convertError(err, codes.Internal) + } + } + } + + return result, nil +} + +func (a *AccessAdapter) SendTransaction(_ context.Context, tx *flowgo.TransactionBody) error { + a.logger.Debug(). + Str("txID", tx.ID().String()). + Msg(`✉️ Transaction submitted`) + + return convertError(a.emulator.SendTransaction(tx), codes.Internal) +} + +func (a *AccessAdapter) GetNodeVersionInfo( + _ context.Context, +) ( + *access.NodeVersionInfo, + error, +) { + return &access.NodeVersionInfo{}, nil +} + +func (a *AccessAdapter) SubscribeBlocksFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlocksFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlocksFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockHeadersFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockHeadersFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockHeadersFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockDigestsFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockDigestsFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeBlockDigestsFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { + return nil +} + +func (a *AccessAdapter) SubscribeTransactionStatuses(ctx context.Context, tx *flowgo.TransactionBody, _ entities.EventEncodingVersion) subscription.Subscription { + return nil +} + +func ConvertCCFEventsToJsonEvents(events []flowgo.Event) ([]flowgo.Event, error) { + converted := make([]flowgo.Event, 0, len(events)) + + for _, event := range events { + evt, err := convert.CcfEventToJsonEvent(event) + if err != nil { + return nil, err + } + converted = append(converted, *evt) + } + + return converted, nil +} diff --git a/integration/emulator/adapters/sdk.go b/integration/emulator/adapters/sdk.go new file mode 100644 index 00000000000..aab4c2bd156 --- /dev/null +++ b/integration/emulator/adapters/sdk.go @@ -0,0 +1,549 @@ +package adapters + +import ( + "context" + "fmt" + "github.com/onflow/cadence" + jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/cadence/stdlib" + sdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go/access" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/rs/zerolog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/onflow/flow-go/integration/emulator" +) + +// SDKAdapter wraps an emulated emulator and implements the RPC handlers +// required by the Access API. +type SDKAdapter struct { + logger *zerolog.Logger + emulator emulator.Emulator +} + +func (b *SDKAdapter) EnableAutoMine() { + b.emulator.EnableAutoMine() +} +func (b *SDKAdapter) DisableAutoMine() { + b.emulator.DisableAutoMine() +} + +func (b *SDKAdapter) Emulator() emulator.Emulator { + return b.emulator +} + +// NewSDKAdapter returns a new SDKAdapter. +func NewSDKAdapter(logger *zerolog.Logger, emulator emulator.Emulator) *SDKAdapter { + return &SDKAdapter{ + logger: logger, + emulator: emulator, + } +} + +func (b *SDKAdapter) Ping(ctx context.Context) error { + return b.emulator.Ping() +} + +func (b *SDKAdapter) GetChainID(ctx context.Context) sdk.ChainID { + return sdk.ChainID(b.emulator.GetNetworkParameters().ChainID) +} + +// GetLatestBlockHeader gets the latest sealed block header. +func (b *SDKAdapter) GetLatestBlockHeader( + _ context.Context, + _ bool, +) ( + *sdk.BlockHeader, + sdk.BlockStatus, + error, +) { + block, err := b.emulator.GetLatestBlock() + if err != nil { + return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) + } + blockHeader := sdk.BlockHeader{ + ID: sdk.Identifier(block.ID()), + ParentID: sdk.Identifier(block.Header.ParentID), + Height: block.Header.Height, + Timestamp: block.Header.Timestamp, + } + return &blockHeader, sdk.BlockStatusSealed, nil +} + +// GetBlockHeaderByHeight gets a block header by height. +func (b *SDKAdapter) GetBlockHeaderByHeight( + _ context.Context, + height uint64, +) ( + *sdk.BlockHeader, + sdk.BlockStatus, + error, +) { + block, err := b.emulator.GetBlockByHeight(height) + if err != nil { + return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) + } + blockHeader := sdk.BlockHeader{ + ID: sdk.Identifier(block.ID()), + ParentID: sdk.Identifier(block.Header.ParentID), + Height: block.Header.Height, + Timestamp: block.Header.Timestamp, + } + return &blockHeader, sdk.BlockStatusSealed, nil +} + +// GetBlockHeaderByID gets a block header by ID. +func (b *SDKAdapter) GetBlockHeaderByID( + _ context.Context, + id sdk.Identifier, +) ( + *sdk.BlockHeader, + sdk.BlockStatus, + error, +) { + block, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, sdk.BlockStatusUnknown, err + } + blockHeader := sdk.BlockHeader{ + ID: sdk.Identifier(block.ID()), + ParentID: sdk.Identifier(block.Header.ParentID), + Height: block.Header.Height, + Timestamp: block.Header.Timestamp, + } + return &blockHeader, sdk.BlockStatusSealed, nil +} + +// GetLatestBlock gets the latest sealed block. +func (b *SDKAdapter) GetLatestBlock( + _ context.Context, + _ bool, +) ( + *sdk.Block, + sdk.BlockStatus, + error, +) { + flowBlock, err := b.emulator.GetLatestBlock() + if err != nil { + return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) + } + block := sdk.Block{ + BlockHeader: sdk.BlockHeader{ + ID: sdk.Identifier(flowBlock.ID()), + ParentID: sdk.Identifier(flowBlock.Header.ParentID), + Height: flowBlock.Header.Height, + Timestamp: flowBlock.Header.Timestamp, + }, + BlockPayload: convertBlockPayload(flowBlock.Payload), + } + return &block, sdk.BlockStatusSealed, nil +} + +// GetBlockByHeight gets a block by height. +func (b *SDKAdapter) GetBlockByHeight( + ctx context.Context, + height uint64, +) ( + *sdk.Block, + sdk.BlockStatus, + error, +) { + flowBlock, err := b.emulator.GetBlockByHeight(height) + if err != nil { + return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) + } + block := sdk.Block{ + BlockHeader: sdk.BlockHeader{ + ID: sdk.Identifier(flowBlock.ID()), + ParentID: sdk.Identifier(flowBlock.Header.ParentID), + Height: flowBlock.Header.Height, + Timestamp: flowBlock.Header.Timestamp, + }, + BlockPayload: convertBlockPayload(flowBlock.Payload), + } + return &block, sdk.BlockStatusSealed, nil +} + +// GetBlockByID gets a block by ID. +func (b *SDKAdapter) GetBlockByID( + _ context.Context, + id sdk.Identifier, +) ( + *sdk.Block, + sdk.BlockStatus, + error, +) { + flowBlock, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) + } + block := sdk.Block{ + BlockHeader: sdk.BlockHeader{ + ID: sdk.Identifier(flowBlock.ID()), + ParentID: sdk.Identifier(flowBlock.Header.ParentID), + Height: flowBlock.Header.Height, + Timestamp: flowBlock.Header.Timestamp, + }, + BlockPayload: convertBlockPayload(flowBlock.Payload), + } + return &block, sdk.BlockStatusSealed, nil +} + +func convertBlockPayload(payload *flowgo.Payload) sdk.BlockPayload { + var seals []*sdk.BlockSeal + sealCount := len(payload.Seals) + if sealCount > 0 { + seals = make([]*sdk.BlockSeal, 0, sealCount) + for _, seal := range payload.Seals { + seals = append(seals, &sdk.BlockSeal{ + BlockID: sdk.Identifier(seal.BlockID), + ExecutionReceiptID: sdk.Identifier(seal.ResultID), + }) + } + } + + var collectionGuarantees []*sdk.CollectionGuarantee + guaranteesCount := len(payload.Guarantees) + if guaranteesCount > 0 { + collectionGuarantees = make([]*sdk.CollectionGuarantee, 0, guaranteesCount) + for _, guarantee := range payload.Guarantees { + collectionGuarantees = append(collectionGuarantees, &sdk.CollectionGuarantee{ + CollectionID: sdk.Identifier(guarantee.CollectionID), + }) + } + } + + return sdk.BlockPayload{ + Seals: seals, + CollectionGuarantees: collectionGuarantees, + } +} + +// GetCollectionByID gets a collection by ID. +func (b *SDKAdapter) GetCollectionByID( + _ context.Context, + id sdk.Identifier, +) (*sdk.Collection, error) { + flowCollection, err := b.emulator.GetCollectionByID(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, err + } + collection := emulator.FlowLightCollectionToSDK(*flowCollection) + return &collection, nil +} + +func (b *SDKAdapter) SendTransaction(ctx context.Context, tx sdk.Transaction) error { + flowTx := emulator.SDKTransactionToFlow(tx) + return b.emulator.SendTransaction(flowTx) +} + +// GetTransaction gets a transaction by ID. +func (b *SDKAdapter) GetTransaction( + ctx context.Context, + id sdk.Identifier, +) (*sdk.Transaction, error) { + tx, err := b.emulator.GetTransaction(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, err + } + sdkTx := emulator.FlowTransactionToSDK(*tx) + return &sdkTx, nil +} + +// GetTransactionResult gets a transaction by ID. +func (b *SDKAdapter) GetTransactionResult( + ctx context.Context, + id sdk.Identifier, +) (*sdk.TransactionResult, error) { + flowResult, err := b.emulator.GetTransactionResult(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, err + } + return emulator.FlowTransactionResultToSDK(flowResult) +} + +// GetAccount returns an account by address at the latest sealed block. +func (b *SDKAdapter) GetAccount( + ctx context.Context, + address sdk.Address, +) (*sdk.Account, error) { + account, err := b.getAccount(address) + if err != nil { + return nil, err + } + return account, nil +} + +// GetAccountAtLatestBlock returns an account by address at the latest sealed block. +func (b *SDKAdapter) GetAccountAtLatestBlock( + ctx context.Context, + address sdk.Address, +) (*sdk.Account, error) { + account, err := b.getAccount(address) + if err != nil { + return nil, err + } + return account, nil +} + +func (b *SDKAdapter) getAccount(address sdk.Address) (*sdk.Account, error) { + account, err := b.emulator.GetAccount(emulator.SDKAddressToFlow(address)) + if err != nil { + return nil, err + } + return emulator.FlowAccountToSDK(*account) +} + +func (b *SDKAdapter) GetAccountAtBlockHeight( + ctx context.Context, + address sdk.Address, + height uint64, +) (*sdk.Account, error) { + account, err := b.emulator.GetAccountAtBlockHeight(emulator.SDKAddressToFlow(address), height) + if err != nil { + return nil, err + } + return emulator.FlowAccountToSDK(*account) +} + +// ExecuteScriptAtLatestBlock executes a script at a the latest block +func (b *SDKAdapter) ExecuteScriptAtLatestBlock( + ctx context.Context, + script []byte, + arguments [][]byte, +) ([]byte, error) { + block, err := b.emulator.GetLatestBlock() + if err != nil { + return nil, err + } + return b.executeScriptAtBlock(script, arguments, block.Header.Height) +} + +// ExecuteScriptAtBlockHeight executes a script at a specific block height +func (b *SDKAdapter) ExecuteScriptAtBlockHeight( + ctx context.Context, + blockHeight uint64, + script []byte, + arguments [][]byte, +) ([]byte, error) { + return b.executeScriptAtBlock(script, arguments, blockHeight) +} + +// ExecuteScriptAtBlockID executes a script at a specific block ID +func (b *SDKAdapter) ExecuteScriptAtBlockID( + ctx context.Context, + blockID sdk.Identifier, + script []byte, + arguments [][]byte, +) ([]byte, error) { + block, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(blockID)) + if err != nil { + return nil, err + } + return b.executeScriptAtBlock(script, arguments, block.Header.Height) +} + +// executeScriptAtBlock is a helper for executing a script at a specific block +func (b *SDKAdapter) executeScriptAtBlock(script []byte, arguments [][]byte, blockHeight uint64) ([]byte, error) { + result, err := b.emulator.ExecuteScriptAtBlockHeight(script, arguments, blockHeight) + if err != nil { + return nil, err + } + if !result.Succeeded() { + return nil, result.Error + } + valueBytes, err := jsoncdc.Encode(result.Value) + if err != nil { + return nil, err + } + return valueBytes, nil +} + +func (b *SDKAdapter) GetLatestProtocolStateSnapshot(_ context.Context) ([]byte, error) { + return nil, nil +} + +func (a *SDKAdapter) GetProtocolStateSnapshotByBlockID(_ context.Context, _ flowgo.Identifier) ([]byte, error) { + return nil, nil +} + +func (a *SDKAdapter) GetProtocolStateSnapshotByHeight(_ context.Context, _ uint64) ([]byte, error) { + return nil, nil +} + +func (b *SDKAdapter) GetExecutionResultForBlockID(_ context.Context, _ sdk.Identifier) (*sdk.ExecutionResult, error) { + return nil, nil +} + +func (b *SDKAdapter) GetSystemTransaction(_ context.Context, _ flowgo.Identifier) (*flowgo.TransactionBody, error) { + return nil, nil +} + +func (b *SDKAdapter) GetSystemTransactionResult(_ context.Context, _ flowgo.Identifier, _ entities.EventEncodingVersion) (*access.TransactionResult, error) { + return nil, nil +} + +func (b *SDKAdapter) GetTransactionsByBlockID(ctx context.Context, id sdk.Identifier) ([]*sdk.Transaction, error) { + result := []*sdk.Transaction{} + transactions, err := b.emulator.GetTransactionsByBlockID(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, err + } + for _, transaction := range transactions { + sdkTransaction := emulator.FlowTransactionToSDK(*transaction) + result = append(result, &sdkTransaction) + + } + return result, nil +} + +func (b *SDKAdapter) GetTransactionResultsByBlockID(ctx context.Context, id sdk.Identifier) ([]*sdk.TransactionResult, error) { + result := []*sdk.TransactionResult{} + transactionResults, err := b.emulator.GetTransactionResultsByBlockID(emulator.SDKIdentifierToFlow(id)) + if err != nil { + return nil, err + } + for _, transactionResult := range transactionResults { + sdkResult, err := emulator.FlowTransactionResultToSDK(transactionResult) + if err != nil { + return nil, err + } + result = append(result, sdkResult) + } + return result, nil +} + +func (b *SDKAdapter) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []sdk.Identifier) ([]*sdk.BlockEvents, error) { + result := []*sdk.BlockEvents{} + flowBlockEvents, err := b.emulator.GetEventsForBlockIDs(eventType, emulator.SDKIdentifiersToFlow(blockIDs)) + if err != nil { + return nil, err + } + + for _, flowBlockEvent := range flowBlockEvents { + sdkEvents, err := emulator.FlowEventsToSDK(flowBlockEvent.Events) + if err != nil { + return nil, err + } + + sdkBlockEvents := &sdk.BlockEvents{ + BlockID: sdk.Identifier(flowBlockEvent.BlockID), + Height: flowBlockEvent.BlockHeight, + BlockTimestamp: flowBlockEvent.BlockTimestamp, + Events: sdkEvents, + } + + result = append(result, sdkBlockEvents) + + } + + return result, nil +} + +func (b *SDKAdapter) GetEventsForHeightRange(ctx context.Context, eventType string, startHeight, endHeight uint64) ([]*sdk.BlockEvents, error) { + result := []*sdk.BlockEvents{} + + flowBlockEvents, err := b.emulator.GetEventsForHeightRange(eventType, startHeight, endHeight) + if err != nil { + return nil, err + } + + for _, flowBlockEvent := range flowBlockEvents { + sdkEvents, err := emulator.FlowEventsToSDK(flowBlockEvent.Events) + + if err != nil { + return nil, err + } + + sdkBlockEvents := &sdk.BlockEvents{ + BlockID: sdk.Identifier(flowBlockEvent.BlockID), + Height: flowBlockEvent.BlockHeight, + BlockTimestamp: flowBlockEvent.BlockTimestamp, + Events: sdkEvents, + } + + result = append(result, sdkBlockEvents) + + } + + return result, nil +} + +// CreateAccount submits a transaction to create a new account with the given +// account keys and contracts. The transaction is paid by the service account. +func (b *SDKAdapter) CreateAccount(ctx context.Context, publicKeys []*sdk.AccountKey, contracts []templates.Contract) (sdk.Address, error) { + + serviceKey := b.emulator.ServiceKey() + latestBlock, err := b.emulator.GetLatestBlock() + serviceAddress := emulator.FlowAddressToSDK(serviceKey.Address) + + if err != nil { + return sdk.Address{}, err + } + + if publicKeys == nil { + publicKeys = []*sdk.AccountKey{} + } + tx, err := templates.CreateAccount(publicKeys, contracts, serviceAddress) + if err != nil { + return sdk.Address{}, err + } + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetReferenceBlockID(sdk.Identifier(latestBlock.ID())). + SetProposalKey(serviceAddress, serviceKey.Index, serviceKey.SequenceNumber). + SetPayer(serviceAddress) + + signer, err := serviceKey.Signer() + if err != nil { + return sdk.Address{}, err + } + + err = tx.SignEnvelope(serviceAddress, serviceKey.Index, signer) + if err != nil { + return sdk.Address{}, err + } + + err = b.SendTransaction(ctx, *tx) + if err != nil { + return sdk.Address{}, err + } + + _, results, err := b.emulator.ExecuteAndCommitBlock() + if err != nil { + return sdk.Address{}, err + } + lastResult := results[len(results)-1] + + _, err = b.emulator.CommitBlock() + if err != nil { + return sdk.Address{}, err + } + + if !lastResult.Succeeded() { + return sdk.Address{}, lastResult.Error + } + + var address sdk.Address + + for _, event := range lastResult.Events { + if event.Type == sdk.EventAccountCreated { + addressFieldValue := cadence.SearchFieldByName( + event.Value, + stdlib.AccountEventAddressParameter.Identifier, + ) + address = sdk.Address(addressFieldValue.(cadence.Address)) + break + } + } + + if address == (sdk.Address{}) { + return sdk.Address{}, fmt.Errorf("failed to find AccountCreated event") + } + + return address, nil +} diff --git a/integration/emulator/adapters/tests/access_test.go b/integration/emulator/adapters/tests/access_test.go new file mode 100644 index 00000000000..da521706537 --- /dev/null +++ b/integration/emulator/adapters/tests/access_test.go @@ -0,0 +1,785 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "github.com/onflow/flow-go/integration/emulator/mocks" + "testing" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/ccf" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/common/rpc/convert" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func accessTest(f func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator)) func(t *testing.T) { + return func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + emu := mocks.NewMockEmulator(mockCtrl) + logger := zerolog.Nop() + back := adapters.NewAccessAdapter(&logger, emu) + + f(t, back, emu) + } +} + +func TestAccess(t *testing.T) { + + t.Run("Ping", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + emu.EXPECT(). + Ping(). + Times(1) + + err := adapter.Ping(context.Background()) + assert.NoError(t, err) + })) + + t.Run("GetNetworkParameters", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + expected := access.NetworkParameters{ + ChainID: flowgo.MonotonicEmulator, + } + + emu.EXPECT(). + GetNetworkParameters(). + Return(expected). + Times(1) + + result := adapter.GetNetworkParameters(context.Background()) + assert.Equal(t, expected, result) + + })) + + t.Run("GetLatestBlockHeader", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetLatestBlock(). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetLatestBlockHeader(context.Background(), true) + assert.Equal(t, expected.Header, result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetLatestBlock(). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetLatestBlockHeader(context.Background(), true) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetBlockHeaderByHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetBlockByHeight(uint64(42)). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetBlockHeaderByHeight(context.Background(), 42) + assert.Equal(t, expected.Header, result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetBlockByHeight(uint64(42)). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetBlockHeaderByHeight(context.Background(), 42) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetBlockHeaderByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + id := flowgo.Identifier{} + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetBlockByID(id). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetBlockHeaderByID(context.Background(), id) + assert.Equal(t, expected.Header, result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetBlockByID(id). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetBlockHeaderByID(context.Background(), id) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetLatestBlock(). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetLatestBlock(context.Background(), true) + assert.Equal(t, expected, *result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetLatestBlock(). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetLatestBlock(context.Background(), true) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetBlockByHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetBlockByHeight(uint64(42)). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetBlockByHeight(context.Background(), 42) + assert.Equal(t, expected, *result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetBlockByHeight(uint64(42)). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetBlockByHeight(context.Background(), 42) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetBlockByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + id := flowgo.Identifier{} + header := flowgo.Header{ + Height: 42, + } + expected := flowgo.Block{Header: &header} + + //success + emu.EXPECT(). + GetBlockByID(id). + Return(&expected, nil). + Times(1) + + result, blockStatus, err := adapter.GetBlockByID(context.Background(), id) + assert.Equal(t, expected, *result) + assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetBlockByID(id). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, blockStatus, err = adapter.GetBlockByID(context.Background(), id) + assert.Nil(t, result) + assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) + assert.Error(t, err) + + })) + + t.Run("GetCollectionByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + id := flowgo.Identifier{} + expected := flowgo.LightCollection{} + + //success + emu.EXPECT(). + GetCollectionByID(id). + Return(&expected, nil). + Times(1) + + result, err := adapter.GetCollectionByID(context.Background(), id) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetCollectionByID(id). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetCollectionByID(context.Background(), id) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetTransaction", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + id := flowgo.Identifier{} + expected := flowgo.TransactionBody{} + + //success + emu.EXPECT(). + GetTransaction(id). + Return(&expected, nil). + Times(1) + + result, err := adapter.GetTransaction(context.Background(), id) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetTransaction(id). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetTransaction(context.Background(), id) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetTransactionResult", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + txID := flowgo.Identifier{} + blockID := flowgo.Identifier{} + collectionID := flowgo.Identifier{} + + emuResult := access.TransactionResult{ + Events: []flowgo.Event{ + ccfEventFixture(t), + }, + } + expected := access.TransactionResult{ + Events: []flowgo.Event{ + jsonCDCEventFixture(t), + }, + } + + //success + emu.EXPECT(). + GetTransactionResult(txID). + Return(&emuResult, nil). + Times(1) + + result, err := adapter.GetTransactionResult(context.Background(), txID, blockID, collectionID, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetTransactionResult(txID). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetTransactionResult(context.Background(), txID, blockID, collectionID, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetAccount", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + address := flowgo.Address{} + expected := flowgo.Account{} + + //success + emu.EXPECT(). + GetAccount(address). + Return(&expected, nil). + Times(1) + + result, err := adapter.GetAccount(context.Background(), address) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetAccount(address). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetAccount(context.Background(), address) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetAccountAtLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + address := flowgo.Address{} + expected := flowgo.Account{} + + //success + emu.EXPECT(). + GetAccount(address). + Return(&expected, nil). + Times(1) + + result, err := adapter.GetAccountAtLatestBlock(context.Background(), address) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetAccount(address). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetAccountAtLatestBlock(context.Background(), address) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetAccountAtBlockHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + address := flowgo.Address{} + height := uint64(42) + + expected := flowgo.Account{} + + //success + emu.EXPECT(). + GetAccountAtBlockHeight(address, height). + Return(&expected, nil). + Times(1) + + result, err := adapter.GetAccountAtBlockHeight(context.Background(), address, height) + assert.Equal(t, expected, *result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetAccountAtBlockHeight(address, height). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetAccountAtBlockHeight(context.Background(), address, height) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("ExecuteScriptAtLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + script := []byte("some cadence code here") + var arguments [][]byte + + stringValue, _ := cadence.NewString("42") + emulatorResult := emulator.ScriptResult{Value: stringValue} + expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) + + // called once for each script execution + emu.EXPECT(). + GetLatestBlock(). + Return(&flowgo.Block{Header: &flowgo.Header{}}, nil). + Times(2) + + //success + emu.EXPECT(). + ExecuteScript(script, arguments). + Return(&emulatorResult, nil). + Times(1) + + result, err := adapter.ExecuteScriptAtLatestBlock(context.Background(), script, arguments) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + ExecuteScript(script, arguments). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.ExecuteScriptAtLatestBlock(context.Background(), script, arguments) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("ExecuteScriptAtBlockHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + script := []byte("some cadence code here") + var arguments [][]byte + + height := uint64(42) + stringValue, _ := cadence.NewString("42") + emulatorResult := emulator.ScriptResult{Value: stringValue} + expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) + + //success + emu.EXPECT(). + ExecuteScriptAtBlockHeight(script, arguments, height). + Return(&emulatorResult, nil). + Times(1) + + result, err := adapter.ExecuteScriptAtBlockHeight(context.Background(), height, script, arguments) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + ExecuteScriptAtBlockHeight(script, arguments, height). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.ExecuteScriptAtBlockHeight(context.Background(), height, script, arguments) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("ExecuteScriptAtBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + script := []byte("some cadence code here") + var arguments [][]byte + + id := flowgo.Identifier{} + stringValue, _ := cadence.NewString("42") + emulatorResult := emulator.ScriptResult{Value: stringValue} + expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) + + //success + emu.EXPECT(). + ExecuteScriptAtBlockID(script, arguments, id). + Return(&emulatorResult, nil). + Times(1) + + result, err := adapter.ExecuteScriptAtBlockID(context.Background(), id, script, arguments) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + ExecuteScriptAtBlockID(script, arguments, id). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.ExecuteScriptAtBlockID(context.Background(), id, script, arguments) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetEventsForHeightRange", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + eventType := "testEvent" + startHeight := uint64(0) + endHeight := uint64(42) + + blockEvents := []flowgo.BlockEvents{ + { + Events: []flowgo.Event{ + ccfEventFixture(t), + }, + }, + } + expected := []flowgo.BlockEvents{ + { + Events: []flowgo.Event{ + jsonCDCEventFixture(t), + }, + }, + } + + //success + emu.EXPECT(). + GetEventsForHeightRange(eventType, startHeight, endHeight). + Return(blockEvents, nil). + Times(1) + + result, err := adapter.GetEventsForHeightRange(context.Background(), eventType, startHeight, endHeight, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetEventsForHeightRange(eventType, startHeight, endHeight). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetEventsForHeightRange(context.Background(), eventType, startHeight, endHeight, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetEventsForBlockIDs", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + eventType := "testEvent" + blockIDs := []flowgo.Identifier{flowgo.Identifier{}} + + blockEvents := []flowgo.BlockEvents{ + { + Events: []flowgo.Event{ + ccfEventFixture(t), + }, + }, + } + expected := []flowgo.BlockEvents{ + { + Events: []flowgo.Event{ + jsonCDCEventFixture(t), + }, + }, + } + + //success + emu.EXPECT(). + GetEventsForBlockIDs(eventType, blockIDs). + Return(blockEvents, nil). + Times(1) + + result, err := adapter.GetEventsForBlockIDs(context.Background(), eventType, blockIDs, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetEventsForBlockIDs(eventType, blockIDs). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetEventsForBlockIDs(context.Background(), eventType, blockIDs, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetTransactionResultByIndex", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + blockID := flowgo.Identifier{} + index := uint32(0) + + txResult := &access.TransactionResult{ + Events: []flowgo.Event{ + ccfEventFixture(t), + }, + } + results := []*access.TransactionResult{txResult} + convertedTXResult := &access.TransactionResult{ + Events: []flowgo.Event{ + jsonCDCEventFixture(t), + }, + } + + //success + emu.EXPECT(). + GetTransactionResultsByBlockID(blockID). + Return(results, nil). + Times(1) + + result, err := adapter.GetTransactionResultByIndex(context.Background(), blockID, index, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Equal(t, convertedTXResult, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetTransactionResultsByBlockID(blockID). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetTransactionResultByIndex(context.Background(), blockID, index, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetTransactionsByBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + blockID := flowgo.Identifier{} + + expected := []*flowgo.TransactionBody{} + + //success + emu.EXPECT(). + GetTransactionsByBlockID(blockID). + Return(expected, nil). + Times(1) + + result, err := adapter.GetTransactionsByBlockID(context.Background(), blockID) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetTransactionsByBlockID(blockID). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetTransactionsByBlockID(context.Background(), blockID) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("GetTransactionResultsByBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + blockID := flowgo.Identifier{} + + results := []*access.TransactionResult{ + { + Events: []flowgo.Event{ + ccfEventFixture(t), + }, + }, + } + expected := []*access.TransactionResult{ + { + Events: []flowgo.Event{ + jsonCDCEventFixture(t), + }, + }, + } + + //success + emu.EXPECT(). + GetTransactionResultsByBlockID(blockID). + Return(results, nil). + Times(1) + + result, err := adapter.GetTransactionResultsByBlockID(context.Background(), blockID, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Equal(t, expected, result) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + GetTransactionResultsByBlockID(blockID). + Return(nil, fmt.Errorf("some error")). + Times(1) + + result, err = adapter.GetTransactionResultsByBlockID(context.Background(), blockID, entities.EventEncodingVersion_JSON_CDC_V0) + assert.Nil(t, result) + assert.Error(t, err) + + })) + + t.Run("SendTransaction", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + + transaction := flowgo.TransactionBody{} + + //success + emu.EXPECT(). + SendTransaction(&transaction). + Return(nil). + Times(1) + + err := adapter.SendTransaction(context.Background(), &transaction) + assert.NoError(t, err) + + //fail + emu.EXPECT(). + SendTransaction(&transaction). + Return(fmt.Errorf("some error")). + Times(1) + + err = adapter.SendTransaction(context.Background(), &transaction) + assert.Error(t, err) + + })) + + t.Run("GetNodeVersionInfo", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { + info, err := adapter.GetNodeVersionInfo(context.Background()) + assert.NoError(t, err) + require.Equal(t, uint64(0), info.NodeRootBlockHeight) + })) + +} + +func ccfEventFixture(t *testing.T) flowgo.Event { + cadenceValue := cadence.NewInt(2) + + ccfEvent, err := ccf.EventsEncMode.Encode(cadenceValue) + require.NoError(t, err) + + return flowgo.Event{ + Payload: ccfEvent, + } +} + +func jsonCDCEventFixture(t *testing.T) flowgo.Event { + converted, err := convert.CcfEventToJsonEvent(ccfEventFixture(t)) + require.NoError(t, err) + return *converted +} diff --git a/integration/emulator/blockTicker.go b/integration/emulator/blockTicker.go new file mode 100644 index 00000000000..40ef1ce7e34 --- /dev/null +++ b/integration/emulator/blockTicker.go @@ -0,0 +1,56 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "time" +) + +type BlocksTicker struct { + emulator Emulator + ticker *time.Ticker + done chan bool +} + +func NewBlocksTicker( + emulator Emulator, + blockTime time.Duration, +) *BlocksTicker { + return &BlocksTicker{ + emulator: emulator, + ticker: time.NewTicker(blockTime), + done: make(chan bool, 1), + } +} + +func (t *BlocksTicker) Start() error { + for { + select { + case <-t.ticker.C: + _, _ = t.emulator.ExecuteBlock() + _, _ = t.emulator.CommitBlock() + case <-t.done: + return nil + } + } +} + +func (t *BlocksTicker) Stop() { + t.done <- true +} diff --git a/integration/emulator/blockchain.go b/integration/emulator/blockchain.go new file mode 100644 index 00000000000..987bc161c50 --- /dev/null +++ b/integration/emulator/blockchain.go @@ -0,0 +1,1080 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package emulator provides an emulated version of the Flow emulator that can be used +// for development purposes. +// +// This package can be used as a library or as a standalone application. +// +// When used as a library, this package provides tools to write programmatic tests for +// Flow applications. +// +// When used as a standalone application, this package implements the Flow Access API +// and is fully-compatible with Flow gRPC client libraries. +package emulator + +import ( + "context" + _ "embed" + "encoding/hex" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" + + "github.com/onflow/flow-core-contracts/lib/go/templates" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine" + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/environment" + fvmerrors "github.com/onflow/flow-go/fvm/errors" + reusableRuntime "github.com/onflow/flow-go/fvm/runtime" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/metrics" + "github.com/rs/zerolog" +) + +// systemChunkTransactionTemplate looks for the RandomBeaconHistory +// heartbeat resource on the service account and calls it. +// +//go:embed templates/systemChunkTransactionTemplate.cdc +var systemChunkTransactionTemplate string + +var _ Emulator = &Blockchain{} + +// New instantiates a new emulated emulator with the provided options. +func New(opts ...Option) (*Blockchain, error) { + + // apply options to the default config + conf := defaultConfig + for _, opt := range opts { + opt(&conf) + } + b := &Blockchain{ + storage: conf.GetStore(), + broadcaster: engine.NewBroadcaster(), + serviceKey: conf.GetServiceKey(), + conf: conf, + entropyProvider: &blockHashEntropyProvider{}, + } + return b.ReloadBlockchain() +} + +func (b *Blockchain) Now() time.Time { + if b.clockOverride != nil { + return b.clockOverride() + } + return time.Now().UTC() +} + +// Blockchain emulates the functionality of the Flow emulator. +type Blockchain struct { + // committed chain state: blocks, transactions, registers, events + storage EmulatorStorage + broadcaster *engine.Broadcaster + + // mutex protecting pending block + mu sync.RWMutex + + // pending block containing block info, register state, pending transactions + pendingBlock *pendingBlock + clockOverride func() time.Time + // used to execute transactions and scripts + vm *fvm.VirtualMachine + vmCtx fvm.Context + transactionValidator *access.TransactionValidator + serviceKey ServiceKey + conf config + entropyProvider *blockHashEntropyProvider +} + +func (b *Blockchain) Broadcaster() *engine.Broadcaster { + return b.broadcaster +} + +func (b *Blockchain) ReloadBlockchain() (*Blockchain, error) { + + cadenceLogger := b.conf.Logger.Hook(CadenceHook{MainLogger: &b.conf.ServerLogger}).Level(zerolog.DebugLevel) + + b.vm = fvm.NewVirtualMachine() + b.vmCtx = fvm.NewContext( + fvm.WithLogger(cadenceLogger), + fvm.WithChain(b.conf.GetChainID().Chain()), + fvm.WithBlocks(b.storage), + fvm.WithContractDeploymentRestricted(false), + fvm.WithContractRemovalRestricted(!b.conf.ContractRemovalEnabled), + fvm.WithComputationLimit(b.conf.ScriptGasLimit), + fvm.WithCadenceLogging(true), + fvm.WithAccountStorageLimit(b.conf.StorageLimitEnabled), + fvm.WithTransactionFeesEnabled(b.conf.TransactionFeesEnabled), + fvm.WithReusableCadenceRuntimePool( + reusableRuntime.NewReusableCadenceRuntimePool( + 0, + runtime.Config{ + AttachmentsEnabled: true, + }), + ), + fvm.WithEntropyProvider(b.entropyProvider), + fvm.WithEVMEnabled(true), + fvm.WithAuthorizationChecksEnabled(b.conf.TransactionValidationEnabled), + fvm.WithSequenceNumberCheckAndIncrementEnabled(b.conf.TransactionValidationEnabled), + ) + + latestBlock, latestLedger, err := configureLedger( + b.conf, + b.storage, + b.vm, + b.vmCtx) + if err != nil { + return nil, err + } + + b.pendingBlock = newPendingBlock(latestBlock, latestLedger, b.Now()) + err = b.configureTransactionValidator() + if err != nil { + return nil, err + } + + return b, nil +} + +func (b *Blockchain) EnableAutoMine() { + b.conf.AutoMine = true +} + +func (b *Blockchain) DisableAutoMine() { + b.conf.AutoMine = false +} + +func (b *Blockchain) Ping() error { + return nil +} + +func (b *Blockchain) GetChain() flowgo.Chain { + return b.vmCtx.Chain +} + +func (b *Blockchain) GetNetworkParameters() access.NetworkParameters { + return access.NetworkParameters{ + ChainID: b.GetChain().ChainID(), + } +} + +// `blockHashEntropyProvider implements `environment.EntropyProvider` +// which provides a source of entropy to fvm context (required for Cadence's randomness), +// by using the latest block hash. +type blockHashEntropyProvider struct { + LatestBlock flowgo.Identifier +} + +func (gen *blockHashEntropyProvider) RandomSource() ([]byte, error) { + return gen.LatestBlock[:], nil +} + +// make sure `blockHashEntropyProvider implements `environment.EntropyProvider` +var _ environment.EntropyProvider = &blockHashEntropyProvider{} + +func (b *Blockchain) configureTransactionValidator() error { + validator, err := access.NewTransactionValidator( + b.storage, + b.conf.GetChainID().Chain(), + metrics.NewNoopCollector(), + access.TransactionValidationOptions{ + Expiry: b.conf.TransactionExpiry, + ExpiryBuffer: 0, + AllowEmptyReferenceBlockID: b.conf.TransactionExpiry == 0, + AllowUnknownReferenceBlockID: false, + MaxGasLimit: b.conf.TransactionMaxGasLimit, + CheckScriptsParse: true, + MaxTransactionByteSize: flowgo.DefaultMaxTransactionByteSize, + MaxCollectionByteSize: flowgo.DefaultMaxCollectionByteSize, + CheckPayerBalanceMode: access.Disabled, + }, + nil, + ) + if err != nil { + return err + } + b.transactionValidator = validator + return nil +} + +func (b *Blockchain) setFVMContextFromHeader(header *flowgo.Header) fvm.Context { + b.vmCtx = fvm.NewContextFromParent( + b.vmCtx, + fvm.WithBlockHeader(header), + ) + return b.vmCtx +} + +// ServiceKey returns the service private key for this emulator. +func (b *Blockchain) ServiceKey() ServiceKey { + serviceAccount, err := b.getAccount(b.serviceKey.Address) + if err != nil { + return b.serviceKey + } + + if len(serviceAccount.Keys) > 0 { + b.serviceKey.Index = 0 + b.serviceKey.SequenceNumber = serviceAccount.Keys[0].SeqNumber + b.serviceKey.Weight = serviceAccount.Keys[0].Weight + } + + return b.serviceKey +} + +// PendingBlockID returns the ID of the pending block. +func (b *Blockchain) PendingBlockID() flowgo.Identifier { + return b.pendingBlock.ID() +} + +// PendingBlockView returns the view of the pending block. +func (b *Blockchain) PendingBlockView() uint64 { + return b.pendingBlock.view +} + +// PendingBlockTimestamp returns the Timestamp of the pending block. +func (b *Blockchain) PendingBlockTimestamp() time.Time { + return b.pendingBlock.Block().Header.Timestamp +} + +// GetLatestBlock gets the latest sealed block. +func (b *Blockchain) GetLatestBlock() (*flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getLatestBlock() +} + +func (b *Blockchain) getLatestBlock() (*flowgo.Block, error) { + block, err := b.storage.LatestBlock(context.Background()) + if err != nil { + return nil, err + } + + return &block, nil +} + +// GetBlockByID gets a block by ID. +func (b *Blockchain) GetBlockByID(id flowgo.Identifier) (*flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getBlockByID(id) +} + +func (b *Blockchain) getBlockByID(id flowgo.Identifier) (*flowgo.Block, error) { + block, err := b.storage.BlockByID(context.Background(), id) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, &BlockNotFoundByIDError{ID: id} + } + + return nil, err + } + + return block, nil +} + +// GetBlockByHeight gets a block by height. +func (b *Blockchain) GetBlockByHeight(height uint64) (*flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + block, err := b.getBlockByHeight(height) + if err != nil { + return nil, err + } + + return block, nil +} + +func (b *Blockchain) getBlockByHeight(height uint64) (*flowgo.Block, error) { + block, err := b.storage.BlockByHeight(context.Background(), height) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, &BlockNotFoundByHeightError{Height: height} + } + return nil, err + } + + return block, nil +} + +func (b *Blockchain) GetCollectionByID(colID flowgo.Identifier) (*flowgo.LightCollection, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getCollectionByID(colID) +} + +func (b *Blockchain) getCollectionByID(colID flowgo.Identifier) (*flowgo.LightCollection, error) { + col, err := b.storage.CollectionByID(context.Background(), colID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, &CollectionNotFoundError{ID: colID} + } + return nil, err + } + + return &col, nil +} + +func (b *Blockchain) GetFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getFullCollectionByID(colID) +} + +func (b *Blockchain) getFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) { + col, err := b.storage.FullCollectionByID(context.Background(), colID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, &CollectionNotFoundError{ID: colID} + } + return nil, err + } + + return &col, nil +} + +// GetTransaction gets an existing transaction by ID. +// +// The function first looks in the pending block, then the current emulator state. +func (b *Blockchain) GetTransaction(txID flowgo.Identifier) (*flowgo.TransactionBody, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getTransaction(txID) +} + +func (b *Blockchain) getTransaction(txID flowgo.Identifier) (*flowgo.TransactionBody, error) { + pendingTx := b.pendingBlock.GetTransaction(txID) + if pendingTx != nil { + return pendingTx, nil + } + + tx, err := b.storage.TransactionByID(context.Background(), txID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, &TransactionNotFoundError{ID: txID} + } + return nil, err + } + + return &tx, nil +} + +func (b *Blockchain) GetTransactionResult(txID flowgo.Identifier) (*access.TransactionResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.getTransactionResult(txID) +} + +func (b *Blockchain) getTransactionResult(txID flowgo.Identifier) (*access.TransactionResult, error) { + if b.pendingBlock.ContainsTransaction(txID) { + return &access.TransactionResult{ + Status: flowgo.TransactionStatusPending, + }, nil + } + + storedResult, err := b.storage.TransactionResultByID(context.Background(), txID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return &access.TransactionResult{ + Status: flowgo.TransactionStatusUnknown, + }, nil + } + return nil, err + } + + statusCode := 0 + if storedResult.ErrorCode > 0 { + statusCode = 1 + } + result := access.TransactionResult{ + Status: flowgo.TransactionStatusSealed, + StatusCode: uint(statusCode), + ErrorMessage: storedResult.ErrorMessage, + Events: storedResult.Events, + TransactionID: txID, + BlockHeight: storedResult.BlockHeight, + BlockID: storedResult.BlockID, + } + + return &result, nil +} + +// GetAccountByIndex returns the account for the given address. +func (b *Blockchain) GetAccountByIndex(index uint) (*flowgo.Account, error) { + + address, err := b.vmCtx.Chain.ChainID().Chain().AddressAtIndex(uint64(index)) + if err != nil { + return nil, err + } + + latestBlock, err := b.getLatestBlock() + if err != nil { + return nil, err + } + return b.getAccountAtBlock(address, latestBlock.Header.Height) + +} + +// GetAccount returns the account for the given address. +func (b *Blockchain) GetAccount(address flowgo.Address) (*flowgo.Account, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getAccount(address) +} + +// getAccount returns the account for the given address. +func (b *Blockchain) getAccount(address flowgo.Address) (*flowgo.Account, error) { + latestBlock, err := b.getLatestBlock() + if err != nil { + return nil, err + } + return b.getAccountAtBlock(address, latestBlock.Header.Height) +} + +// GetAccountAtBlockHeight returns the account for the given address at specified block height. +func (b *Blockchain) GetAccountAtBlockHeight(address flowgo.Address, blockHeight uint64) (*flowgo.Account, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + account, err := b.getAccountAtBlock(address, blockHeight) + if err != nil { + return nil, err + } + + return account, nil +} + +// GetAccountAtBlock returns the account for the given address at specified block height. +func (b *Blockchain) getAccountAtBlock(address flowgo.Address, blockHeight uint64) (*flowgo.Account, error) { + ledger, err := b.storage.LedgerByHeight(context.Background(), blockHeight) + if err != nil { + return nil, err + } + + account, err := fvm.GetAccount(b.vmCtx, address, ledger) + if fvmerrors.IsAccountNotFoundError(err) { + return nil, &AccountNotFoundError{Address: address} + } + + return account, nil +} + +func (b *Blockchain) GetEventsForBlockIDs(eventType string, blockIDs []flowgo.Identifier) (result []flowgo.BlockEvents, err error) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, blockID := range blockIDs { + block, err := b.storage.BlockByID(context.Background(), blockID) + if err != nil { + break + } + events, err := b.storage.EventsByHeight(context.Background(), block.Header.Height, eventType) + if err != nil { + break + } + result = append(result, flowgo.BlockEvents{ + BlockID: block.ID(), + BlockHeight: block.Header.Height, + BlockTimestamp: block.Header.Timestamp, + Events: events, + }) + } + + return result, err +} + +func (b *Blockchain) GetEventsForHeightRange(eventType string, startHeight, endHeight uint64) (result []flowgo.BlockEvents, err error) { + b.mu.RLock() + defer b.mu.RUnlock() + + for blockHeight := startHeight; blockHeight <= endHeight; blockHeight++ { + block, err := b.storage.BlockByHeight(context.Background(), blockHeight) + if err != nil { + break + } + + events, err := b.storage.EventsByHeight(context.Background(), blockHeight, eventType) + if err != nil { + break + } + + result = append(result, flowgo.BlockEvents{ + BlockID: block.ID(), + BlockHeight: block.Header.Height, + BlockTimestamp: block.Header.Timestamp, + Events: events, + }) + } + + return result, err +} + +// GetEventsByHeight returns the events in the block at the given height, optionally filtered by type. +func (b *Blockchain) GetEventsByHeight(blockHeight uint64, eventType string) ([]flowgo.Event, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.storage.EventsByHeight(context.Background(), blockHeight, eventType) +} + +// SendTransaction submits a transaction to the network. +func (b *Blockchain) SendTransaction(flowTx *flowgo.TransactionBody) error { + b.mu.Lock() + defer b.mu.Unlock() + + err := b.addTransaction(*flowTx) + if err != nil { + return err + } + + if b.conf.AutoMine { + _, _, err := b.executeAndCommitBlock() + if err != nil { + return err + } + } + + return nil +} + +// AddTransaction validates a transaction and adds it to the current pending block. +func (b *Blockchain) AddTransaction(tx flowgo.TransactionBody) error { + b.mu.Lock() + defer b.mu.Unlock() + + return b.addTransaction(tx) +} + +func (b *Blockchain) addTransaction(tx flowgo.TransactionBody) error { + + // If index > 0, pending block has begun execution (cannot add more transactions) + if b.pendingBlock.ExecutionStarted() { + return &PendingBlockMidExecutionError{BlockID: b.pendingBlock.ID()} + } + + if b.pendingBlock.ContainsTransaction(tx.ID()) { + return &DuplicateTransactionError{TxID: tx.ID()} + } + + _, err := b.storage.TransactionByID(context.Background(), tx.ID()) + if err == nil { + // Found the transaction, this is a duplicate + return &DuplicateTransactionError{TxID: tx.ID()} + } else if !errors.Is(err, ErrNotFound) { + // Error in the storage provider + return fmt.Errorf("failed to check storage for transaction %w", err) + } + + err = b.transactionValidator.Validate(context.Background(), &tx) + if err != nil { + return ConvertAccessError(err) + } + + // add transaction to pending block + b.pendingBlock.AddTransaction(tx) + + return nil +} + +// ExecuteBlock executes the remaining transactions in pending block. +func (b *Blockchain) ExecuteBlock() ([]*TransactionResult, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.executeBlock() +} + +func (b *Blockchain) executeBlock() ([]*TransactionResult, error) { + results := make([]*TransactionResult, 0) + + // empty blocks do not require execution, treat as a no-op + if b.pendingBlock.Empty() { + return results, nil + } + + header := b.pendingBlock.Block().Header + blockContext := b.setFVMContextFromHeader(header) + + // cannot execute a block that has already executed + if b.pendingBlock.ExecutionComplete() { + return results, &PendingBlockTransactionsExhaustedError{ + BlockID: b.pendingBlock.ID(), + } + } + + // continue executing transactions until execution is complete + for !b.pendingBlock.ExecutionComplete() { + result, err := b.executeNextTransaction(blockContext) + if err != nil { + return results, err + } + + results = append(results, result) + } + + return results, nil +} + +// ExecuteNextTransaction executes the next indexed transaction in pending block. +func (b *Blockchain) ExecuteNextTransaction() (*TransactionResult, error) { + b.mu.Lock() + defer b.mu.Unlock() + + header := b.pendingBlock.Block().Header + blockContext := b.setFVMContextFromHeader(header) + return b.executeNextTransaction(blockContext) +} + +// executeNextTransaction is a helper function for ExecuteBlock and ExecuteNextTransaction that +// executes the next transaction in the pending block. +func (b *Blockchain) executeNextTransaction(ctx fvm.Context) (*TransactionResult, error) { + // check if there are remaining txs to be executed + if b.pendingBlock.ExecutionComplete() { + return nil, &PendingBlockTransactionsExhaustedError{ + BlockID: b.pendingBlock.ID(), + } + } + + txnBody := b.pendingBlock.NextTransaction() + txnId := txnBody.ID() + + // use the computer to execute the next transaction + output, err := b.pendingBlock.ExecuteNextTransaction(b.vm, ctx) + if err != nil { + // fail fast if fatal error occurs + return nil, err + } + + tr, err := VMTransactionResultToEmulator(txnId, output) + if err != nil { + // fail fast if fatal error occurs + return nil, err + } + + return tr, nil +} + +// CommitBlock seals the current pending block and saves it to storage. +// +// This function clears the pending transaction pool and resets the pending block. +func (b *Blockchain) CommitBlock() (*flowgo.Block, error) { + b.mu.Lock() + defer b.mu.Unlock() + + block, err := b.commitBlock() + if err != nil { + return nil, err + } + + return block, nil +} + +func (b *Blockchain) commitBlock() (*flowgo.Block, error) { + // pending block cannot be committed before execution starts (unless empty) + if !b.pendingBlock.ExecutionStarted() && !b.pendingBlock.Empty() { + return nil, &PendingBlockCommitBeforeExecutionError{BlockID: b.pendingBlock.ID()} + } + + // pending block cannot be committed before execution completes + if b.pendingBlock.ExecutionStarted() && !b.pendingBlock.ExecutionComplete() { + return nil, &PendingBlockMidExecutionError{BlockID: b.pendingBlock.ID()} + } + + block := b.pendingBlock.Block() + collections := b.pendingBlock.Collections() + transactions := b.pendingBlock.Transactions() + transactionResults, err := convertToSealedResults(b.pendingBlock.TransactionResults(), b.pendingBlock.ID(), b.pendingBlock.height) + if err != nil { + return nil, err + } + + // lastly we execute the system chunk transaction + err = b.executeSystemChunkTransaction() + if err != nil { + return nil, err + } + + executionSnapshot := b.pendingBlock.Finalize() + events := b.pendingBlock.Events() + + // commit the pending block to storage + err = b.storage.CommitBlock( + context.Background(), + *block, + collections, + transactions, + transactionResults, + executionSnapshot, + events) + if err != nil { + return nil, err + } + + ledger, err := b.storage.LedgerByHeight( + context.Background(), + block.Header.Height, + ) + if err != nil { + return nil, err + } + + // notify listeners on new block + b.broadcaster.Publish() + + // reset pending block using current block and ledger state + b.pendingBlock = newPendingBlock(block, ledger, b.Now()) + b.entropyProvider.LatestBlock = block.ID() + + return block, nil +} + +// ExecuteAndCommitBlock is a utility that combines ExecuteBlock with CommitBlock. +func (b *Blockchain) ExecuteAndCommitBlock() (*flowgo.Block, []*TransactionResult, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.executeAndCommitBlock() +} + +// ExecuteAndCommitBlock is a utility that combines ExecuteBlock with CommitBlock. +func (b *Blockchain) executeAndCommitBlock() (*flowgo.Block, []*TransactionResult, error) { + + results, err := b.executeBlock() + if err != nil { + return nil, nil, err + } + + block, err := b.commitBlock() + if err != nil { + return nil, results, err + } + + for _, result := range results { + PrintTransactionResult(&b.conf.ServerLogger, result) + } + + blockID := block.ID() + b.conf.ServerLogger.Debug().Fields(map[string]any{ + "blockHeight": block.Header.Height, + "blockID": hex.EncodeToString(blockID[:]), + }).Msgf("📦 Block #%d committed", block.Header.Height) + + return block, results, nil +} + +// ResetPendingBlock clears the transactions in pending block. +func (b *Blockchain) ResetPendingBlock() error { + b.mu.Lock() + defer b.mu.Unlock() + + latestBlock, err := b.storage.LatestBlock(context.Background()) + if err != nil { + return err + } + + latestLedger, err := b.storage.LedgerByHeight( + context.Background(), + latestBlock.Header.Height, + ) + if err != nil { + return err + } + + // reset pending block using latest committed block and ledger state + b.pendingBlock = newPendingBlock(&latestBlock, latestLedger, b.Now()) + + return nil +} + +// ExecuteScript executes a read-only script against the world state and returns the result. +func (b *Blockchain) ExecuteScript( + script []byte, + arguments [][]byte, +) (*ScriptResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + latestBlock, err := b.getLatestBlock() + if err != nil { + return nil, err + } + + return b.executeScriptAtBlockID(script, arguments, latestBlock.Header.ID()) +} + +func (b *Blockchain) ExecuteScriptAtBlockID(script []byte, arguments [][]byte, id flowgo.Identifier) (*ScriptResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.executeScriptAtBlockID(script, arguments, id) +} + +func (b *Blockchain) executeScriptAtBlockID(script []byte, arguments [][]byte, id flowgo.Identifier) (*ScriptResult, error) { + requestedBlock, err := b.storage.BlockByID(context.Background(), id) + if err != nil { + return nil, err + } + + requestedLedgerSnapshot, err := b.storage.LedgerByHeight( + context.Background(), + requestedBlock.Header.Height, + ) + if err != nil { + return nil, err + } + + blockContext := fvm.NewContextFromParent( + b.vmCtx, + fvm.WithBlockHeader(requestedBlock.Header), + ) + + scriptProc := fvm.Script(script).WithArguments(arguments...) + + _, output, err := b.vm.Run( + blockContext, + scriptProc, + requestedLedgerSnapshot) + if err != nil { + return nil, err + } + + scriptID := flowgo.MakeIDFromFingerPrint(script) + + var scriptError error = nil + var convertedValue cadence.Value = nil + + if output.Err == nil { + convertedValue = output.Value + } else { + scriptError = VMErrorToEmulator(output.Err) + } + + scriptResult := &ScriptResult{ + ScriptID: scriptID, + Value: convertedValue, + Error: scriptError, + Logs: output.Logs, + Events: output.Events, + ComputationUsed: output.ComputationUsed, + MemoryEstimate: output.MemoryEstimate, + } + + return scriptResult, nil +} + +func (b *Blockchain) ExecuteScriptAtBlockHeight( + script []byte, + arguments [][]byte, + blockHeight uint64, +) (*ScriptResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + requestedBlock, err := b.getBlockByHeight(blockHeight) + if err != nil { + return nil, err + } + + return b.executeScriptAtBlockID(script, arguments, requestedBlock.Header.ID()) +} + +func convertToSealedResults( + results map[flowgo.Identifier]IndexedTransactionResult, + blockID flowgo.Identifier, + blockHeight uint64, +) (map[flowgo.Identifier]*StorableTransactionResult, error) { + + output := make(map[flowgo.Identifier]*StorableTransactionResult) + + for id, result := range results { + temp, err := ToStorableResult(result.ProcedureOutput, blockID, blockHeight) + if err != nil { + return nil, err + } + output[id] = &temp + } + + return output, nil +} + +func (b *Blockchain) GetTransactionsByBlockID(blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + block, err := b.getBlockByID(blockID) + if err != nil { + return nil, fmt.Errorf("failed to get block %s: %w", blockID, err) + } + + var transactions []*flowgo.TransactionBody + for i, guarantee := range block.Payload.Guarantees { + c, err := b.getCollectionByID(guarantee.CollectionID) + if err != nil { + return nil, fmt.Errorf("failed to get collection [%d] %s: %w", i, guarantee.CollectionID, err) + } + + for j, txID := range c.Transactions { + tx, err := b.getTransaction(txID) + if err != nil { + return nil, fmt.Errorf("failed to get transaction [%d] %s: %w", j, txID, err) + } + transactions = append(transactions, tx) + } + } + return transactions, nil +} + +func (b *Blockchain) GetTransactionResultsByBlockID(blockID flowgo.Identifier) ([]*access.TransactionResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + block, err := b.getBlockByID(blockID) + if err != nil { + return nil, fmt.Errorf("failed to get block %s: %w", blockID, err) + } + + var results []*access.TransactionResult + for i, guarantee := range block.Payload.Guarantees { + c, err := b.getCollectionByID(guarantee.CollectionID) + if err != nil { + return nil, fmt.Errorf("failed to get collection [%d] %s: %w", i, guarantee.CollectionID, err) + } + + for j, txID := range c.Transactions { + result, err := b.getTransactionResult(txID) + if err != nil { + return nil, fmt.Errorf("failed to get transaction result [%d] %s: %w", j, txID, err) + } + results = append(results, result) + } + } + return results, nil +} + +func (b *Blockchain) GetLogs(identifier flowgo.Identifier) ([]string, error) { + txResult, err := b.storage.TransactionResultByID(context.Background(), identifier) + if err != nil { + return nil, err + + } + return txResult.Logs, nil +} + +// SetClock sets the given clock on blockchain's pending block. +func (b *Blockchain) SetClock(clock func() time.Time) { + b.clockOverride = clock +} + +// NewScriptEnvironment returns an environment.Environment by +// using as a storage snapshot the blockchain's ledger state. +// Useful for tools that use the emulator's blockchain as a library. +func (b *Blockchain) NewScriptEnvironment() environment.Environment { + return environment.NewScriptEnvironmentFromStorageSnapshot( + b.vmCtx.EnvironmentParams, + b.pendingBlock.ledgerState.NewChild(), + ) +} + +func (b *Blockchain) systemChunkTransaction() (*flowgo.TransactionBody, error) { + serviceAddress := b.GetChain().ServiceAddress() + + script := templates.ReplaceAddresses( + systemChunkTransactionTemplate, + templates.Environment{ + RandomBeaconHistoryAddress: serviceAddress.Hex(), + }, + ) + + // TODO: move this to `templates.Environment` struct + script = strings.ReplaceAll( + script, + `import EVM from "EVM"`, + fmt.Sprintf( + "import EVM from %s", + serviceAddress.HexWithPrefix(), + ), + ) + + tx := flowgo.NewTransactionBody(). + SetScript([]byte(script)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + AddAuthorizer(serviceAddress). + SetPayer(serviceAddress). + SetReferenceBlockID(b.pendingBlock.parentID) + + return tx, nil +} + +func (b *Blockchain) executeSystemChunkTransaction() error { + txn, err := b.systemChunkTransaction() + if err != nil { + return err + } + ctx := fvm.NewContextFromParent( + b.vmCtx, + fvm.WithLogger(zerolog.Nop()), + fvm.WithAuthorizationChecksEnabled(false), + fvm.WithSequenceNumberCheckAndIncrementEnabled(false), + fvm.WithRandomSourceHistoryCallAllowed(true), + fvm.WithBlockHeader(b.pendingBlock.Block().Header), + ) + + executionSnapshot, output, err := b.vm.Run( + ctx, + fvm.Transaction(txn, uint32(len(b.pendingBlock.Transactions()))), + b.pendingBlock.ledgerState, + ) + if err != nil { + return err + } + + if output.Err != nil { + return output.Err + } + + b.pendingBlock.events = append(b.pendingBlock.events, output.Events...) + + err = b.pendingBlock.ledgerState.Merge(executionSnapshot) + if err != nil { + return err + } + + return nil +} + +func (b *Blockchain) GetRegisterValues(registerIDs flowgo.RegisterIDs, height uint64) (values []flowgo.RegisterValue, err error) { + ledger, err := b.storage.LedgerByHeight(context.Background(), height) + if err != nil { + return nil, err + } + for _, registerID := range registerIDs { + value, err := ledger.Get(registerID) + if err != nil { + return nil, err + } + values = append(values, value) + } + return values, nil +} diff --git a/integration/emulator/config.go b/integration/emulator/config.go new file mode 100644 index 00000000000..6654d873d97 --- /dev/null +++ b/integration/emulator/config.go @@ -0,0 +1,271 @@ +package emulator + +import ( + "fmt" + "github.com/onflow/cadence" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/meter" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" +) + +// config is a set of configuration options for an emulated emulator. +type config struct { + ServiceKey *ServiceKey + Store EmulatorStorage + SimpleAddresses bool + GenesisTokenSupply cadence.UFix64 + TransactionMaxGasLimit uint64 + ScriptGasLimit uint64 + TransactionExpiry uint + StorageLimitEnabled bool + TransactionFeesEnabled bool + ExecutionEffortWeights meter.ExecutionEffortWeights + ContractRemovalEnabled bool + MinimumStorageReservation cadence.UFix64 + StorageMBPerFLOW cadence.UFix64 + Logger zerolog.Logger + ServerLogger zerolog.Logger + TransactionValidationEnabled bool + ChainID flowgo.ChainID + AutoMine bool +} + +const defaultGenesisTokenSupply = "1000000000.0" +const defaultScriptGasLimit = 100000 +const defaultTransactionMaxGasLimit = flowgo.DefaultMaxTransactionGasLimit + +// defaultConfig is the default configuration for an emulated emulator. +var defaultConfig = func() config { + genesisTokenSupply, err := cadence.NewUFix64(defaultGenesisTokenSupply) + if err != nil { + panic(fmt.Sprintf("Failed to parse default genesis token supply: %s", err.Error())) + } + + return config{ + ServiceKey: DefaultServiceKey(), + Store: nil, + SimpleAddresses: false, + GenesisTokenSupply: genesisTokenSupply, + ScriptGasLimit: defaultScriptGasLimit, + TransactionMaxGasLimit: defaultTransactionMaxGasLimit, + MinimumStorageReservation: fvm.DefaultMinimumStorageReservation, + StorageMBPerFLOW: fvm.DefaultStorageMBPerFLOW, + TransactionExpiry: 0, // TODO: replace with sensible default + StorageLimitEnabled: true, + Logger: zerolog.Nop(), + ServerLogger: zerolog.Nop(), + TransactionValidationEnabled: true, + ChainID: flowgo.Emulator, + AutoMine: false, + } +}() + +func (conf config) GetStore() EmulatorStorage { + if conf.Store == nil { + conf.Store = NewMemoryStore() + } + return conf.Store +} + +func (conf config) GetChainID() flowgo.ChainID { + if conf.SimpleAddresses { + return flowgo.MonotonicEmulator + } + return conf.ChainID +} + +func (conf config) GetServiceKey() ServiceKey { + // set up service key + serviceKey := conf.ServiceKey + if serviceKey == nil { + serviceKey = DefaultServiceKey() + } + serviceKey.Address = conf.GetChainID().Chain().ServiceAddress() + serviceKey.Weight = fvm.AccountKeyWeightThreshold + return *serviceKey +} + +// Option is a function applying a change to the emulator config. +type Option func(*config) + +// WithLogger sets the fvm logger +func WithLogger( + logger zerolog.Logger, +) Option { + return func(c *config) { + c.Logger = logger + } +} + +// WithServerLogger sets the logger +func WithServerLogger( + logger zerolog.Logger, +) Option { + return func(c *config) { + c.ServerLogger = logger + } +} + +// WithServicePublicKey sets the service key from a public key. +func WithServicePublicKey( + servicePublicKey crypto.PublicKey, + sigAlgo crypto.SigningAlgorithm, + hashAlgo hash.HashingAlgorithm, +) Option { + return func(c *config) { + c.ServiceKey = &ServiceKey{ + PublicKey: servicePublicKey, + SigAlgo: sigAlgo, + HashAlgo: hashAlgo, + } + } +} + +// WithServicePrivateKey sets the service key from private key. +func WithServicePrivateKey( + privateKey crypto.PrivateKey, + sigAlgo crypto.SigningAlgorithm, + hashAlgo hash.HashingAlgorithm, +) Option { + return func(c *config) { + c.ServiceKey = &ServiceKey{ + PrivateKey: privateKey, + PublicKey: privateKey.PublicKey(), + HashAlgo: hashAlgo, + SigAlgo: sigAlgo, + } + } +} + +// WithStore sets the persistent storage provider. +func WithStore(store EmulatorStorage) Option { + return func(c *config) { + c.Store = store + } +} + +// WithSimpleAddresses enables simple addresses, which are sequential starting with 0x01. +func WithSimpleAddresses() Option { + return func(c *config) { + c.SimpleAddresses = true + } +} + +// WithGenesisTokenSupply sets the genesis token supply. +func WithGenesisTokenSupply(supply cadence.UFix64) Option { + return func(c *config) { + c.GenesisTokenSupply = supply + } +} + +// WithTransactionMaxGasLimit sets the maximum gas limit for transactions. +// +// Individual transactions will still be bounded by the limit they declare. +// This function sets the maximum limit that any transaction can declare. +// +// This limit does not affect script executions. Use WithScriptGasLimit +// to set the gas limit for script executions. +func WithTransactionMaxGasLimit(maxLimit uint64) Option { + return func(c *config) { + c.TransactionMaxGasLimit = maxLimit + } +} + +// WithScriptGasLimit sets the gas limit for scripts. +// +// This limit does not affect transactions, which declare their own limit. +// Use WithTransactionMaxGasLimit to set the maximum gas limit for transactions. +func WithScriptGasLimit(limit uint64) Option { + return func(c *config) { + c.ScriptGasLimit = limit + } +} + +// WithTransactionExpiry sets the transaction expiry measured in blocks. +// +// If set to zero, transaction expiry is disabled and the reference block ID field +// is not required. +func WithTransactionExpiry(expiry uint) Option { + return func(c *config) { + c.TransactionExpiry = expiry + } +} + +// WithStorageLimitEnabled enables/disables limiting account storage used to their storage capacity. +// +// If set to false, accounts can store any amount of data, +// otherwise they can only store as much as their storage capacity. +// The default is true. +func WithStorageLimitEnabled(enabled bool) Option { + return func(c *config) { + c.StorageLimitEnabled = enabled + } +} + +// WithMinimumStorageReservation sets the minimum account balance. +// +// The cost of creating new accounts is also set to this value. +// The default is taken from fvm.DefaultMinimumStorageReservation +func WithMinimumStorageReservation(minimumStorageReservation cadence.UFix64) Option { + return func(c *config) { + c.MinimumStorageReservation = minimumStorageReservation + } +} + +// WithStorageMBPerFLOW sets the cost of a megabyte of storage in FLOW +// +// the default is taken from fvm.DefaultStorageMBPerFLOW +func WithStorageMBPerFLOW(storageMBPerFLOW cadence.UFix64) Option { + return func(c *config) { + c.StorageMBPerFLOW = storageMBPerFLOW + } +} + +// WithTransactionFeesEnabled enables/disables transaction fees. +// +// If set to false transactions don't cost any flow. +// The default is false. +func WithTransactionFeesEnabled(enabled bool) Option { + return func(c *config) { + c.TransactionFeesEnabled = enabled + } +} + +// WithExecutionEffortWeights sets the execution effort weights. +// default is the Mainnet values. +func WithExecutionEffortWeights(weights meter.ExecutionEffortWeights) Option { + return func(c *config) { + c.ExecutionEffortWeights = weights + } +} + +// WithContractRemovalEnabled restricts/allows removal of already deployed contracts. +// +// The default is provided by on-chain value. +func WithContractRemovalEnabled(enabled bool) Option { + return func(c *config) { + c.ContractRemovalEnabled = enabled + } +} + +// WithTransactionValidationEnabled enables/disables transaction validation. +// +// If set to false, the emulator will not verify transaction signatures or validate sequence numbers. +// +// The default is true. +func WithTransactionValidationEnabled(enabled bool) Option { + return func(c *config) { + c.TransactionValidationEnabled = enabled + } +} + +// WithChainID sets chain type for address generation +// The default is emulator. +func WithChainID(chainID flowgo.ChainID) Option { + return func(c *config) { + c.ChainID = chainID + } +} diff --git a/integration/emulator/convert.go b/integration/emulator/convert.go new file mode 100644 index 00000000000..85ba557d95e --- /dev/null +++ b/integration/emulator/convert.go @@ -0,0 +1,374 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "fmt" + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/ccf" + sdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/fvm" + fvmerrors "github.com/onflow/flow-go/fvm/errors" + flowgo "github.com/onflow/flow-go/model/flow" +) + +func SDKIdentifierToFlow(sdkIdentifier sdk.Identifier) flowgo.Identifier { + return flowgo.Identifier(sdkIdentifier) +} + +func SDKIdentifiersToFlow(sdkIdentifiers []sdk.Identifier) []flowgo.Identifier { + ret := make([]flowgo.Identifier, len(sdkIdentifiers)) + for i, sdkIdentifier := range sdkIdentifiers { + ret[i] = SDKIdentifierToFlow(sdkIdentifier) + } + return ret +} + +func FlowIdentifierToSDK(flowIdentifier flowgo.Identifier) sdk.Identifier { + return sdk.Identifier(flowIdentifier) +} + +func FlowIdentifiersToSDK(flowIdentifiers []flowgo.Identifier) []sdk.Identifier { + ret := make([]sdk.Identifier, len(flowIdentifiers)) + for i, flowIdentifier := range flowIdentifiers { + ret[i] = FlowIdentifierToSDK(flowIdentifier) + } + return ret +} + +func SDKProposalKeyToFlow(sdkProposalKey sdk.ProposalKey) flowgo.ProposalKey { + return flowgo.ProposalKey{ + Address: SDKAddressToFlow(sdkProposalKey.Address), + KeyIndex: sdkProposalKey.KeyIndex, + SequenceNumber: sdkProposalKey.SequenceNumber, + } +} + +func FlowProposalKeyToSDK(flowProposalKey flowgo.ProposalKey) sdk.ProposalKey { + return sdk.ProposalKey{ + Address: FlowAddressToSDK(flowProposalKey.Address), + KeyIndex: flowProposalKey.KeyIndex, + SequenceNumber: flowProposalKey.SequenceNumber, + } +} + +func SDKAddressToFlow(sdkAddress sdk.Address) flowgo.Address { + return flowgo.Address(sdkAddress) +} + +func FlowAddressToSDK(flowAddress flowgo.Address) sdk.Address { + return sdk.Address(flowAddress) +} + +func SDKAddressesToFlow(sdkAddresses []sdk.Address) []flowgo.Address { + ret := make([]flowgo.Address, len(sdkAddresses)) + for i, sdkAddress := range sdkAddresses { + ret[i] = SDKAddressToFlow(sdkAddress) + } + return ret +} + +func FlowAddressesToSDK(flowAddresses []flowgo.Address) []sdk.Address { + ret := make([]sdk.Address, len(flowAddresses)) + for i, flowAddress := range flowAddresses { + ret[i] = FlowAddressToSDK(flowAddress) + } + return ret +} + +func SDKTransactionSignatureToFlow(sdkTransactionSignature sdk.TransactionSignature) flowgo.TransactionSignature { + return flowgo.TransactionSignature{ + Address: SDKAddressToFlow(sdkTransactionSignature.Address), + SignerIndex: sdkTransactionSignature.SignerIndex, + KeyIndex: sdkTransactionSignature.KeyIndex, + Signature: sdkTransactionSignature.Signature, + } +} + +func FlowTransactionSignatureToSDK(flowTransactionSignature flowgo.TransactionSignature) sdk.TransactionSignature { + return sdk.TransactionSignature{ + Address: FlowAddressToSDK(flowTransactionSignature.Address), + SignerIndex: flowTransactionSignature.SignerIndex, + KeyIndex: flowTransactionSignature.KeyIndex, + Signature: flowTransactionSignature.Signature, + } +} + +func SDKTransactionSignaturesToFlow(sdkTransactionSignatures []sdk.TransactionSignature) []flowgo.TransactionSignature { + ret := make([]flowgo.TransactionSignature, len(sdkTransactionSignatures)) + for i, sdkTransactionSignature := range sdkTransactionSignatures { + ret[i] = SDKTransactionSignatureToFlow(sdkTransactionSignature) + } + return ret +} + +func FlowTransactionSignaturesToSDK(flowTransactionSignatures []flowgo.TransactionSignature) []sdk.TransactionSignature { + ret := make([]sdk.TransactionSignature, len(flowTransactionSignatures)) + for i, flowTransactionSignature := range flowTransactionSignatures { + ret[i] = FlowTransactionSignatureToSDK(flowTransactionSignature) + } + return ret +} + +func SDKTransactionToFlow(sdkTx sdk.Transaction) *flowgo.TransactionBody { + return &flowgo.TransactionBody{ + ReferenceBlockID: SDKIdentifierToFlow(sdkTx.ReferenceBlockID), + Script: sdkTx.Script, + Arguments: sdkTx.Arguments, + GasLimit: sdkTx.GasLimit, + ProposalKey: SDKProposalKeyToFlow(sdkTx.ProposalKey), + Payer: SDKAddressToFlow(sdkTx.Payer), + Authorizers: SDKAddressesToFlow(sdkTx.Authorizers), + PayloadSignatures: SDKTransactionSignaturesToFlow(sdkTx.PayloadSignatures), + EnvelopeSignatures: SDKTransactionSignaturesToFlow(sdkTx.EnvelopeSignatures), + } +} + +func FlowTransactionToSDK(flowTx flowgo.TransactionBody) sdk.Transaction { + transaction := sdk.Transaction{ + ReferenceBlockID: FlowIdentifierToSDK(flowTx.ReferenceBlockID), + Script: flowTx.Script, + Arguments: flowTx.Arguments, + GasLimit: flowTx.GasLimit, + ProposalKey: FlowProposalKeyToSDK(flowTx.ProposalKey), + Payer: FlowAddressToSDK(flowTx.Payer), + Authorizers: FlowAddressesToSDK(flowTx.Authorizers), + PayloadSignatures: FlowTransactionSignaturesToSDK(flowTx.PayloadSignatures), + EnvelopeSignatures: FlowTransactionSignaturesToSDK(flowTx.EnvelopeSignatures), + } + return transaction +} + +func FlowTransactionResultToSDK(result *access.TransactionResult) (*sdk.TransactionResult, error) { + + events, err := FlowEventsToSDK(result.Events) + if err != nil { + return nil, err + } + + if result.ErrorMessage != "" { + err = &ExecutionError{Code: int(result.StatusCode), Message: result.ErrorMessage} + } + + sdkResult := &sdk.TransactionResult{ + Status: sdk.TransactionStatus(result.Status), + Error: err, + Events: events, + TransactionID: sdk.Identifier(result.TransactionID), + BlockHeight: result.BlockHeight, + BlockID: sdk.Identifier(result.BlockID), + } + + return sdkResult, nil +} + +func SDKEventToFlow(event sdk.Event) (flowgo.Event, error) { + payload, err := ccf.EventsEncMode.Encode(event.Value) + if err != nil { + return flowgo.Event{}, err + } + + return flowgo.Event{ + Type: flowgo.EventType(event.Type), + TransactionID: SDKIdentifierToFlow(event.TransactionID), + TransactionIndex: uint32(event.TransactionIndex), + EventIndex: uint32(event.EventIndex), + Payload: payload, + }, nil +} + +func FlowEventToSDK(flowEvent flowgo.Event) (sdk.Event, error) { + cadenceValue, err := ccf.EventsDecMode.Decode(nil, flowEvent.Payload) + if err != nil { + return sdk.Event{}, err + } + + cadenceEvent, ok := cadenceValue.(cadence.Event) + if !ok { + return sdk.Event{}, fmt.Errorf("cadence value not of type event: %s", cadenceValue) + } + + return sdk.Event{ + Type: string(flowEvent.Type), + TransactionID: FlowIdentifierToSDK(flowEvent.TransactionID), + TransactionIndex: int(flowEvent.TransactionIndex), + EventIndex: int(flowEvent.EventIndex), + Value: cadenceEvent, + }, nil +} + +func FlowEventsToSDK(flowEvents []flowgo.Event) ([]sdk.Event, error) { + ret := make([]sdk.Event, len(flowEvents)) + var err error + for i, flowEvent := range flowEvents { + ret[i], err = FlowEventToSDK(flowEvent) + if err != nil { + return nil, err + } + } + return ret, nil +} + +func FlowAccountPublicKeyToSDK(flowPublicKey flowgo.AccountPublicKey, index uint32) (sdk.AccountKey, error) { + + return sdk.AccountKey{ + Index: index, + PublicKey: flowPublicKey.PublicKey, + SigAlgo: flowPublicKey.SignAlgo, + HashAlgo: flowPublicKey.HashAlgo, + Weight: flowPublicKey.Weight, + SequenceNumber: flowPublicKey.SeqNumber, + Revoked: flowPublicKey.Revoked, + }, nil +} + +func SDKAccountKeyToFlow(key *sdk.AccountKey) (flowgo.AccountPublicKey, error) { + + return flowgo.AccountPublicKey{ + Index: key.Index, + PublicKey: key.PublicKey, + SignAlgo: key.SigAlgo, + HashAlgo: key.HashAlgo, + Weight: key.Weight, + SeqNumber: key.SequenceNumber, + Revoked: key.Revoked, + }, nil +} + +func SDKAccountKeysToFlow(keys []*sdk.AccountKey) ([]flowgo.AccountPublicKey, error) { + accountKeys := make([]flowgo.AccountPublicKey, len(keys)) + + for i, key := range keys { + accountKey, err := SDKAccountKeyToFlow(key) + if err != nil { + return nil, err + } + + accountKeys[i] = accountKey + } + + return accountKeys, nil +} + +func FlowAccountPublicKeysToSDK(flowPublicKeys []flowgo.AccountPublicKey) ([]*sdk.AccountKey, error) { + ret := make([]*sdk.AccountKey, len(flowPublicKeys)) + for i, flowPublicKey := range flowPublicKeys { + v, err := FlowAccountPublicKeyToSDK(flowPublicKey, uint32(i)) + if err != nil { + return nil, err + } + + ret[i] = &v + } + return ret, nil +} + +func FlowAccountToSDK(flowAccount flowgo.Account) (*sdk.Account, error) { + sdkPublicKeys, err := FlowAccountPublicKeysToSDK(flowAccount.Keys) + if err != nil { + return &sdk.Account{}, err + } + + return &sdk.Account{ + Address: FlowAddressToSDK(flowAccount.Address), + Balance: flowAccount.Balance, + Code: nil, + Keys: sdkPublicKeys, + Contracts: flowAccount.Contracts, + }, nil +} + +func SDKAccountToFlow(account *sdk.Account) (*flowgo.Account, error) { + keys, err := SDKAccountKeysToFlow(account.Keys) + if err != nil { + return nil, err + } + + return &flowgo.Account{ + Address: SDKAddressToFlow(account.Address), + Balance: account.Balance, + Keys: keys, + Contracts: account.Contracts, + }, nil +} + +func FlowLightCollectionToSDK(flowCollection flowgo.LightCollection) sdk.Collection { + return sdk.Collection{ + TransactionIDs: FlowIdentifiersToSDK(flowCollection.Transactions), + } +} + +func VMTransactionResultToEmulator( + txnId flowgo.Identifier, + output fvm.ProcedureOutput, +) ( + *TransactionResult, + error, +) { + txID := FlowIdentifierToSDK(txnId) + + sdkEvents, err := FlowEventsToSDK(output.Events) + if err != nil { + return nil, err + } + + return &TransactionResult{ + TransactionID: txID, + ComputationUsed: output.ComputationUsed, + MemoryEstimate: output.MemoryEstimate, + Error: VMErrorToEmulator(output.Err), + Logs: output.Logs, + Events: sdkEvents, + }, nil +} + +func VMErrorToEmulator(vmError fvmerrors.CodedError) error { + if vmError == nil { + return nil + } + + return &FVMError{FlowError: vmError} +} + +func ToStorableResult( + output fvm.ProcedureOutput, + blockID flowgo.Identifier, + blockHeight uint64, +) ( + StorableTransactionResult, + error, +) { + var errorCode int + var errorMessage string + + if output.Err != nil { + errorCode = int(output.Err.Code()) + errorMessage = output.Err.Error() + } + + return StorableTransactionResult{ + BlockID: blockID, + BlockHeight: blockHeight, + ErrorCode: errorCode, + ErrorMessage: errorMessage, + Logs: output.Logs, + Events: output.Events, + }, nil +} diff --git a/integration/emulator/emulator.go b/integration/emulator/emulator.go new file mode 100644 index 00000000000..707a6b2b23c --- /dev/null +++ b/integration/emulator/emulator.go @@ -0,0 +1,171 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "fmt" + sdkcrypto "github.com/onflow/flow-go-sdk/crypto" + + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/access" + flowgo "github.com/onflow/flow-go/model/flow" +) + +// SignatureAlgorithm is an identifier for a signature algorithm (and parameters if applicable). +type SignatureAlgorithm = crypto.SigningAlgorithm + +const ( + UnknownSignatureAlgorithm SignatureAlgorithm = crypto.UnknownSigningAlgorithm + // ECDSA_P256 is ECDSA on NIST P-256 curve + ECDSA_P256 = crypto.ECDSAP256 + // ECDSA_secp256k1 is ECDSA on secp256k1 curve + ECDSA_secp256k1 = crypto.ECDSASecp256k1 + // BLS_BLS12_381 is BLS on BLS12-381 curve + BLS_BLS12_381 = crypto.BLSBLS12381 +) + +// StringToSignatureAlgorithm converts a string to a SignatureAlgorithm. +func StringToSignatureAlgorithm(s string) SignatureAlgorithm { + switch s { + case ECDSA_P256.String(): + return ECDSA_P256 + case ECDSA_secp256k1.String(): + return ECDSA_secp256k1 + case BLS_BLS12_381.String(): + return BLS_BLS12_381 + default: + return UnknownSignatureAlgorithm + } +} + +type ServiceKey struct { + Index uint32 + Address flowgo.Address + SequenceNumber uint64 + PrivateKey crypto.PrivateKey + PublicKey crypto.PublicKey + HashAlgo hash.HashingAlgorithm + SigAlgo SignatureAlgorithm + Weight int +} + +const defaultServiceKeyPrivateKeySeed = "elephant ears space cowboy octopus rodeo potato cannon pineapple" +const DefaultServiceKeySigAlgo = sdkcrypto.ECDSA_P256 +const DefaultServiceKeyHashAlgo = sdkcrypto.SHA3_256 + +func DefaultServiceKey() *ServiceKey { + return GenerateDefaultServiceKey(DefaultServiceKeySigAlgo, DefaultServiceKeyHashAlgo) +} + +func GenerateDefaultServiceKey( + sigAlgo crypto.SigningAlgorithm, + hashAlgo hash.HashingAlgorithm, +) *ServiceKey { + privateKey, err := crypto.GeneratePrivateKey( + sigAlgo, + []byte(defaultServiceKeyPrivateKeySeed), + ) + if err != nil { + panic(fmt.Sprintf("Failed to generate default service key: %s", err.Error())) + } + + return &ServiceKey{ + PrivateKey: privateKey, + PublicKey: privateKey.PublicKey(), + SigAlgo: sigAlgo, + HashAlgo: hashAlgo, + } +} + +func (s ServiceKey) Signer() (sdkcrypto.Signer, error) { + return sdkcrypto.NewInMemorySigner(s.PrivateKey, s.HashAlgo) +} + +func (s ServiceKey) AccountKey() (crypto.PublicKey, crypto.PrivateKey) { + + var publicKey crypto.PublicKey + if s.PublicKey != nil { + publicKey = s.PublicKey + } + + if s.PrivateKey != nil { + publicKey = s.PrivateKey.PublicKey() + } + + return publicKey, s.PrivateKey + +} + +type AccessProvider interface { + Ping() error + GetNetworkParameters() access.NetworkParameters + + GetLatestBlock() (*flowgo.Block, error) + GetBlockByID(id flowgo.Identifier) (*flowgo.Block, error) + GetBlockByHeight(height uint64) (*flowgo.Block, error) + + GetCollectionByID(colID flowgo.Identifier) (*flowgo.LightCollection, error) + GetFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) + + GetTransaction(txID flowgo.Identifier) (*flowgo.TransactionBody, error) + GetTransactionResult(txID flowgo.Identifier) (*access.TransactionResult, error) + GetTransactionsByBlockID(blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) + GetTransactionResultsByBlockID(blockID flowgo.Identifier) ([]*access.TransactionResult, error) + + GetAccount(address flowgo.Address) (*flowgo.Account, error) + GetAccountAtBlockHeight(address flowgo.Address, blockHeight uint64) (*flowgo.Account, error) + GetAccountByIndex(uint) (*flowgo.Account, error) + + GetEventsByHeight(blockHeight uint64, eventType string) ([]flowgo.Event, error) + GetEventsForBlockIDs(eventType string, blockIDs []flowgo.Identifier) ([]flowgo.BlockEvents, error) + GetEventsForHeightRange(eventType string, startHeight, endHeight uint64) ([]flowgo.BlockEvents, error) + + ExecuteScript(script []byte, arguments [][]byte) (*ScriptResult, error) + ExecuteScriptAtBlockHeight(script []byte, arguments [][]byte, blockHeight uint64) (*ScriptResult, error) + ExecuteScriptAtBlockID(script []byte, arguments [][]byte, id flowgo.Identifier) (*ScriptResult, error) + + SendTransaction(tx *flowgo.TransactionBody) error + AddTransaction(tx flowgo.TransactionBody) error +} + +type AutoMineCapable interface { + EnableAutoMine() + DisableAutoMine() +} + +type ExecutionCapable interface { + ExecuteAndCommitBlock() (*flowgo.Block, []*TransactionResult, error) + ExecuteNextTransaction() (*TransactionResult, error) + ExecuteBlock() ([]*TransactionResult, error) + CommitBlock() (*flowgo.Block, error) +} + +type Contract struct { + Name string + Source string +} + +// Emulator defines the method set of an emulated emulator. +type Emulator interface { + ServiceKey() ServiceKey + AccessProvider + AutoMineCapable + ExecutionCapable +} diff --git a/integration/emulator/errors.go b/integration/emulator/errors.go new file mode 100644 index 00000000000..fe275eb409d --- /dev/null +++ b/integration/emulator/errors.go @@ -0,0 +1,274 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "errors" + "fmt" + + fvmerrors "github.com/onflow/flow-go/fvm/errors" + + "github.com/onflow/flow-go-sdk/crypto" + "github.com/onflow/flow-go/access" + flowgo "github.com/onflow/flow-go/model/flow" +) + +var ErrNotFound = errors.New("could not find entity") + +type InvalidArgumentError struct { + msg string +} + +func (e InvalidArgumentError) Error() string { + return fmt.Sprintf("Invalid argument error: %s", e.msg) +} + +func NewInvalidArgumentError(msg string) *InvalidArgumentError { + return &InvalidArgumentError{msg: msg} +} + +type InternalError struct { + msg string +} + +func (e InternalError) Error() string { + return fmt.Sprintf("Internal error: %s", e.msg) +} + +func NewInternalError(msg string) *InternalError { + return &InternalError{msg: msg} +} + +// A NotFoundError indicates that an entity could not be found. +type NotFoundError interface { + isNotFoundError() +} + +// A BlockNotFoundError indicates that a block could not be found. +type BlockNotFoundError interface { + isBlockNotFoundError() +} + +// A BlockNotFoundByHeightError indicates that a block could not be found at the specified height. +type BlockNotFoundByHeightError struct { + Height uint64 +} + +func (e *BlockNotFoundByHeightError) isNotFoundError() {} +func (e *BlockNotFoundByHeightError) isBlockNotFoundError() {} + +func (e *BlockNotFoundByHeightError) Error() string { + return fmt.Sprintf("could not find block at height %d", e.Height) +} + +// A BlockNotFoundByIDError indicates that a block with the specified ID could not be found. +type BlockNotFoundByIDError struct { + ID flowgo.Identifier +} + +func (e *BlockNotFoundByIDError) isNotFoundError() {} +func (e *BlockNotFoundByIDError) isBlockNotFoundError() {} + +func (e *BlockNotFoundByIDError) Error() string { + return fmt.Sprintf("could not find block with ID %s", e.ID) +} + +// A CollectionNotFoundError indicates that a collection could not be found. +type CollectionNotFoundError struct { + ID flowgo.Identifier +} + +func (e *CollectionNotFoundError) isNotFoundError() {} + +func (e *CollectionNotFoundError) Error() string { + return fmt.Sprintf("could not find collection with ID %s", e.ID) +} + +// A TransactionNotFoundError indicates that a transaction could not be found. +type TransactionNotFoundError struct { + ID flowgo.Identifier +} + +func (e *TransactionNotFoundError) isNotFoundError() {} + +func (e *TransactionNotFoundError) Error() string { + return fmt.Sprintf("could not find transaction with ID %s", e.ID) +} + +// An AccountNotFoundError indicates that an account could not be found. +type AccountNotFoundError struct { + Address flowgo.Address +} + +func (e *AccountNotFoundError) isNotFoundError() {} + +func (e *AccountNotFoundError) Error() string { + return fmt.Sprintf("could not find account with address %s", e.Address) +} + +// A TransactionValidationError indicates that a submitted transaction is invalid. +type TransactionValidationError interface { + isTransactionValidationError() +} + +// A DuplicateTransactionError indicates that a transaction has already been submitted. +type DuplicateTransactionError struct { + TxID flowgo.Identifier +} + +func (e *DuplicateTransactionError) isTransactionValidationError() {} + +func (e *DuplicateTransactionError) Error() string { + return fmt.Sprintf("transaction with ID %s has already been submitted", e.TxID) +} + +// IncompleteTransactionError indicates that a transaction is missing one or more required fields. +type IncompleteTransactionError struct { + MissingFields []string +} + +func (e *IncompleteTransactionError) isTransactionValidationError() {} + +func (e *IncompleteTransactionError) Error() string { + return fmt.Sprintf("transaction is missing required fields: %s", e.MissingFields) +} + +// ExpiredTransactionError indicates that a transaction has expired. +type ExpiredTransactionError struct { + RefHeight, FinalHeight uint64 +} + +func (e *ExpiredTransactionError) isTransactionValidationError() {} + +func (e *ExpiredTransactionError) Error() string { + return fmt.Sprintf("transaction is expired: ref_height=%d final_height=%d", e.RefHeight, e.FinalHeight) +} + +// InvalidTransactionScriptError indicates that a transaction contains an invalid Cadence script. +type InvalidTransactionScriptError struct { + ParserErr error +} + +func (e *InvalidTransactionScriptError) isTransactionValidationError() {} + +func (e *InvalidTransactionScriptError) Error() string { + return fmt.Sprintf("failed to parse transaction Cadence script: %s", e.ParserErr) +} + +func (e *InvalidTransactionScriptError) Unwrap() error { + return e.ParserErr +} + +// InvalidTransactionGasLimitError indicates that a transaction specifies a gas limit that exceeds the maximum. +type InvalidTransactionGasLimitError struct { + Maximum uint64 + Actual uint64 +} + +func (e *InvalidTransactionGasLimitError) isTransactionValidationError() {} + +func (e *InvalidTransactionGasLimitError) Error() string { + return fmt.Sprintf("transaction gas limit (%d) exceeds the maximum gas limit (%d)", e.Actual, e.Maximum) +} + +// An InvalidStateVersionError indicates that a state version hash provided is invalid. +type InvalidStateVersionError struct { + Version crypto.Hash +} + +func (e *InvalidStateVersionError) Error() string { + return fmt.Sprintf("execution state with version hash %x is invalid", e.Version) +} + +// A PendingBlockCommitBeforeExecutionError indicates that the current pending block has not been executed (cannot commit). +type PendingBlockCommitBeforeExecutionError struct { + BlockID flowgo.Identifier +} + +func (e *PendingBlockCommitBeforeExecutionError) Error() string { + return fmt.Sprintf("pending block with ID %s cannot be committed before execution", e.BlockID) +} + +// A PendingBlockMidExecutionError indicates that the current pending block is mid-execution. +type PendingBlockMidExecutionError struct { + BlockID flowgo.Identifier +} + +func (e *PendingBlockMidExecutionError) Error() string { + return fmt.Sprintf("pending block with ID %s is currently being executed", e.BlockID) +} + +// A PendingBlockTransactionsExhaustedError indicates that the current pending block has finished executing (no more transactions to execute). +type PendingBlockTransactionsExhaustedError struct { + BlockID flowgo.Identifier +} + +func (e *PendingBlockTransactionsExhaustedError) Error() string { + return fmt.Sprintf("pending block with ID %s contains no more transactions to execute", e.BlockID) +} + +// A StorageError indicates that an error occurred in the storage provider. +type StorageError struct { + inner error +} + +func (e *StorageError) Error() string { + return fmt.Sprintf("storage failure: %v", e.inner) +} + +func (e *StorageError) Unwrap() error { + return e.inner +} + +// An ExecutionError occurs when a transaction fails to execute. +type ExecutionError struct { + Code int + Message string +} + +func (e *ExecutionError) Error() string { + return fmt.Sprintf("execution error code %d: %s", e.Code, e.Message) +} + +type FVMError struct { + FlowError fvmerrors.CodedError +} + +func (f *FVMError) Error() string { + return f.FlowError.Error() +} + +func (f *FVMError) Unwrap() error { + return f.FlowError +} + +func ConvertAccessError(err error) error { + switch typedErr := err.(type) { + case access.IncompleteTransactionError: + return &IncompleteTransactionError{MissingFields: typedErr.MissingFields} + case access.ExpiredTransactionError: + return &ExpiredTransactionError{RefHeight: typedErr.RefHeight, FinalHeight: typedErr.FinalHeight} + case access.InvalidGasLimitError: + return &InvalidTransactionGasLimitError{Maximum: typedErr.Maximum, Actual: typedErr.Actual} + case access.InvalidScriptError: + return &InvalidTransactionScriptError{ParserErr: typedErr.ParserErr} + } + + return err +} diff --git a/integration/emulator/ledger.go b/integration/emulator/ledger.go new file mode 100644 index 00000000000..7064ebbd4d6 --- /dev/null +++ b/integration/emulator/ledger.go @@ -0,0 +1,141 @@ +package emulator + +import ( + "context" + "errors" + "fmt" + "github.com/onflow/cadence" + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/meter" + "github.com/onflow/flow-go/fvm/storage/snapshot" + flowgo "github.com/onflow/flow-go/model/flow" + "math" +) + +func configureLedger( + conf config, + store EmulatorStorage, + vm *fvm.VirtualMachine, + ctx fvm.Context, +) ( + *flowgo.Block, + snapshot.StorageSnapshot, + error, +) { + + latestBlock, err := store.LatestBlock(context.Background()) + if err != nil && !errors.Is(err, ErrNotFound) { + return nil, nil, err + } + + if errors.Is(err, ErrNotFound) { + // bootstrap the ledger with the genesis block + ledger, err := store.LedgerByHeight(context.Background(), 0) + if err != nil { + return nil, nil, err + } + + genesisExecutionSnapshot, err := bootstrapLedger(vm, ctx, ledger, conf) + if err != nil { + return nil, nil, fmt.Errorf("failed to bootstrap execution state: %w", err) + } + + // commit the genesis block to storage + genesis := flowgo.Genesis(conf.GetChainID()) + latestBlock = *genesis + + err = store.CommitBlock( + context.Background(), + *genesis, + nil, + nil, + nil, + genesisExecutionSnapshot, + nil, + ) + if err != nil { + return nil, nil, err + } + } + + latestLedger, err := store.LedgerByHeight( + context.Background(), + latestBlock.Header.Height, + ) + + if err != nil { + return nil, nil, err + } + + return &latestBlock, latestLedger, nil +} + +func bootstrapLedger( + vm *fvm.VirtualMachine, + ctx fvm.Context, + ledger snapshot.StorageSnapshot, + conf config, +) ( + *snapshot.ExecutionSnapshot, + error, +) { + serviceKey := conf.GetServiceKey() + + ctx = fvm.NewContextFromParent( + ctx, + fvm.WithAccountStorageLimit(false), + ) + + flowAccountKey := flowgo.AccountPublicKey{ + PublicKey: serviceKey.PublicKey, + SignAlgo: serviceKey.SigAlgo, + HashAlgo: serviceKey.HashAlgo, + Weight: fvm.AccountKeyWeightThreshold, + } + + bootstrap := configureBootstrapProcedure(conf, flowAccountKey, conf.GenesisTokenSupply) + + executionSnapshot, output, err := vm.Run(ctx, bootstrap, ledger) + if err != nil { + return nil, err + } + + if output.Err != nil { + return nil, output.Err + } + + return executionSnapshot, nil +} + +func configureBootstrapProcedure(conf config, flowAccountKey flowgo.AccountPublicKey, supply cadence.UFix64) *fvm.BootstrapProcedure { + options := make([]fvm.BootstrapProcedureOption, 0) + options = append(options, + fvm.WithInitialTokenSupply(supply), + fvm.WithRestrictedAccountCreationEnabled(false), + // This enables variable transaction fees AND execution effort metering + // as described in Variable Transaction Fees: + // Execution Effort FLIP: https://github.com/onflow/flow/pull/753 + fvm.WithTransactionFee(fvm.DefaultTransactionFees), + fvm.WithExecutionMemoryLimit(math.MaxUint32), + fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), + fvm.WithExecutionEffortWeights(environment.MainnetExecutionEffortWeights), + ) + + if conf.ExecutionEffortWeights != nil { + options = append(options, + fvm.WithExecutionEffortWeights(conf.ExecutionEffortWeights), + ) + } + if conf.StorageLimitEnabled { + options = append(options, + fvm.WithAccountCreationFee(conf.MinimumStorageReservation), + fvm.WithMinimumStorageReservation(conf.MinimumStorageReservation), + fvm.WithStorageMBPerFLOW(conf.StorageMBPerFLOW), + ) + } + return fvm.Bootstrap( + flowAccountKey, + options..., + ) +} diff --git a/integration/emulator/log.go b/integration/emulator/log.go new file mode 100644 index 00000000000..0da17361d22 --- /dev/null +++ b/integration/emulator/log.go @@ -0,0 +1,22 @@ +package emulator + +import ( + "github.com/logrusorgru/aurora" + "github.com/rs/zerolog" + "strings" +) + +type CadenceHook struct { + MainLogger *zerolog.Logger +} + +func (h CadenceHook) Run(_ *zerolog.Event, level zerolog.Level, msg string) { + const logPrefix = "Cadence log:" + if level != zerolog.NoLevel && strings.HasPrefix(msg, logPrefix) { + h.MainLogger.Info().Msg( + strings.Replace(msg, + logPrefix, + aurora.Colorize("LOG:", aurora.BlueFg|aurora.BoldFm).String(), + 1)) + } +} diff --git a/integration/emulator/logging.go b/integration/emulator/logging.go new file mode 100644 index 00000000000..27800804e9e --- /dev/null +++ b/integration/emulator/logging.go @@ -0,0 +1,93 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "fmt" + "github.com/logrusorgru/aurora" + sdk "github.com/onflow/flow-go-sdk" + "github.com/rs/zerolog" +) + +func PrintScriptResult(logger *zerolog.Logger, result *ScriptResult) { + if result.Succeeded() { + logger.Debug(). + Str("scriptID", result.ScriptID.String()). + Uint64("computationUsed", result.ComputationUsed). + Uint64("memoryEstimate", result.MemoryEstimate). + Msg("⭐ Script executed") + } else { + logger.Warn(). + Str("scriptID", result.ScriptID.String()). + Uint64("computationUsed", result.ComputationUsed). + Uint64("memoryEstimate", result.MemoryEstimate). + Msg("❗ Script reverted") + } + + if !result.Succeeded() { + logger.Warn().Msgf( + "%s %s", + logPrefix("ERR", FlowIdentifierToSDK(result.ScriptID), aurora.RedFg), + result.Error.Error(), + ) + } +} + +func PrintTransactionResult(logger *zerolog.Logger, result *TransactionResult) { + if result.Succeeded() { + logger.Debug(). + Str("txID", result.TransactionID.String()). + Uint64("computationUsed", result.ComputationUsed). + Uint64("memoryEstimate", result.MemoryEstimate). + Msg("⭐ Transaction executed") + } else { + logger.Warn(). + Str("txID", result.TransactionID.String()). + Uint64("computationUsed", result.ComputationUsed). + Uint64("memoryEstimate", result.MemoryEstimate). + Msg("❗ Transaction reverted") + } + + for _, event := range result.Events { + logger.Debug().Msgf( + "%s %s", + logPrefix("EVT", result.TransactionID, aurora.GreenFg), + event, + ) + } + + if !result.Succeeded() { + logger.Warn().Msgf( + "%s %s", + logPrefix("ERR", result.TransactionID, aurora.RedFg), + result.Error.Error(), + ) + + if result.Debug != nil { + logger.Debug().Fields(result.Debug.Meta).Msgf("%s %s", "❗ Transaction Signature Error", result.Debug.Message) + } + } +} + +func logPrefix(prefix string, id sdk.Identifier, color aurora.Color) string { + prefix = aurora.Colorize(prefix, color|aurora.BoldFm).String() + shortID := fmt.Sprintf("[%s]", id.String()[:6]) + shortID = aurora.Colorize(shortID, aurora.FaintFm).String() + return fmt.Sprintf("%s %s", prefix, shortID) +} diff --git a/integration/emulator/memstore.go b/integration/emulator/memstore.go new file mode 100644 index 00000000000..23bdfa7a0ac --- /dev/null +++ b/integration/emulator/memstore.go @@ -0,0 +1,395 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "context" + "errors" + "fmt" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/fvm/environment" + "sync" + + "github.com/onflow/flow-go/fvm/storage/snapshot" + flowgo "github.com/onflow/flow-go/model/flow" +) + +// Store implements the Store interface with an in-memory store. +type Store struct { + mu sync.RWMutex + // block ID to block height + blockIDToHeight map[flowgo.Identifier]uint64 + // blocks by height + blocks map[uint64]flowgo.Block + // collections by ID + collections map[flowgo.Identifier]flowgo.LightCollection + // transactions by ID + transactions map[flowgo.Identifier]flowgo.TransactionBody + // Transaction results by ID + transactionResults map[flowgo.Identifier]StorableTransactionResult + // Ledger states by block height + ledger map[uint64]snapshot.SnapshotTree + // events by block height + eventsByBlockHeight map[uint64][]flowgo.Event + // highest block height + blockHeight uint64 +} + +var _ environment.Blocks = &Store{} +var _ access.Blocks = &Store{} +var _ EmulatorStorage = &Store{} + +func (b *Store) HeaderByID(id flowgo.Identifier) (*flowgo.Header, error) { + block, err := b.BlockByID(context.Background(), id) + if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, nil + } + return nil, err + } + return block.Header, nil +} + +func (b *Store) FinalizedHeader() (*flowgo.Header, error) { + block, err := b.LatestBlock(context.Background()) + if err != nil { + return nil, err + } + + return block.Header, nil +} + +func (b *Store) SealedHeader() (*flowgo.Header, error) { + block, err := b.LatestBlock(context.Background()) + if err != nil { + return nil, err + } + + return block.Header, nil +} + +func (b *Store) IndexedHeight() (uint64, error) { + block, err := b.LatestBlock(context.Background()) + if err != nil { + return 0, err + } + + return block.Header.Height, nil +} + +// ByHeightFrom We don't have to do anything complex here, as emulator does not fork the chain +func (b *Store) ByHeightFrom(height uint64, header *flowgo.Header) (*flowgo.Header, error) { + if height > header.Height { + return nil, ErrNotFound + } + block, err := b.BlockByHeight(context.Background(), height) + if err != nil { + return nil, err + } + + return block.Header, nil +} + +// New returns a new in-memory Store implementation. +func NewMemoryStore() *Store { + return &Store{ + mu: sync.RWMutex{}, + blockIDToHeight: make(map[flowgo.Identifier]uint64), + blocks: make(map[uint64]flowgo.Block), + collections: make(map[flowgo.Identifier]flowgo.LightCollection), + transactions: make(map[flowgo.Identifier]flowgo.TransactionBody), + transactionResults: make(map[flowgo.Identifier]StorableTransactionResult), + ledger: make(map[uint64]snapshot.SnapshotTree), + eventsByBlockHeight: make(map[uint64][]flowgo.Event), + } +} + +func (b *Store) Start() error { + return nil +} + +func (b *Store) Stop() { +} + +func (b *Store) LatestBlockHeight(ctx context.Context) (uint64, error) { + block, err := b.LatestBlock(ctx) + if err != nil { + return 0, err + } + + return block.Header.Height, nil +} + +func (b *Store) LatestBlock(_ context.Context) (flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + latestBlock, ok := b.blocks[b.blockHeight] + if !ok { + return flowgo.Block{}, ErrNotFound + } + return latestBlock, nil +} + +func (b *Store) StoreBlock(_ context.Context, block *flowgo.Block) error { + b.mu.Lock() + defer b.mu.Unlock() + + return b.storeBlock(block) +} + +func (b *Store) storeBlock(block *flowgo.Block) error { + b.blocks[block.Header.Height] = *block + b.blockIDToHeight[block.ID()] = block.Header.Height + + if block.Header.Height > b.blockHeight { + b.blockHeight = block.Header.Height + } + + return nil +} + +func (b *Store) BlockByID(_ context.Context, blockID flowgo.Identifier) (*flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + blockHeight, ok := b.blockIDToHeight[blockID] + if !ok { + return nil, ErrNotFound + } + + block, ok := b.blocks[blockHeight] + if !ok { + return nil, ErrNotFound + } + + return &block, nil + +} + +func (b *Store) BlockByHeight(_ context.Context, height uint64) (*flowgo.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + block, ok := b.blocks[height] + if !ok { + return nil, ErrNotFound + } + + return &block, nil +} + +func (b *Store) CommitBlock( + _ context.Context, + block flowgo.Block, + collections []*flowgo.LightCollection, + transactions map[flowgo.Identifier]*flowgo.TransactionBody, + transactionResults map[flowgo.Identifier]*StorableTransactionResult, + executionSnapshot *snapshot.ExecutionSnapshot, + events []flowgo.Event, +) error { + b.mu.Lock() + defer b.mu.Unlock() + + if len(transactions) != len(transactionResults) { + return fmt.Errorf( + "transactions count (%d) does not match result count (%d)", + len(transactions), + len(transactionResults), + ) + } + + err := b.storeBlock(&block) + if err != nil { + return err + } + + for _, col := range collections { + err := b.InsertCollection(*col) + if err != nil { + return err + } + } + + for _, tx := range transactions { + err := b.InsertTransaction(tx.ID(), *tx) + if err != nil { + return err + } + } + + for txID, result := range transactionResults { + err := b.InsertTransactionResult(txID, *result) + if err != nil { + return err + } + } + + err = b.InsertExecutionSnapshot( + block.Header.Height, + executionSnapshot) + if err != nil { + return err + } + + err = b.InsertEvents(block.Header.Height, events) + if err != nil { + return err + } + + return nil +} + +func (b *Store) CollectionByID( + _ context.Context, + collectionID flowgo.Identifier, +) (flowgo.LightCollection, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + tx, ok := b.collections[collectionID] + if !ok { + return flowgo.LightCollection{}, ErrNotFound + } + return tx, nil +} + +func (b *Store) FullCollectionByID( + _ context.Context, + collectionID flowgo.Identifier, +) (flowgo.Collection, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + light, ok := b.collections[collectionID] + if !ok { + return flowgo.Collection{}, ErrNotFound + } + + txs := make([]*flowgo.TransactionBody, len(light.Transactions)) + for i, txID := range light.Transactions { + tx, ok := b.transactions[txID] + if !ok { + return flowgo.Collection{}, ErrNotFound + } + txs[i] = &tx + } + + return flowgo.Collection{ + Transactions: txs, + }, nil +} + +func (b *Store) TransactionByID( + _ context.Context, + transactionID flowgo.Identifier, +) (flowgo.TransactionBody, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + tx, ok := b.transactions[transactionID] + if !ok { + return flowgo.TransactionBody{}, ErrNotFound + } + return tx, nil + +} + +func (b *Store) TransactionResultByID( + _ context.Context, + transactionID flowgo.Identifier, +) (StorableTransactionResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + result, ok := b.transactionResults[transactionID] + if !ok { + return StorableTransactionResult{}, ErrNotFound + } + return result, nil + +} + +func (b *Store) LedgerByHeight( + _ context.Context, + blockHeight uint64, +) (snapshot.StorageSnapshot, error) { + return b.ledger[blockHeight], nil +} + +func (b *Store) EventsByHeight( + _ context.Context, + blockHeight uint64, + eventType string, +) ([]flowgo.Event, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + allEvents := b.eventsByBlockHeight[blockHeight] + + events := make([]flowgo.Event, 0) + + for _, event := range allEvents { + if eventType == "" { + events = append(events, event) + } else { + if string(event.Type) == eventType { + events = append(events, event) + } + } + } + + return events, nil +} + +func (b *Store) InsertCollection(col flowgo.LightCollection) error { + b.collections[col.ID()] = col + return nil +} + +func (b *Store) InsertTransaction(txID flowgo.Identifier, tx flowgo.TransactionBody) error { + b.transactions[txID] = tx + return nil +} + +func (b *Store) InsertTransactionResult(txID flowgo.Identifier, result StorableTransactionResult) error { + b.transactionResults[txID] = result + return nil +} + +func (b *Store) InsertExecutionSnapshot( + blockHeight uint64, + executionSnapshot *snapshot.ExecutionSnapshot, +) error { + oldLedger := b.ledger[blockHeight-1] + + b.ledger[blockHeight] = oldLedger.Append(executionSnapshot) + + return nil +} + +func (b *Store) InsertEvents(blockHeight uint64, events []flowgo.Event) error { + if b.eventsByBlockHeight[blockHeight] == nil { + b.eventsByBlockHeight[blockHeight] = events + } else { + b.eventsByBlockHeight[blockHeight] = append(b.eventsByBlockHeight[blockHeight], events...) + } + + return nil +} diff --git a/integration/emulator/mocks/emulator.go b/integration/emulator/mocks/emulator.go new file mode 100644 index 00000000000..eb54226291d --- /dev/null +++ b/integration/emulator/mocks/emulator.go @@ -0,0 +1,468 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/onflow/flow-go/integration/emulator (interfaces: Emulator) +// +// Generated by this command: +// +// mockgen -destination=emulator/mocks/emulator.go -package=mocks github.com/onflow/flow-go/integration/emulator Emulator +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + emulator "github.com/onflow/flow-go/integration/emulator" + access "github.com/onflow/flow-go/access" + flow "github.com/onflow/flow-go/model/flow" + gomock "go.uber.org/mock/gomock" +) + +// MockEmulator is a mock of Emulator interface. +type MockEmulator struct { + ctrl *gomock.Controller + recorder *MockEmulatorMockRecorder + isgomock struct{} +} + +// MockEmulatorMockRecorder is the mock recorder for MockEmulator. +type MockEmulatorMockRecorder struct { + mock *MockEmulator +} + +// NewMockEmulator creates a new mock instance. +func NewMockEmulator(ctrl *gomock.Controller) *MockEmulator { + mock := &MockEmulator{ctrl: ctrl} + mock.recorder = &MockEmulatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEmulator) EXPECT() *MockEmulatorMockRecorder { + return m.recorder +} + +// AddTransaction mocks base method. +func (m *MockEmulator) AddTransaction(tx flow.TransactionBody) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddTransaction", tx) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddTransaction indicates an expected call of AddTransaction. +func (mr *MockEmulatorMockRecorder) AddTransaction(tx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTransaction", reflect.TypeOf((*MockEmulator)(nil).AddTransaction), tx) +} + +// CommitBlock mocks base method. +func (m *MockEmulator) CommitBlock() (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitBlock") + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CommitBlock indicates an expected call of CommitBlock. +func (mr *MockEmulatorMockRecorder) CommitBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitBlock", reflect.TypeOf((*MockEmulator)(nil).CommitBlock)) +} + +// DisableAutoMine mocks base method. +func (m *MockEmulator) DisableAutoMine() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DisableAutoMine") +} + +// DisableAutoMine indicates an expected call of DisableAutoMine. +func (mr *MockEmulatorMockRecorder) DisableAutoMine() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableAutoMine", reflect.TypeOf((*MockEmulator)(nil).DisableAutoMine)) +} + +// EnableAutoMine mocks base method. +func (m *MockEmulator) EnableAutoMine() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "EnableAutoMine") +} + +// EnableAutoMine indicates an expected call of EnableAutoMine. +func (mr *MockEmulatorMockRecorder) EnableAutoMine() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableAutoMine", reflect.TypeOf((*MockEmulator)(nil).EnableAutoMine)) +} + +// ExecuteAndCommitBlock mocks base method. +func (m *MockEmulator) ExecuteAndCommitBlock() (*flow.Block, []*emulator.TransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteAndCommitBlock") + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].([]*emulator.TransactionResult) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ExecuteAndCommitBlock indicates an expected call of ExecuteAndCommitBlock. +func (mr *MockEmulatorMockRecorder) ExecuteAndCommitBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteAndCommitBlock", reflect.TypeOf((*MockEmulator)(nil).ExecuteAndCommitBlock)) +} + +// ExecuteBlock mocks base method. +func (m *MockEmulator) ExecuteBlock() ([]*emulator.TransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteBlock") + ret0, _ := ret[0].([]*emulator.TransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteBlock indicates an expected call of ExecuteBlock. +func (mr *MockEmulatorMockRecorder) ExecuteBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteBlock", reflect.TypeOf((*MockEmulator)(nil).ExecuteBlock)) +} + +// ExecuteNextTransaction mocks base method. +func (m *MockEmulator) ExecuteNextTransaction() (*emulator.TransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteNextTransaction") + ret0, _ := ret[0].(*emulator.TransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteNextTransaction indicates an expected call of ExecuteNextTransaction. +func (mr *MockEmulatorMockRecorder) ExecuteNextTransaction() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteNextTransaction", reflect.TypeOf((*MockEmulator)(nil).ExecuteNextTransaction)) +} + +// ExecuteScript mocks base method. +func (m *MockEmulator) ExecuteScript(script []byte, arguments [][]byte) (*emulator.ScriptResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteScript", script, arguments) + ret0, _ := ret[0].(*emulator.ScriptResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteScript indicates an expected call of ExecuteScript. +func (mr *MockEmulatorMockRecorder) ExecuteScript(script, arguments any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteScript", reflect.TypeOf((*MockEmulator)(nil).ExecuteScript), script, arguments) +} + +// ExecuteScriptAtBlockHeight mocks base method. +func (m *MockEmulator) ExecuteScriptAtBlockHeight(script []byte, arguments [][]byte, blockHeight uint64) (*emulator.ScriptResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteScriptAtBlockHeight", script, arguments, blockHeight) + ret0, _ := ret[0].(*emulator.ScriptResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteScriptAtBlockHeight indicates an expected call of ExecuteScriptAtBlockHeight. +func (mr *MockEmulatorMockRecorder) ExecuteScriptAtBlockHeight(script, arguments, blockHeight any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteScriptAtBlockHeight", reflect.TypeOf((*MockEmulator)(nil).ExecuteScriptAtBlockHeight), script, arguments, blockHeight) +} + +// ExecuteScriptAtBlockID mocks base method. +func (m *MockEmulator) ExecuteScriptAtBlockID(script []byte, arguments [][]byte, id flow.Identifier) (*emulator.ScriptResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteScriptAtBlockID", script, arguments, id) + ret0, _ := ret[0].(*emulator.ScriptResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteScriptAtBlockID indicates an expected call of ExecuteScriptAtBlockID. +func (mr *MockEmulatorMockRecorder) ExecuteScriptAtBlockID(script, arguments, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteScriptAtBlockID", reflect.TypeOf((*MockEmulator)(nil).ExecuteScriptAtBlockID), script, arguments, id) +} + +// GetAccount mocks base method. +func (m *MockEmulator) GetAccount(address flow.Address) (*flow.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", address) + ret0, _ := ret[0].(*flow.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockEmulatorMockRecorder) GetAccount(address any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockEmulator)(nil).GetAccount), address) +} + +// GetAccountAtBlockHeight mocks base method. +func (m *MockEmulator) GetAccountAtBlockHeight(address flow.Address, blockHeight uint64) (*flow.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccountAtBlockHeight", address, blockHeight) + ret0, _ := ret[0].(*flow.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAccountAtBlockHeight indicates an expected call of GetAccountAtBlockHeight. +func (mr *MockEmulatorMockRecorder) GetAccountAtBlockHeight(address, blockHeight any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountAtBlockHeight", reflect.TypeOf((*MockEmulator)(nil).GetAccountAtBlockHeight), address, blockHeight) +} + +// GetAccountByIndex mocks base method. +func (m *MockEmulator) GetAccountByIndex(arg0 uint) (*flow.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccountByIndex", arg0) + ret0, _ := ret[0].(*flow.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAccountByIndex indicates an expected call of GetAccountByIndex. +func (mr *MockEmulatorMockRecorder) GetAccountByIndex(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountByIndex", reflect.TypeOf((*MockEmulator)(nil).GetAccountByIndex), arg0) +} + +// GetBlockByHeight mocks base method. +func (m *MockEmulator) GetBlockByHeight(height uint64) (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockByHeight", height) + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockByHeight indicates an expected call of GetBlockByHeight. +func (mr *MockEmulatorMockRecorder) GetBlockByHeight(height any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByHeight", reflect.TypeOf((*MockEmulator)(nil).GetBlockByHeight), height) +} + +// GetBlockByID mocks base method. +func (m *MockEmulator) GetBlockByID(id flow.Identifier) (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockByID", id) + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockByID indicates an expected call of GetBlockByID. +func (mr *MockEmulatorMockRecorder) GetBlockByID(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByID", reflect.TypeOf((*MockEmulator)(nil).GetBlockByID), id) +} + +// GetCollectionByID mocks base method. +func (m *MockEmulator) GetCollectionByID(colID flow.Identifier) (*flow.LightCollection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCollectionByID", colID) + ret0, _ := ret[0].(*flow.LightCollection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCollectionByID indicates an expected call of GetCollectionByID. +func (mr *MockEmulatorMockRecorder) GetCollectionByID(colID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCollectionByID", reflect.TypeOf((*MockEmulator)(nil).GetCollectionByID), colID) +} + +// GetEventsByHeight mocks base method. +func (m *MockEmulator) GetEventsByHeight(blockHeight uint64, eventType string) ([]flow.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEventsByHeight", blockHeight, eventType) + ret0, _ := ret[0].([]flow.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEventsByHeight indicates an expected call of GetEventsByHeight. +func (mr *MockEmulatorMockRecorder) GetEventsByHeight(blockHeight, eventType any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventsByHeight", reflect.TypeOf((*MockEmulator)(nil).GetEventsByHeight), blockHeight, eventType) +} + +// GetEventsForBlockIDs mocks base method. +func (m *MockEmulator) GetEventsForBlockIDs(eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEventsForBlockIDs", eventType, blockIDs) + ret0, _ := ret[0].([]flow.BlockEvents) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEventsForBlockIDs indicates an expected call of GetEventsForBlockIDs. +func (mr *MockEmulatorMockRecorder) GetEventsForBlockIDs(eventType, blockIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventsForBlockIDs", reflect.TypeOf((*MockEmulator)(nil).GetEventsForBlockIDs), eventType, blockIDs) +} + +// GetEventsForHeightRange mocks base method. +func (m *MockEmulator) GetEventsForHeightRange(eventType string, startHeight, endHeight uint64) ([]flow.BlockEvents, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEventsForHeightRange", eventType, startHeight, endHeight) + ret0, _ := ret[0].([]flow.BlockEvents) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEventsForHeightRange indicates an expected call of GetEventsForHeightRange. +func (mr *MockEmulatorMockRecorder) GetEventsForHeightRange(eventType, startHeight, endHeight any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventsForHeightRange", reflect.TypeOf((*MockEmulator)(nil).GetEventsForHeightRange), eventType, startHeight, endHeight) +} + +// GetFullCollectionByID mocks base method. +func (m *MockEmulator) GetFullCollectionByID(colID flow.Identifier) (*flow.Collection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFullCollectionByID", colID) + ret0, _ := ret[0].(*flow.Collection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFullCollectionByID indicates an expected call of GetFullCollectionByID. +func (mr *MockEmulatorMockRecorder) GetFullCollectionByID(colID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFullCollectionByID", reflect.TypeOf((*MockEmulator)(nil).GetFullCollectionByID), colID) +} + +// GetLatestBlock mocks base method. +func (m *MockEmulator) GetLatestBlock() (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestBlock") + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestBlock indicates an expected call of GetLatestBlock. +func (mr *MockEmulatorMockRecorder) GetLatestBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestBlock", reflect.TypeOf((*MockEmulator)(nil).GetLatestBlock)) +} + +// GetNetworkParameters mocks base method. +func (m *MockEmulator) GetNetworkParameters() access.NetworkParameters { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNetworkParameters") + ret0, _ := ret[0].(access.NetworkParameters) + return ret0 +} + +// GetNetworkParameters indicates an expected call of GetNetworkParameters. +func (mr *MockEmulatorMockRecorder) GetNetworkParameters() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNetworkParameters", reflect.TypeOf((*MockEmulator)(nil).GetNetworkParameters)) +} + +// GetTransaction mocks base method. +func (m *MockEmulator) GetTransaction(txID flow.Identifier) (*flow.TransactionBody, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTransaction", txID) + ret0, _ := ret[0].(*flow.TransactionBody) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTransaction indicates an expected call of GetTransaction. +func (mr *MockEmulatorMockRecorder) GetTransaction(txID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransaction", reflect.TypeOf((*MockEmulator)(nil).GetTransaction), txID) +} + +// GetTransactionResult mocks base method. +func (m *MockEmulator) GetTransactionResult(txID flow.Identifier) (*access.TransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTransactionResult", txID) + ret0, _ := ret[0].(*access.TransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTransactionResult indicates an expected call of GetTransactionResult. +func (mr *MockEmulatorMockRecorder) GetTransactionResult(txID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionResult", reflect.TypeOf((*MockEmulator)(nil).GetTransactionResult), txID) +} + +// GetTransactionResultsByBlockID mocks base method. +func (m *MockEmulator) GetTransactionResultsByBlockID(blockID flow.Identifier) ([]*access.TransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTransactionResultsByBlockID", blockID) + ret0, _ := ret[0].([]*access.TransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTransactionResultsByBlockID indicates an expected call of GetTransactionResultsByBlockID. +func (mr *MockEmulatorMockRecorder) GetTransactionResultsByBlockID(blockID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionResultsByBlockID", reflect.TypeOf((*MockEmulator)(nil).GetTransactionResultsByBlockID), blockID) +} + +// GetTransactionsByBlockID mocks base method. +func (m *MockEmulator) GetTransactionsByBlockID(blockID flow.Identifier) ([]*flow.TransactionBody, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTransactionsByBlockID", blockID) + ret0, _ := ret[0].([]*flow.TransactionBody) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTransactionsByBlockID indicates an expected call of GetTransactionsByBlockID. +func (mr *MockEmulatorMockRecorder) GetTransactionsByBlockID(blockID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionsByBlockID", reflect.TypeOf((*MockEmulator)(nil).GetTransactionsByBlockID), blockID) +} + +// Ping mocks base method. +func (m *MockEmulator) Ping() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Ping") + ret0, _ := ret[0].(error) + return ret0 +} + +// Ping indicates an expected call of Ping. +func (mr *MockEmulatorMockRecorder) Ping() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockEmulator)(nil).Ping)) +} + +// SendTransaction mocks base method. +func (m *MockEmulator) SendTransaction(tx *flow.TransactionBody) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTransaction", tx) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendTransaction indicates an expected call of SendTransaction. +func (mr *MockEmulatorMockRecorder) SendTransaction(tx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransaction", reflect.TypeOf((*MockEmulator)(nil).SendTransaction), tx) +} + +// ServiceKey mocks base method. +func (m *MockEmulator) ServiceKey() emulator.ServiceKey { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceKey") + ret0, _ := ret[0].(emulator.ServiceKey) + return ret0 +} + +// ServiceKey indicates an expected call of ServiceKey. +func (mr *MockEmulatorMockRecorder) ServiceKey() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceKey", reflect.TypeOf((*MockEmulator)(nil).ServiceKey)) +} diff --git a/integration/emulator/mocks/emulatorStorage.go b/integration/emulator/mocks/emulatorStorage.go new file mode 100644 index 00000000000..18fc37812e1 --- /dev/null +++ b/integration/emulator/mocks/emulatorStorage.go @@ -0,0 +1,323 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/onflow/flow-emulator/emulator (interfaces: EmulatorStorage) +// +// Generated by this command: +// +// mockgen -destination=emulator/mocks/emulatorStorage.go -package=mocks github.com/onflow/flow-emulator/emulator EmulatorStorage +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + emulator "github.com/onflow/flow-go/integration/emulator" + snapshot "github.com/onflow/flow-go/fvm/storage/snapshot" + flow "github.com/onflow/flow-go/model/flow" + gomock "go.uber.org/mock/gomock" +) + +// MockEmulatorStorage is a mock of EmulatorStorage interface. +type MockEmulatorStorage struct { + ctrl *gomock.Controller + recorder *MockEmulatorStorageMockRecorder + isgomock struct{} +} + +// MockEmulatorStorageMockRecorder is the mock recorder for MockEmulatorStorage. +type MockEmulatorStorageMockRecorder struct { + mock *MockEmulatorStorage +} + +// NewMockEmulatorStorage creates a new mock instance. +func NewMockEmulatorStorage(ctrl *gomock.Controller) *MockEmulatorStorage { + mock := &MockEmulatorStorage{ctrl: ctrl} + mock.recorder = &MockEmulatorStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEmulatorStorage) EXPECT() *MockEmulatorStorageMockRecorder { + return m.recorder +} + +// BlockByHeight mocks base method. +func (m *MockEmulatorStorage) BlockByHeight(ctx context.Context, height uint64) (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockByHeight", ctx, height) + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BlockByHeight indicates an expected call of BlockByHeight. +func (mr *MockEmulatorStorageMockRecorder) BlockByHeight(ctx, height any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHeight", reflect.TypeOf((*MockEmulatorStorage)(nil).BlockByHeight), ctx, height) +} + +// BlockByID mocks base method. +func (m *MockEmulatorStorage) BlockByID(ctx context.Context, blockID flow.Identifier) (*flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockByID", ctx, blockID) + ret0, _ := ret[0].(*flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BlockByID indicates an expected call of BlockByID. +func (mr *MockEmulatorStorageMockRecorder) BlockByID(ctx, blockID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByID", reflect.TypeOf((*MockEmulatorStorage)(nil).BlockByID), ctx, blockID) +} + +// ByHeightFrom mocks base method. +func (m *MockEmulatorStorage) ByHeightFrom(height uint64, header *flow.Header) (*flow.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ByHeightFrom", height, header) + ret0, _ := ret[0].(*flow.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ByHeightFrom indicates an expected call of ByHeightFrom. +func (mr *MockEmulatorStorageMockRecorder) ByHeightFrom(height, header any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByHeightFrom", reflect.TypeOf((*MockEmulatorStorage)(nil).ByHeightFrom), height, header) +} + +// CollectionByID mocks base method. +func (m *MockEmulatorStorage) CollectionByID(ctx context.Context, collectionID flow.Identifier) (flow.LightCollection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CollectionByID", ctx, collectionID) + ret0, _ := ret[0].(flow.LightCollection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CollectionByID indicates an expected call of CollectionByID. +func (mr *MockEmulatorStorageMockRecorder) CollectionByID(ctx, collectionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CollectionByID", reflect.TypeOf((*MockEmulatorStorage)(nil).CollectionByID), ctx, collectionID) +} + +// CommitBlock mocks base method. +func (m *MockEmulatorStorage) CommitBlock(ctx context.Context, block flow.Block, collections []*flow.LightCollection, transactions map[flow.Identifier]*flow.TransactionBody, transactionResults map[flow.Identifier]*emulator.StorableTransactionResult, executionSnapshot *snapshot.ExecutionSnapshot, events []flow.Event) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitBlock", ctx, block, collections, transactions, transactionResults, executionSnapshot, events) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitBlock indicates an expected call of CommitBlock. +func (mr *MockEmulatorStorageMockRecorder) CommitBlock(ctx, block, collections, transactions, transactionResults, executionSnapshot, events any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitBlock", reflect.TypeOf((*MockEmulatorStorage)(nil).CommitBlock), ctx, block, collections, transactions, transactionResults, executionSnapshot, events) +} + +// EventsByHeight mocks base method. +func (m *MockEmulatorStorage) EventsByHeight(ctx context.Context, blockHeight uint64, eventType string) ([]flow.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EventsByHeight", ctx, blockHeight, eventType) + ret0, _ := ret[0].([]flow.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EventsByHeight indicates an expected call of EventsByHeight. +func (mr *MockEmulatorStorageMockRecorder) EventsByHeight(ctx, blockHeight, eventType any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventsByHeight", reflect.TypeOf((*MockEmulatorStorage)(nil).EventsByHeight), ctx, blockHeight, eventType) +} + +// FinalizedHeader mocks base method. +func (m *MockEmulatorStorage) FinalizedHeader() (*flow.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FinalizedHeader") + ret0, _ := ret[0].(*flow.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FinalizedHeader indicates an expected call of FinalizedHeader. +func (mr *MockEmulatorStorageMockRecorder) FinalizedHeader() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizedHeader", reflect.TypeOf((*MockEmulatorStorage)(nil).FinalizedHeader)) +} + +// FullCollectionByID mocks base method. +func (m *MockEmulatorStorage) FullCollectionByID(ctx context.Context, collectionID flow.Identifier) (flow.Collection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FullCollectionByID", ctx, collectionID) + ret0, _ := ret[0].(flow.Collection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FullCollectionByID indicates an expected call of FullCollectionByID. +func (mr *MockEmulatorStorageMockRecorder) FullCollectionByID(ctx, collectionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullCollectionByID", reflect.TypeOf((*MockEmulatorStorage)(nil).FullCollectionByID), ctx, collectionID) +} + +// HeaderByID mocks base method. +func (m *MockEmulatorStorage) HeaderByID(id flow.Identifier) (*flow.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeaderByID", id) + ret0, _ := ret[0].(*flow.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HeaderByID indicates an expected call of HeaderByID. +func (mr *MockEmulatorStorageMockRecorder) HeaderByID(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeaderByID", reflect.TypeOf((*MockEmulatorStorage)(nil).HeaderByID), id) +} + +// IndexedHeight mocks base method. +func (m *MockEmulatorStorage) IndexedHeight() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IndexedHeight") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IndexedHeight indicates an expected call of IndexedHeight. +func (mr *MockEmulatorStorageMockRecorder) IndexedHeight() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexedHeight", reflect.TypeOf((*MockEmulatorStorage)(nil).IndexedHeight)) +} + +// LatestBlock mocks base method. +func (m *MockEmulatorStorage) LatestBlock(ctx context.Context) (flow.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LatestBlock", ctx) + ret0, _ := ret[0].(flow.Block) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LatestBlock indicates an expected call of LatestBlock. +func (mr *MockEmulatorStorageMockRecorder) LatestBlock(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestBlock", reflect.TypeOf((*MockEmulatorStorage)(nil).LatestBlock), ctx) +} + +// LatestBlockHeight mocks base method. +func (m *MockEmulatorStorage) LatestBlockHeight(ctx context.Context) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LatestBlockHeight", ctx) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LatestBlockHeight indicates an expected call of LatestBlockHeight. +func (mr *MockEmulatorStorageMockRecorder) LatestBlockHeight(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestBlockHeight", reflect.TypeOf((*MockEmulatorStorage)(nil).LatestBlockHeight), ctx) +} + +// LedgerByHeight mocks base method. +func (m *MockEmulatorStorage) LedgerByHeight(ctx context.Context, blockHeight uint64) (snapshot.StorageSnapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LedgerByHeight", ctx, blockHeight) + ret0, _ := ret[0].(snapshot.StorageSnapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LedgerByHeight indicates an expected call of LedgerByHeight. +func (mr *MockEmulatorStorageMockRecorder) LedgerByHeight(ctx, blockHeight any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LedgerByHeight", reflect.TypeOf((*MockEmulatorStorage)(nil).LedgerByHeight), ctx, blockHeight) +} + +// SealedHeader mocks base method. +func (m *MockEmulatorStorage) SealedHeader() (*flow.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SealedHeader") + ret0, _ := ret[0].(*flow.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SealedHeader indicates an expected call of SealedHeader. +func (mr *MockEmulatorStorageMockRecorder) SealedHeader() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SealedHeader", reflect.TypeOf((*MockEmulatorStorage)(nil).SealedHeader)) +} + +// Start mocks base method. +func (m *MockEmulatorStorage) Start() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start") + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start. +func (mr *MockEmulatorStorageMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockEmulatorStorage)(nil).Start)) +} + +// Stop mocks base method. +func (m *MockEmulatorStorage) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockEmulatorStorageMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockEmulatorStorage)(nil).Stop)) +} + +// StoreBlock mocks base method. +func (m *MockEmulatorStorage) StoreBlock(ctx context.Context, block *flow.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreBlock", ctx, block) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreBlock indicates an expected call of StoreBlock. +func (mr *MockEmulatorStorageMockRecorder) StoreBlock(ctx, block any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreBlock", reflect.TypeOf((*MockEmulatorStorage)(nil).StoreBlock), ctx, block) +} + +// TransactionByID mocks base method. +func (m *MockEmulatorStorage) TransactionByID(ctx context.Context, transactionID flow.Identifier) (flow.TransactionBody, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TransactionByID", ctx, transactionID) + ret0, _ := ret[0].(flow.TransactionBody) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TransactionByID indicates an expected call of TransactionByID. +func (mr *MockEmulatorStorageMockRecorder) TransactionByID(ctx, transactionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionByID", reflect.TypeOf((*MockEmulatorStorage)(nil).TransactionByID), ctx, transactionID) +} + +// TransactionResultByID mocks base method. +func (m *MockEmulatorStorage) TransactionResultByID(ctx context.Context, transactionID flow.Identifier) (emulator.StorableTransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TransactionResultByID", ctx, transactionID) + ret0, _ := ret[0].(emulator.StorableTransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TransactionResultByID indicates an expected call of TransactionResultByID. +func (mr *MockEmulatorStorageMockRecorder) TransactionResultByID(ctx, transactionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionResultByID", reflect.TypeOf((*MockEmulatorStorage)(nil).TransactionResultByID), ctx, transactionID) +} diff --git a/integration/emulator/pendingBlock.go b/integration/emulator/pendingBlock.go new file mode 100644 index 00000000000..4d92e8a40a7 --- /dev/null +++ b/integration/emulator/pendingBlock.go @@ -0,0 +1,236 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package emulator + +import ( + "math/rand" + "time" + + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/storage/state" + flowgo "github.com/onflow/flow-go/model/flow" +) + +type IndexedTransactionResult struct { + fvm.ProcedureOutput + Index uint32 +} + +// MaxViewIncrease represents the largest difference in view number between +// two consecutive blocks. The minimum view increment is 1. +const MaxViewIncrease = 3 + +// A pendingBlock contains the pending state required to form a new block. +type pendingBlock struct { + height uint64 + view uint64 + parentID flowgo.Identifier + timestamp time.Time + // mapping from transaction ID to transaction + transactions map[flowgo.Identifier]*flowgo.TransactionBody + // list of transaction IDs in the block + transactionIDs []flowgo.Identifier + // mapping from transaction ID to transaction result + transactionResults map[flowgo.Identifier]IndexedTransactionResult + // current working ledger, updated after each transaction execution + ledgerState *state.ExecutionState + // events emitted during execution + events []flowgo.Event + // index of transaction execution + index uint32 +} + +// newPendingBlock creates a new pending block sequentially after a specified block. +func newPendingBlock( + prevBlock *flowgo.Block, + ledgerSnapshot snapshot.StorageSnapshot, + timestamp time.Time, +) *pendingBlock { + return &pendingBlock{ + height: prevBlock.Header.Height + 1, + // the view increments by between 1 and MaxViewIncrease to match + // behaviour on a real network, where views are not consecutive + view: prevBlock.Header.View + uint64(rand.Intn(MaxViewIncrease)+1), + parentID: prevBlock.ID(), + timestamp: timestamp, + transactions: make(map[flowgo.Identifier]*flowgo.TransactionBody), + transactionIDs: make([]flowgo.Identifier, 0), + transactionResults: make(map[flowgo.Identifier]IndexedTransactionResult), + ledgerState: state.NewExecutionState( + ledgerSnapshot, + state.DefaultParameters()), + events: make([]flowgo.Event, 0), + index: 0, + } +} + +// ID returns the ID of the pending block. +func (b *pendingBlock) ID() flowgo.Identifier { + return b.Block().ID() +} + +// Block returns the block information for the pending block. +func (b *pendingBlock) Block() *flowgo.Block { + collections := b.Collections() + + guarantees := make([]*flowgo.CollectionGuarantee, len(collections)) + for i, collection := range collections { + guarantees[i] = &flowgo.CollectionGuarantee{ + CollectionID: collection.ID(), + } + } + + return &flowgo.Block{ + Header: &flowgo.Header{ + Height: b.height, + View: b.view, + ParentID: b.parentID, + Timestamp: b.timestamp, + }, + Payload: &flowgo.Payload{ + Guarantees: guarantees, + }, + } +} + +func (b *pendingBlock) Collections() []*flowgo.LightCollection { + if len(b.transactionIDs) == 0 { + return []*flowgo.LightCollection{} + } + + transactionIDs := make([]flowgo.Identifier, len(b.transactionIDs)) + + // TODO: remove once SDK models are removed + copy(transactionIDs, b.transactionIDs) + + collection := flowgo.LightCollection{Transactions: transactionIDs} + + return []*flowgo.LightCollection{&collection} +} + +func (b *pendingBlock) Transactions() map[flowgo.Identifier]*flowgo.TransactionBody { + return b.transactions +} + +func (b *pendingBlock) TransactionResults() map[flowgo.Identifier]IndexedTransactionResult { + return b.transactionResults +} + +// Finalize returns the execution snapshot for the pending block. +func (b *pendingBlock) Finalize() *snapshot.ExecutionSnapshot { + return b.ledgerState.Finalize() +} + +// AddTransaction adds a transaction to the pending block. +func (b *pendingBlock) AddTransaction(tx flowgo.TransactionBody) { + b.transactionIDs = append(b.transactionIDs, tx.ID()) + b.transactions[tx.ID()] = &tx +} + +// ContainsTransaction checks if a transaction is included in the pending block. +func (b *pendingBlock) ContainsTransaction(txID flowgo.Identifier) bool { + _, exists := b.transactions[txID] + return exists +} + +// GetTransaction retrieves a transaction in the pending block by ID. +func (b *pendingBlock) GetTransaction(txID flowgo.Identifier) *flowgo.TransactionBody { + return b.transactions[txID] +} + +// NextTransaction returns the next indexed transaction. +func (b *pendingBlock) NextTransaction() *flowgo.TransactionBody { + if int(b.index) > len(b.transactionIDs) { + return nil + } + + txID := b.transactionIDs[b.index] + return b.GetTransaction(txID) +} + +// ExecuteNextTransaction executes the next transaction in the pending block. +// +// This function uses the provided execute function to perform the actual +// execution, then updates the pending block with the output. +func (b *pendingBlock) ExecuteNextTransaction( + vm *fvm.VirtualMachine, + ctx fvm.Context, +) ( + fvm.ProcedureOutput, + error, +) { + txnBody := b.NextTransaction() + txnIndex := b.index + + // increment transaction index even if transaction reverts + b.index++ + + executionSnapshot, output, err := vm.Run( + ctx, + fvm.Transaction(txnBody, txnIndex), + b.ledgerState) + if err != nil { + // fail fast if fatal error occurs + return fvm.ProcedureOutput{}, err + } + + b.events = append(b.events, output.Events...) + + err = b.ledgerState.Merge(executionSnapshot) + if err != nil { + // fail fast if fatal error occurs + return fvm.ProcedureOutput{}, err + } + + b.transactionResults[txnBody.ID()] = IndexedTransactionResult{ + ProcedureOutput: output, + Index: txnIndex, + } + + return output, nil +} + +// Events returns all events captured during the execution of the pending block. +func (b *pendingBlock) Events() []flowgo.Event { + return b.events +} + +// ExecutionStarted returns true if the pending block has started executing. +func (b *pendingBlock) ExecutionStarted() bool { + return b.index > 0 +} + +// ExecutionComplete returns true if the pending block is fully executed. +func (b *pendingBlock) ExecutionComplete() bool { + return b.index >= uint32(b.Size()) +} + +// Size returns the number of transactions in the pending block. +func (b *pendingBlock) Size() int { + return len(b.transactionIDs) +} + +// Empty returns true if the pending block is empty. +func (b *pendingBlock) Empty() bool { + return b.Size() == 0 +} + +func (b *pendingBlock) SetTimestamp(timestamp time.Time) { + b.timestamp = timestamp +} diff --git a/integration/emulator/result.go b/integration/emulator/result.go new file mode 100644 index 00000000000..1ec6a920cbb --- /dev/null +++ b/integration/emulator/result.go @@ -0,0 +1,101 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package emulator + +import ( + "fmt" + "github.com/onflow/cadence" + flowsdk "github.com/onflow/flow-go-sdk" + flowgo "github.com/onflow/flow-go/model/flow" +) + +type StorableTransactionResult struct { + ErrorCode int + ErrorMessage string + Logs []string + Events []flowgo.Event + BlockID flowgo.Identifier + BlockHeight uint64 +} + +// A TransactionResult is the result of executing a transaction. +type TransactionResult struct { + TransactionID flowsdk.Identifier + ComputationUsed uint64 + MemoryEstimate uint64 + Error error + Logs []string + Events []flowsdk.Event + Debug *TransactionResultDebug +} + +// Succeeded returns true if the transaction executed without errors. +func (r TransactionResult) Succeeded() bool { + return r.Error == nil +} + +// Reverted returns true if the transaction executed with errors. +func (r TransactionResult) Reverted() bool { + return !r.Succeeded() +} + +// TransactionResultDebug provides details about unsuccessful transaction execution +type TransactionResultDebug struct { + Message string + Meta map[string]any +} + +// NewTransactionInvalidSignature creates more debug details for transactions with invalid signature +func NewTransactionInvalidSignature( + tx *flowgo.TransactionBody, +) *TransactionResultDebug { + return &TransactionResultDebug{ + Message: "", + Meta: map[string]any{ + "payer": tx.Payer.String(), + "proposer": tx.ProposalKey.Address.String(), + "proposerKeyIndex": fmt.Sprintf("%d", tx.ProposalKey.KeyIndex), + "authorizers": fmt.Sprintf("%v", tx.Authorizers), + "gasLimit": fmt.Sprintf("%d", tx.GasLimit), + }, + } +} + +// TODO - this class should be part of SDK for consistency + +// A ScriptResult is the result of executing a script. +type ScriptResult struct { + ScriptID flowgo.Identifier + Value cadence.Value + Error error + Logs []string + Events []flowgo.Event + ComputationUsed uint64 + MemoryEstimate uint64 +} + +// Succeeded returns true if the script executed without errors. +func (r ScriptResult) Succeeded() bool { + return r.Error == nil +} + +// Reverted returns true if the script executed with errors. +func (r ScriptResult) Reverted() bool { + return !r.Succeeded() +} diff --git a/integration/emulator/store.go b/integration/emulator/store.go new file mode 100644 index 00000000000..7a157ae296a --- /dev/null +++ b/integration/emulator/store.go @@ -0,0 +1,96 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package storage defines the interface and implementations for interacting with +// persistent chain state. +package emulator + +import ( + "context" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/storage/snapshot" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/psiemens/graceland" +) + +// EmulatorStorage defines the storage layer for persistent chain state. +// +// This includes finalized blocks and transactions, and the resultant register +// states and emitted events. It does not include pending state, such as pending +// transactions and register states. +// +// Implementations must distinguish between not found errors and errors with +// the underlying storage by returning an instance of store.ErrNotFound if a +// resource cannot be found. +// +// Implementations must be safe for use by multiple goroutines. + +type EmulatorStorage interface { + graceland.Routine + environment.Blocks + access.Blocks + LatestBlockHeight(ctx context.Context) (uint64, error) + + // LatestBlock returns the block with the highest block height. + LatestBlock(ctx context.Context) (flowgo.Block, error) + + // StoreBlock stores the block in storage. If the exactly same block is already in a storage, return successfully + StoreBlock(ctx context.Context, block *flowgo.Block) error + + // BlockByID returns the block with the given hash. It is available for + // finalized and ambiguous blocks. + BlockByID(ctx context.Context, blockID flowgo.Identifier) (*flowgo.Block, error) + + // BlockByHeight returns the block at the given height. It is only available + // for finalized blocks. + BlockByHeight(ctx context.Context, height uint64) (*flowgo.Block, error) + + // CommitBlock atomically saves the execution results for a block. + CommitBlock( + ctx context.Context, + block flowgo.Block, + collections []*flowgo.LightCollection, + transactions map[flowgo.Identifier]*flowgo.TransactionBody, + transactionResults map[flowgo.Identifier]*StorableTransactionResult, + executionSnapshot *snapshot.ExecutionSnapshot, + events []flowgo.Event, + ) error + + // CollectionByID gets the collection (transaction IDs only) with the given ID. + CollectionByID(ctx context.Context, collectionID flowgo.Identifier) (flowgo.LightCollection, error) + + // FullCollectionByID gets the full collection (including transaction bodies) with the given ID. + FullCollectionByID(ctx context.Context, collectionID flowgo.Identifier) (flowgo.Collection, error) + + // TransactionByID gets the transaction with the given ID. + TransactionByID(ctx context.Context, transactionID flowgo.Identifier) (flowgo.TransactionBody, error) + + // TransactionResultByID gets the transaction result with the given ID. + TransactionResultByID(ctx context.Context, transactionID flowgo.Identifier) (StorableTransactionResult, error) + + // LedgerByHeight returns a storage snapshot into the ledger state + // at a given block. + LedgerByHeight( + ctx context.Context, + blockHeight uint64, + ) (snapshot.StorageSnapshot, error) + + // EventsByHeight returns the events in the block at the given height, optionally filtered by type. + EventsByHeight(ctx context.Context, blockHeight uint64, eventType string) ([]flowgo.Event, error) +} diff --git a/integration/emulator/templates/systemChunkTransactionTemplate.cdc b/integration/emulator/templates/systemChunkTransactionTemplate.cdc new file mode 100644 index 00000000000..ef714ed20ba --- /dev/null +++ b/integration/emulator/templates/systemChunkTransactionTemplate.cdc @@ -0,0 +1,16 @@ +import RandomBeaconHistory from "RandomBeaconHistory" +import EVM from "EVM" + +transaction { + prepare(serviceAccount: auth(BorrowValue) &Account) { + let randomBeaconHistoryHeartbeat = serviceAccount.storage + .borrow<&RandomBeaconHistory.Heartbeat>(from: RandomBeaconHistory.HeartbeatStoragePath) + ?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource") + randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory()) + + let evmHeartbeat = serviceAccount.storage + .borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat) + ?? panic("Couldn't borrow EVM.Heartbeat Resource") + evmHeartbeat.heartbeat() + } +} diff --git a/integration/emulator/tests/accounts_test.go b/integration/emulator/tests/accounts_test.go new file mode 100644 index 00000000000..d2ee7482419 --- /dev/null +++ b/integration/emulator/tests/accounts_test.go @@ -0,0 +1,1218 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/crypto" + "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go-sdk/test" + fvmerrors "github.com/onflow/flow-go/fvm/errors" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/integration/emulator" +) + +const testContract = "access(all) contract Test {}" + +func setupAccountTests(t *testing.T, opts ...emulator.Option) ( + *emulator.Blockchain, + *adapters.SDKAdapter, +) { + b, err := emulator.New( + opts..., + ) + require.NoError(t, err) + + logger := zerolog.Nop() + return b, adapters.NewSDKAdapter(&logger, b) +} + +func TestGetAccount(t *testing.T) { + + t.Parallel() + + t.Run("Get account at latest block height", func(t *testing.T) { + + t.Parallel() + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + acc, err := adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + assert.Equal(t, uint64(0), acc.Keys[0].SequenceNumber) + + }) + + t.Run("Get account at latest block by index", func(t *testing.T) { + + t.Parallel() + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + acc, err := adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + assert.Equal(t, uint64(0), acc.Keys[0].SequenceNumber) + + flowAccount, err := b.GetAccountByIndex(1) //service account + assert.NoError(t, err) + + assert.Equal(t, uint64(0), flowAccount.Keys[0].SeqNumber) + assert.Equal(t, acc.Address.String(), flowAccount.Address.String()) + + }) + + t.Run("Get account at latest block height", func(t *testing.T) { + + t.Parallel() + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + acc, err := adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + assert.Equal(t, uint64(0), acc.Keys[0].SequenceNumber) + + flowAccount, err := b.GetAccountByIndex(1) //service account + assert.NoError(t, err) + + assert.Equal(t, uint64(0), flowAccount.Keys[0].SeqNumber) + assert.Equal(t, acc.Address.String(), flowAccount.Address.String()) + + }) + + t.Run("Get account at specified block height", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + acc, err := adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + assert.Equal(t, uint64(0), acc.Keys[0].SequenceNumber) + contract := templates.Contract{ + Name: "Test", + Source: testContract, + } + + tx := templates.AddAccountContract(serviceAccountAddress, contract) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + bl, err := b.CommitBlock() + assert.NoError(t, err) + + accNow, err := adapter.GetAccountAtBlockHeight(context.Background(), serviceAccountAddress, bl.Header.Height) + assert.NoError(t, err) + + accPrev, err := adapter.GetAccountAtBlockHeight(context.Background(), serviceAccountAddress, bl.Header.Height-uint64(1)) + assert.NoError(t, err) + + assert.Equal(t, accNow.Keys[0].SequenceNumber, uint64(1)) + assert.Equal(t, accPrev.Keys[0].SequenceNumber, uint64(0)) + }) +} + +func TestCreateAccount(t *testing.T) { + + t.Parallel() + + accountKeys := test.AccountKeyGenerator() + + t.Run("Simple addresses", func(t *testing.T) { + b, adapter := setupAccountTests( + t, + emulator.WithSimpleAddresses(), + ) + + accountKey := accountKeys.New() + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + nil, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + assert.Equal(t, "0000000000000006", account.Address.Hex()) + assert.Equal(t, uint64(0x186a0), account.Balance) + require.Len(t, account.Keys, 1) + assert.Equal(t, accountKey.PublicKey.Encode(), account.Keys[0].PublicKey.Encode()) + assert.Empty(t, account.Contracts) + }) + + t.Run("Single public keys", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + accountKey := accountKeys.New() + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + nil, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + require.Len(t, account.Keys, 1) + assert.Equal(t, accountKey.PublicKey.Encode(), account.Keys[0].PublicKey.Encode()) + assert.Empty(t, account.Contracts) + }) + + t.Run("Multiple public keys", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + accountKeyA := accountKeys.New() + accountKeyB := accountKeys.New() + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKeyA, accountKeyB}, + nil, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + require.Len(t, account.Keys, 2) + assert.Equal(t, accountKeyA.PublicKey.Encode(), account.Keys[0].PublicKey.Encode()) + assert.Equal(t, accountKeyB.PublicKey.Encode(), account.Keys[1].PublicKey.Encode()) + assert.Empty(t, account.Contracts) + }) + + t.Run("Public keys and contract", func(t *testing.T) { + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + accountKeyA := accountKeys.New() + accountKeyB := accountKeys.New() + + contracts := []templates.Contract{ + { + Name: "Test", + Source: testContract, + }, + } + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKeyA, accountKeyB}, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + require.Len(t, account.Keys, 2) + assert.Equal(t, accountKeyA.PublicKey.Encode(), account.Keys[0].PublicKey.Encode()) + assert.Equal(t, accountKeyB.PublicKey.Encode(), account.Keys[1].PublicKey.Encode()) + assert.Equal(t, + map[string][]byte{ + "Test": []byte(testContract), + }, + account.Contracts, + ) + }) + + t.Run("Public keys and two contracts", func(t *testing.T) { + b, adapter := setupAccountTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + codeA := ` + access(all) contract Test1 { + access(all) fun a(): Int { + return 1 + } + } + ` + codeB := ` + access(all) contract Test2 { + access(all) fun b(): Int { + return 2 + } + } + ` + + accountKey := accountKeys.New() + + contracts := []templates.Contract{ + { + Name: "Test1", + Source: codeA, + }, + { + Name: "Test2", + Source: codeB, + }, + } + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + require.Len(t, account.Keys, 1) + assert.Equal(t, accountKey.PublicKey.Encode(), account.Keys[0].PublicKey.Encode()) + assert.Equal(t, + map[string][]byte{ + "Test1": []byte(codeA), + "Test2": []byte(codeB), + }, + account.Contracts, + ) + }) + + t.Run("Code and no keys", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + contracts := []templates.Contract{ + { + Name: "Test", + Source: testContract, + }, + } + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + nil, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := LastCreatedAccount(b, result) + require.NoError(t, err) + + assert.Empty(t, account.Keys) + assert.Equal(t, + map[string][]byte{ + "Test": []byte(testContract), + }, + account.Contracts, + ) + }) + + t.Run("Event emitted", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + accountKey := accountKeys.New() + + contracts := []templates.Contract{ + { + Name: "Test", + Source: testContract, + }, + } + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + block, err := b.CommitBlock() + require.NoError(t, err) + + events, err := adapter.GetEventsForHeightRange(context.Background(), flowsdk.EventAccountCreated, block.Header.Height, block.Header.Height) + require.NoError(t, err) + require.Len(t, events, 1) + + accountEvent := flowsdk.AccountCreatedEvent(events[0].Events[0]) + + account, err := adapter.GetAccount(context.Background(), accountEvent.Address()) + assert.NoError(t, err) + + require.Len(t, account.Keys, 1) + assert.Equal(t, accountKey.PublicKey, account.Keys[0].PublicKey) + assert.Equal(t, + map[string][]byte{ + "Test": []byte(testContract), + }, + account.Contracts, + ) + }) + + t.Run("Invalid hash algorithm", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + accountKey := accountKeys.New() + accountKey.SetHashAlgo(crypto.SHA3_384) // SHA3_384 is invalid for ECDSA_P256 + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + nil, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + + assert.True(t, result.Reverted()) + }) + + t.Run("Invalid code", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + contracts := []templates.Contract{ + { + Name: "Test", + Source: "not a valid script", + }, + } + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + nil, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + + assert.True(t, result.Reverted()) + }) + + t.Run("Invalid contract name", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + contracts := []templates.Contract{ + { + Name: "Test2", + Source: testContract, + }, + } + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.CreateAccount( + nil, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + + assert.True(t, result.Reverted()) + }) +} + +func TestAddAccountKey(t *testing.T) { + + t.Parallel() + + accountKeys := test.AccountKeyGenerator() + + t.Run("Valid key", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + newAccountKey, newSigner := accountKeys.NewWithSigner() + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx1, err := templates.AddAccountKey(serviceAccountAddress, newAccountKey) + assert.NoError(t, err) + + tx1.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + script := []byte("transaction { execute {} }") + + var newKeyID = uint32(1) // new key will have ID 1 + var newKeySequenceNum uint64 = 0 + + tx2 := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, newKeyID, newKeySequenceNum). + SetPayer(serviceAccountAddress) + + err = tx2.SignEnvelope(serviceAccountAddress, newKeyID, newSigner) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx2) + require.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + }) + + t.Run("Invalid hash algorithm", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + accountKey := accountKeys.New() + accountKey.SetHashAlgo(crypto.SHA3_384) // SHA3_384 is invalid for ECDSA_P256 + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx, err := templates.AddAccountKey(serviceAccountAddress, accountKey) + assert.NoError(t, err) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Reverted()) + }) +} + +func TestRemoveAccountKey(t *testing.T) { + + t.Parallel() + + b, adapter := setupAccountTests(t) + + accountKeys := test.AccountKeyGenerator() + + newAccountKey, newSigner := accountKeys.NewWithSigner() + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + // create transaction that adds public key to account keys + tx1, err := templates.AddAccountKey(serviceAccountAddress, newAccountKey) + assert.NoError(t, err) + + // create transaction that adds public key to account keys + tx1.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + // sign with service key + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // submit tx1 (should succeed) + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err := adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + require.Len(t, account.Keys, 2) + assert.False(t, account.Keys[0].Revoked) + assert.False(t, account.Keys[1].Revoked) + + // create transaction that removes service key + tx2 := templates.RemoveAccountKey(serviceAccountAddress, 0) + + tx2.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + // sign with service key + signer, err = b.ServiceKey().Signer() + assert.NoError(t, err) + err = tx2.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + assert.NoError(t, err) + + // submit tx2 (should succeed) + err = adapter.SendTransaction(context.Background(), *tx2) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err = adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + // key at index 0 should be revoked + require.Len(t, account.Keys, 2) + assert.True(t, account.Keys[0].Revoked) + assert.False(t, account.Keys[1].Revoked) + + // create transaction that removes remaining account key + tx3 := templates.RemoveAccountKey(serviceAccountAddress, 0) + + tx3.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + // sign with service key (that has been removed) + signer, err = b.ServiceKey().Signer() + assert.NoError(t, err) + err = tx3.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + assert.NoError(t, err) + + // submit tx3 (should fail) + err = adapter.SendTransaction(context.Background(), *tx3) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + + var sigErr fvmerrors.CodedError + assert.ErrorAs(t, result.Error, &sigErr) + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeInvalidProposalSignatureError)) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err = adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + // key at index 1 should not be revoked + require.Len(t, account.Keys, 2) + assert.True(t, account.Keys[0].Revoked) + assert.False(t, account.Keys[1].Revoked) + + // create transaction that removes remaining account key + tx4 := templates.RemoveAccountKey(serviceAccountAddress, 1) + + tx4.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, account.Keys[1].Index, account.Keys[1].SequenceNumber). + SetPayer(serviceAccountAddress) + + // sign with remaining account key + err = tx4.SignEnvelope(serviceAccountAddress, account.Keys[1].Index, newSigner) + assert.NoError(t, err) + + // submit tx4 (should succeed) + err = adapter.SendTransaction(context.Background(), *tx4) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err = adapter.GetAccount(context.Background(), serviceAccountAddress) + assert.NoError(t, err) + + // all keys should be revoked + for _, key := range account.Keys { + assert.True(t, key.Revoked) + } +} + +func TestUpdateAccountCode(t *testing.T) { + + t.Parallel() + + const codeA = ` + access(all) contract Test { + access(all) fun a(): Int { + return 1 + } + } + ` + + const codeB = ` + access(all) contract Test { + access(all) fun b(): Int { + return 2 + } + } + ` + + accountKeys := test.AccountKeyGenerator() + + accountKeyB, signerB := accountKeys.NewWithSigner() + + t.Run("Valid signature", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + contracts := []templates.Contract{ + { + Name: "Test", + Source: codeA, + }, + } + + accountAddressB, err := adapter.CreateAccount( + context.Background(), + []*flowsdk.AccountKey{accountKeyB}, + contracts, + ) + require.NoError(t, err) + + account, err := adapter.GetAccount(context.Background(), accountAddressB) + require.NoError(t, err) + + assert.Equal(t, + map[string][]byte{ + "Test": []byte(codeA), + }, + account.Contracts, + ) + + tx := templates.UpdateAccountContract( + accountAddressB, + templates.Contract{ + Name: "Test", + Source: codeB, + }, + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err = tx.SignPayload(accountAddressB, 0, signerB) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err = adapter.GetAccount(context.Background(), accountAddressB) + assert.NoError(t, err) + + assert.Equal(t, codeB, string(account.Contracts["Test"])) + }) + + t.Run("Invalid signature", func(t *testing.T) { + b, adapter := setupAccountTests(t) + + contracts := []templates.Contract{ + { + Name: "Test", + Source: codeA, + }, + } + + accountAddressB, err := adapter.CreateAccount( + context.Background(), + []*flowsdk.AccountKey{accountKeyB}, + contracts, + ) + require.NoError(t, err) + + account, err := adapter.GetAccount(context.Background(), accountAddressB) + require.NoError(t, err) + + assert.Equal(t, codeA, string(account.Contracts["Test"])) + + tx := templates.UpdateAccountContract( + accountAddressB, + templates.Contract{ + Name: "Test", + Source: codeB, + }, + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeAccountAuthorizationError)) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + account, err = adapter.GetAccount(context.Background(), accountAddressB) + assert.NoError(t, err) + + // code should not be updated + assert.Equal(t, codeA, string(account.Contracts["Test"])) + }) +} + +func TestImportAccountCode(t *testing.T) { + + t.Parallel() + + b, adapter := setupAccountTests(t) + + accountContracts := []templates.Contract{ + { + Name: "Computer", + Source: ` + access(all) contract Computer { + access(all) fun answer(): Int { + return 42 + } + } + `, + }, + } + + address, err := adapter.CreateAccount(context.Background(), nil, accountContracts) + assert.NoError(t, err) + + script := []byte(fmt.Sprintf(` + // address imports can omit leading zeros + import 0x%s + + transaction { + execute { + let answer = Computer.answer() + if answer != 42 { + panic("?!") + } + } + } + `, address)) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) +} + +func TestAccountAccess(t *testing.T) { + + t.Parallel() + + b, adapter := setupAccountTests(t) + + // Create first account and deploy a contract A + // which has a field + // which only other code in the same should be allowed to access + + accountContracts := []templates.Contract{ + { + Name: "A", + Source: ` + access(all) contract A { + access(account) let a: Int + + init() { + self.a = 1 + } + } + `, + }, + } + + accountKeys := test.AccountKeyGenerator() + + accountKey1, signer1 := accountKeys.NewWithSigner() + + address1, err := adapter.CreateAccount( + context.Background(), + []*flowsdk.AccountKey{accountKey1}, + accountContracts, + ) + assert.NoError(t, err) + + // Deploy another contract B to the same account + // which accesses the field in contract A + // which allows access to code in the same account + + tx := templates.AddAccountContract( + address1, + templates.Contract{ + Name: "B", + Source: fmt.Sprintf(` + import A from 0x%s + + access(all) contract B { + access(all) fun use() { + let b = A.a + } + } + `, + address1.Hex(), + ), + }, + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err = tx.SignPayload(address1, 0, signer1) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + require.NoError(t, err) + + // Create another account 2 + + accountKey2, signer2 := accountKeys.NewWithSigner() + + address2, err := adapter.CreateAccount( + context.Background(), + []*flowsdk.AccountKey{accountKey2}, + nil, + ) + assert.NoError(t, err) + + // Deploy a contract C to the second account + // which accesses the field in contract A of the first account + // which allows access to code in the same account + + tx = templates.AddAccountContract( + address2, + templates.Contract{ + Name: "C", + Source: fmt.Sprintf(` + import A from 0x%s + + access(all) contract C { + access(all) fun use() { + let b = A.a + } + } + `, + address1.Hex(), + ), + }, + ) + + tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err = tx.SignPayload(address2, 0, signer2) + require.NoError(t, err) + + signer, err = b.ServiceKey().Signer() + assert.NoError(t, err) + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + require.NoError(t, err) + + require.False(t, result.Succeeded()) + require.Error(t, result.Error) + + require.Contains( + t, + result.Error.Error(), + "error: cannot access `a`: field requires `account` authorization", + ) +} diff --git a/integration/emulator/tests/attachments_test.go b/integration/emulator/tests/attachments_test.go new file mode 100644 index 00000000000..07670a1e1c5 --- /dev/null +++ b/integration/emulator/tests/attachments_test.go @@ -0,0 +1,49 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "github.com/onflow/flow-go/integration/emulator" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAttachments(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + script := ` + access(all) resource R {} + + access(all) attachment A for R {} + + access(all) fun main() { + let r <- create R() + r[A] + destroy r + } + ` + + _, err = b.ExecuteScript([]byte(script), nil) + require.NoError(t, err) +} diff --git a/integration/emulator/tests/block_info_test.go b/integration/emulator/tests/block_info_test.go new file mode 100644 index 00000000000..3bd8e2ec9fd --- /dev/null +++ b/integration/emulator/tests/block_info_test.go @@ -0,0 +1,114 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/integration/emulator" + + flowsdk "github.com/onflow/flow-go-sdk" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBlockInfo(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + block1, err := b.CommitBlock() + require.NoError(t, err) + + block2, err := b.CommitBlock() + require.NoError(t, err) + + t.Run("works as transaction", func(t *testing.T) { + tx := flowsdk.NewTransaction(). + SetScript([]byte(` + transaction { + execute { + let block = getCurrentBlock() + log(block) + + let lastBlock = getBlock(at: block.height - 1) + log(lastBlock) + } + } + `)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + require.Len(t, result.Logs, 2) + assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height+1, + b.PendingBlockView(), b.PendingBlockID(), float64(b.PendingBlockTimestamp().Unix())), result.Logs[0]) + assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height, + block2.Header.View, block2.ID(), float64(block2.Header.Timestamp.Unix())), result.Logs[1]) + }) + + t.Run("works as script", func(t *testing.T) { + script := []byte(` + access(all) fun main() { + let block = getCurrentBlock() + log(block) + + let lastBlock = getBlock(at: block.height - 1) + log(lastBlock) + } + `) + + result, err := b.ExecuteScript(script, nil) + assert.NoError(t, err) + + assert.True(t, result.Succeeded()) + + require.Len(t, result.Logs, 2) + assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height, + block2.Header.View, block2.ID(), float64(block2.Header.Timestamp.Unix())), result.Logs[0]) + assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block1.Header.Height, + block1.Header.View, block1.ID(), float64(block1.Header.Timestamp.Unix())), result.Logs[1]) + }) +} diff --git a/integration/emulator/tests/block_test.go b/integration/emulator/tests/block_test.go new file mode 100644 index 00000000000..0e331865740 --- /dev/null +++ b/integration/emulator/tests/block_test.go @@ -0,0 +1,178 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/onflow/flow-go/integration/emulator" + "github.com/rs/zerolog" + + flowsdk "github.com/onflow/flow-go-sdk" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCommitBlock(t *testing.T) { + + t.Parallel() + + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + + require.NoError(t, err) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Add tx1 to pending block + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx1.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, tx1Result.Status) + + tx2 := flowsdk.NewTransaction(). + SetScript([]byte(`transaction { execute { panic("revert!") } }`)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err = b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx2.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Add tx2 to pending block + err = adapter.SendTransaction(context.Background(), *tx2) + require.NoError(t, err) + + tx2Result, err := adapter.GetTransactionResult(context.Background(), tx2.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, tx2Result.Status) + + // Execute tx1 + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Succeeded()) + + // Execute tx2 + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Reverted()) + + // Commit tx1 and tx2 into new block + _, err = b.CommitBlock() + assert.NoError(t, err) + + // tx1 status becomes TransactionStatusSealed + tx1Result, err = adapter.GetTransactionResult(context.Background(), tx1.ID()) + require.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) + + // tx2 status also becomes TransactionStatusSealed, even though it is reverted + tx2Result, err = adapter.GetTransactionResult(context.Background(), tx2.ID()) + require.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx2Result.Status) + assert.Error(t, tx2Result.Error) +} + +func TestBlockView(t *testing.T) { + + t.Parallel() + + const nBlocks = 3 + + b, err := emulator.New() + require.NoError(t, err) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + t.Run("genesis should have 0 view", func(t *testing.T) { + block, err := b.GetBlockByHeight(0) + require.NoError(t, err) + assert.Equal(t, uint64(0), block.Header.Height) + assert.Equal(t, uint64(0), block.Header.View) + }) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // create a few blocks, each with one transaction + for i := 0; i < nBlocks; i++ { + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Add tx to pending block + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // execute and commit the block + _, _, err = b.ExecuteAndCommitBlock() + require.NoError(t, err) + } + const MaxViewIncrease = 3 + + for height := uint64(1); height <= nBlocks+1; height++ { + block, err := b.GetBlockByHeight(height) + require.NoError(t, err) + + maxView := height * MaxViewIncrease + t.Run(fmt.Sprintf("block %d should have view <%d", height, maxView), func(t *testing.T) { + assert.Equal(t, height, block.Header.Height) + assert.LessOrEqual(t, block.Header.View, maxView) + }) + } +} diff --git a/integration/emulator/tests/blockchain_test.go b/integration/emulator/tests/blockchain_test.go new file mode 100644 index 00000000000..7036891135f --- /dev/null +++ b/integration/emulator/tests/blockchain_test.go @@ -0,0 +1,153 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/onflow/cadence/stdlib" + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/integration/emulator" + + "github.com/onflow/cadence" + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/templates" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const counterScript = ` + + access(all) contract Counting { + + access(all) event CountIncremented(count: Int) + + access(all) resource Counter { + access(all) var count: Int + + init() { + self.count = 0 + } + + access(all) fun add(_ count: Int) { + self.count = self.count + count + emit CountIncremented(count: self.count) + } + } + + access(all) fun createCounter(): @Counter { + return <-create Counter() + } + } +` + +// generateAddTwoToCounterScript generates a script that increments a counter. +// If no counter exists, it is created. +func GenerateAddTwoToCounterScript(counterAddress flowsdk.Address) string { + return fmt.Sprintf( + ` + import 0x%s + + transaction { + prepare(signer: auth(Storage, Capabilities) &Account) { + var counter = signer.storage.borrow<&Counting.Counter>(from: /storage/counter) + if counter == nil { + signer.storage.save(<-Counting.createCounter(), to: /storage/counter) + counter = signer.storage.borrow<&Counting.Counter>(from: /storage/counter) + + // Also publish this for others to borrow. + let cap = signer.capabilities.storage.issue<&Counting.Counter>(/storage/counter) + signer.capabilities.publish(cap, at: /public/counter) + } + counter?.add(2) + } + } + `, + counterAddress, + ) +} + +func DeployAndGenerateAddTwoScript(t *testing.T, adapter *adapters.SDKAdapter) (string, flowsdk.Address) { + + contracts := []templates.Contract{ + { + Name: "Counting", + Source: counterScript, + }, + } + + counterAddress, err := adapter.CreateAccount( + context.Background(), + nil, + contracts, + ) + require.NoError(t, err) + + return GenerateAddTwoToCounterScript(counterAddress), counterAddress +} + +func GenerateGetCounterCountScript(counterAddress flowsdk.Address, accountAddress flowsdk.Address) string { + return fmt.Sprintf( + ` + import 0x%s + + access(all) fun main(): Int { + return getAccount(0x%s).capabilities.borrow<&Counting.Counter>(/public/counter)?.count ?? 0 + } + `, + counterAddress, + accountAddress, + ) +} + +func AssertTransactionSucceeded(t *testing.T, result *emulator.TransactionResult) { + if !assert.True(t, result.Succeeded()) { + t.Error(result.Error) + } +} + +func LastCreatedAccount(b *emulator.Blockchain, result *emulator.TransactionResult) (*flowsdk.Account, error) { + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + address, err := LastCreatedAccountAddress(result) + if err != nil { + return nil, err + } + + return adapter.GetAccount(context.Background(), address) +} + +func LastCreatedAccountAddress(result *emulator.TransactionResult) (flowsdk.Address, error) { + for _, event := range result.Events { + if event.Type == flowsdk.EventAccountCreated { + addressFieldValue := cadence.SearchFieldByName( + event.Value, + stdlib.AccountEventAddressParameter.Identifier, + ) + return flowsdk.Address(addressFieldValue.(cadence.Address)), nil + } + } + + return flowsdk.Address{}, fmt.Errorf("no account created in this result") +} diff --git a/integration/emulator/tests/capcons_test.go b/integration/emulator/tests/capcons_test.go new file mode 100644 index 00000000000..cfe07ac191b --- /dev/null +++ b/integration/emulator/tests/capcons_test.go @@ -0,0 +1,43 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "github.com/onflow/flow-go/integration/emulator" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCapabilityControllers(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + script := ` + access(all) fun main() { + getAccount(0x1).capabilities.get + } + ` + + _, err = b.ExecuteScript([]byte(script), nil) + require.NoError(t, err) +} diff --git a/integration/emulator/tests/collection_test.go b/integration/emulator/tests/collection_test.go new file mode 100644 index 00000000000..7681cc739a0 --- /dev/null +++ b/integration/emulator/tests/collection_test.go @@ -0,0 +1,117 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tests + +import ( + "context" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/rs/zerolog" + + flowsdk "github.com/onflow/flow-go-sdk" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCollections(t *testing.T) { + + t.Parallel() + + t.Run("Empty block", func(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + block, err := b.CommitBlock() + require.NoError(t, err) + + // block should not contain any collections + assert.Empty(t, block.Payload.Guarantees) + }) + + t.Run("Non-empty block", func(t *testing.T) { + + t.Parallel() + + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + require.NoError(t, err) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + tx2 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + err = tx2.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // generate a list of transactions + transactions := []*flowsdk.Transaction{tx1, tx2} + + // add all transactions to block + for _, tx := range transactions { + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + } + + block, _, err := b.ExecuteAndCommitBlock() + require.NoError(t, err) + + // block should contain at least one collection + assert.NotEmpty(t, block.Payload.Guarantees) + + i := 0 + for _, guarantee := range block.Payload.Guarantees { + collection, err := adapter.GetCollectionByID(context.Background(), emulator.FlowIdentifierToSDK(guarantee.ID())) + require.NoError(t, err) + + for _, txID := range collection.TransactionIDs { + assert.Equal(t, transactions[i].ID(), txID) + i++ + } + } + }) +} diff --git a/integration/emulator/tests/events_test.go b/integration/emulator/tests/events_test.go new file mode 100644 index 00000000000..ae52846413b --- /dev/null +++ b/integration/emulator/tests/events_test.go @@ -0,0 +1,202 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/rs/zerolog" + + "github.com/onflow/cadence/common" + "github.com/onflow/flow-go-sdk/templates" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence" + flowsdk "github.com/onflow/flow-go-sdk" +) + +func TestEventEmitted(t *testing.T) { + + t.Parallel() + + t.Run("EmittedFromScript", func(t *testing.T) { + + t.Parallel() + + // Emitting events in scripts is not supported + + b, err := emulator.New() + require.NoError(t, err) + + script := []byte(` + access(all) event MyEvent(x: Int, y: Int) + + access(all) fun main() { + emit MyEvent(x: 1, y: 2) + } + `) + + result, err := b.ExecuteScript(script, nil) + assert.NoError(t, err) + require.NoError(t, result.Error) + require.Empty(t, result.Events) + }) + + t.Run("EmittedFromAccount", func(t *testing.T) { + + t.Parallel() + + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + require.NoError(t, err) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + accountContracts := []templates.Contract{ + { + Name: "Test", + Source: ` + access(all) contract Test { + access(all) event MyEvent(x: Int, y: Int) + + access(all) fun emitMyEvent(x: Int, y: Int) { + emit MyEvent(x: x, y: y) + } + } + `, + }, + } + + publicKey := b.ServiceKey() + accountKey := &flowsdk.AccountKey{ + Index: publicKey.Index, + PublicKey: publicKey.PublicKey, + SigAlgo: publicKey.SigAlgo, + HashAlgo: publicKey.HashAlgo, + Weight: publicKey.Weight, + SequenceNumber: publicKey.SequenceNumber, + } + + address, err := adapter.CreateAccount( + context.Background(), + []*flowsdk.AccountKey{accountKey}, + accountContracts, + ) + assert.NoError(t, err) + + script := []byte(fmt.Sprintf(` + import 0x%s + + transaction { + execute { + Test.emitMyEvent(x: 1, y: 2) + } + } + `, address.Hex())) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Succeeded()) + + block, err := b.CommitBlock() + require.NoError(t, err) + + addr, _ := common.BytesToAddress(address.Bytes()) + location := common.AddressLocation{ + Address: addr, + Name: "Test", + } + expectedType := location.TypeID(nil, "Test.MyEvent") + + events, err := adapter.GetEventsForHeightRange(context.Background(), string(expectedType), block.Header.Height, block.Header.Height) + require.NoError(t, err) + require.Len(t, events, 1) + + actualEvent := events[0].Events[0] + decodedEvent := actualEvent.Value + decodedEventType := decodedEvent.Type().(*cadence.EventType) + expectedID := flowsdk.Event{TransactionID: tx.ID(), EventIndex: 0}.ID() + + assert.Equal(t, string(expectedType), actualEvent.Type) + assert.Equal(t, expectedID, actualEvent.ID()) + + fields := decodedEventType.FieldsMappedByName() + + assert.Contains(t, fields, "x") + assert.Contains(t, fields, "y") + + fieldValues := decodedEvent.FieldsMappedByName() + + assert.Equal(t, cadence.NewInt(1), fieldValues["x"]) + assert.Equal(t, cadence.NewInt(2), fieldValues["y"]) + + events, err = adapter.GetEventsForBlockIDs( + context.Background(), + string(expectedType), + []flowsdk.Identifier{ + flowsdk.Identifier(block.Header.ID()), + }, + ) + require.NoError(t, err) + require.Len(t, events, 1) + + actualEvent = events[0].Events[0] + decodedEvent = actualEvent.Value + decodedEventType = decodedEvent.Type().(*cadence.EventType) + expectedID = flowsdk.Event{TransactionID: tx.ID(), EventIndex: 0}.ID() + + assert.Equal(t, string(expectedType), actualEvent.Type) + assert.Equal(t, expectedID, actualEvent.ID()) + + fields = decodedEventType.FieldsMappedByName() + + assert.Contains(t, fields, "x") + assert.Contains(t, fields, "y") + + fieldValues = decodedEvent.FieldsMappedByName() + + assert.Equal(t, cadence.NewInt(1), fieldValues["x"]) + assert.Equal(t, cadence.NewInt(2), fieldValues["y"]) + + }) +} diff --git a/integration/emulator/tests/logs_test.go b/integration/emulator/tests/logs_test.go new file mode 100644 index 00000000000..39c7d3e4ead --- /dev/null +++ b/integration/emulator/tests/logs_test.go @@ -0,0 +1,45 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "github.com/onflow/flow-go/integration/emulator" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRuntimeLogs(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + script := []byte(` + access(all) fun main() { + log("elephant ears") + } + `) + + result, err := b.ExecuteScript(script, nil) + assert.NoError(t, err) + assert.Equal(t, []string{`"elephant ears"`}, result.Logs) +} diff --git a/integration/emulator/tests/memstore_test.go b/integration/emulator/tests/memstore_test.go new file mode 100644 index 00000000000..0c576126727 --- /dev/null +++ b/integration/emulator/tests/memstore_test.go @@ -0,0 +1,117 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "github.com/onflow/flow-go/integration/emulator" + "sync" + "testing" + + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/model/flow" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMemstore(t *testing.T) { + + t.Parallel() + + const blockHeight = 0 + key := flow.NewRegisterID(flowgo.EmptyAddress, "foo") + value := []byte("bar") + store := emulator.NewMemoryStore() + + err := store.InsertExecutionSnapshot( + blockHeight, + &snapshot.ExecutionSnapshot{ + WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ + key: value, + }, + }, + ) + require.NoError(t, err) + + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + snapshot, err := store.LedgerByHeight( + context.Background(), + blockHeight) + require.NoError(t, err) + actualValue, err := snapshot.Get(key) + + require.NoError(t, err) + assert.Equal(t, value, actualValue) + }() + } + + wg.Wait() +} + +func TestMemstoreSetValueToNil(t *testing.T) { + + t.Parallel() + + store := emulator.NewMemoryStore() + key := flow.NewRegisterID(flowgo.EmptyAddress, "foo") + value := []byte("bar") + var nilByte []byte + nilValue := nilByte + + // set initial value + err := store.InsertExecutionSnapshot( + 0, + &snapshot.ExecutionSnapshot{ + WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ + key: value, + }, + }) + require.NoError(t, err) + + // check initial value + ledger, err := store.LedgerByHeight(context.Background(), 0) + require.NoError(t, err) + register, err := ledger.Get(key) + require.NoError(t, err) + require.Equal(t, string(value), string(register)) + + // set value to nil + err = store.InsertExecutionSnapshot( + 1, + &snapshot.ExecutionSnapshot{ + WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ + key: nilValue, + }, + }) + require.NoError(t, err) + + // check value is nil + ledger, err = store.LedgerByHeight(context.Background(), 1) + require.NoError(t, err) + register, err = ledger.Get(key) + require.NoError(t, err) + require.Equal(t, string(nilValue), string(register)) +} diff --git a/integration/emulator/tests/pendingBlock_test.go b/integration/emulator/tests/pendingBlock_test.go new file mode 100644 index 00000000000..3e512609947 --- /dev/null +++ b/integration/emulator/tests/pendingBlock_test.go @@ -0,0 +1,459 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + "time" + + "github.com/rs/zerolog" + + flowsdk "github.com/onflow/flow-go-sdk" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupPendingBlockTests(t *testing.T) ( + *emulator.Blockchain, + *adapters.SDKAdapter, + *flowsdk.Transaction, + *flowsdk.Transaction, + *flowsdk.Transaction, +) { + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + require.NoError(t, err) + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + tx2 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber+1). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err = b.ServiceKey().Signer() + assert.NoError(t, err) + err = tx2.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + invalid := flowsdk.NewTransaction(). + SetScript([]byte(`transaction { execute { panic("revert!") } }`)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err = b.ServiceKey().Signer() + assert.NoError(t, err) + err = invalid.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + return b, adapter, tx1, tx2, invalid +} + +func TestPendingBlockBeforeExecution(t *testing.T) { + + t.Parallel() + + t.Run("EmptyPendingBlock", func(t *testing.T) { + + t.Parallel() + + b, _, _, _, _ := setupPendingBlockTests(t) + + // Execute empty pending block + _, err := b.ExecuteBlock() + assert.NoError(t, err) + + // Commit empty pending block + _, err = b.CommitBlock() + assert.NoError(t, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("AddDuplicateTransaction", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx, _, _ := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Add tx1 again + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.DuplicateTransactionError{}, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("CommitBeforeExecution", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx, _, _ := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Attempt to commit block before execution begins + _, err = b.CommitBlock() + assert.IsType(t, &emulator.PendingBlockCommitBeforeExecutionError{}, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) +} + +func TestPendingBlockDuringExecution(t *testing.T) { + + t.Parallel() + + t.Run("ExecuteNextTransaction", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, _, invalid := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + require.NoError(t, err) + + // Add invalid script tx to pending block + err = adapter.SendTransaction(context.Background(), *invalid) + require.NoError(t, err) + + // Execute tx1 (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Execute invalid script tx (reverts) + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Reverted()) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("ExecuteBlock", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, _, invalid := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + require.NoError(t, err) + + // Add invalid script tx to pending block + err = adapter.SendTransaction(context.Background(), *invalid) + require.NoError(t, err) + + // Execute all tx in pending block (tx1, invalid) + results, err := b.ExecuteBlock() + assert.NoError(t, err) + + // tx1 result + assert.True(t, results[0].Succeeded()) + // invalid script tx result + assert.True(t, results[1].Reverted()) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("ExecuteNextThenBlock", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, tx2, invalid := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Add tx2 to pending block + err = adapter.SendTransaction(context.Background(), *tx2) + assert.NoError(t, err) + + // Add invalid script tx to pending block + err = adapter.SendTransaction(context.Background(), *invalid) + assert.NoError(t, err) + + // Execute tx1 first (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Execute rest of tx in pending block (tx2, invalid) + results, err := b.ExecuteBlock() + assert.NoError(t, err) + // tx2 result + assert.True(t, results[0].Succeeded()) + // invalid script tx result + assert.True(t, results[1].Reverted()) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("AddTransactionMidExecution", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, tx2, invalid := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Add invalid to pending block + err = adapter.SendTransaction(context.Background(), *invalid) + assert.NoError(t, err) + + // Execute tx1 first (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Attempt to add tx2 to pending block after execution begins + err = adapter.SendTransaction(context.Background(), *tx2) + assert.IsType(t, &emulator.PendingBlockMidExecutionError{}, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("CommitMidExecution", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, _, invalid := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Add invalid to pending block + err = adapter.SendTransaction(context.Background(), *invalid) + assert.NoError(t, err) + + // Execute tx1 first (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Attempt to commit block before execution finishes + _, err = b.CommitBlock() + assert.IsType(t, &emulator.PendingBlockMidExecutionError{}, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) + + t.Run("TransactionsExhaustedDuringExecution", func(t *testing.T) { + + t.Parallel() + + b, adapter, tx1, _, _ := setupPendingBlockTests(t) + + // Add tx1 to pending block + err := adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Execute tx1 (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Attempt to execute nonexistent next tx (fails) + _, err = b.ExecuteNextTransaction() + assert.IsType(t, &emulator.PendingBlockTransactionsExhaustedError{}, err) + + // Attempt to execute rest of block tx (fails) + _, err = b.ExecuteBlock() + assert.IsType(t, &emulator.PendingBlockTransactionsExhaustedError{}, err) + + err = b.ResetPendingBlock() + assert.NoError(t, err) + }) +} + +func TestPendingBlockCommit(t *testing.T) { + + t.Parallel() + + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + require.NoError(t, err) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + t.Run("CommitBlock", func(t *testing.T) { + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Add tx1 to pending block + err = adapter.SendTransaction(context.Background(), *tx1) + require.NoError(t, err) + + // Enter execution mode (block hash should not change after this point) + blockID := b.PendingBlockID() + + // Execute tx1 (succeeds) + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + // Commit pending block + block, err := b.CommitBlock() + assert.NoError(t, err) + assert.Equal(t, blockID, block.ID()) + }) + + t.Run("ExecuteAndCommitBlock", func(t *testing.T) { + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Add tx1 to pending block + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Enter execution mode (block hash should not change after this point) + blockID := b.PendingBlockID() + + // Execute and commit pending block + block, results, err := b.ExecuteAndCommitBlock() + assert.NoError(t, err) + assert.Equal(t, blockID, block.ID()) + assert.Len(t, results, 1) + }) +} + +type testClock struct { + Time time.Time +} + +func (tc testClock) Now() time.Time { + return tc.Time.UTC() +} + +func TestPendingBlockSetTimestamp(t *testing.T) { + + t.Parallel() + + b, adapter, _, _, _ := setupPendingBlockTests(t) + clock := testClock{ + Time: time.Now().UTC(), + } + b.SetClock(clock.Now) + _, _ = b.CommitBlock() + + script := []byte(` + access(all) fun main(): UFix64 { + return getCurrentBlock().timestamp + } + `) + scriptResult, err := adapter.ExecuteScriptAtLatestBlock( + context.Background(), + script, + [][]byte{}, + ) + require.NoError(t, err) + + expected := fmt.Sprintf( + "{\"value\":\"%d.00000000\",\"type\":\"UFix64\"}\n", + clock.Time.Unix(), + ) + assert.Equal(t, expected, string(scriptResult)) + + clock = testClock{ + Time: time.Now().Add(time.Hour * 24 * 7).UTC(), + } + b.SetClock(clock.Now) + _, _ = b.CommitBlock() + + scriptResult, err = adapter.ExecuteScriptAtLatestBlock( + context.Background(), + script, + [][]byte{}, + ) + require.NoError(t, err) + + /*expected = fmt.Sprintf( + "{\"value\":\"%d.00000000\",\"type\":\"UFix64\"}\n", + clock.Time.Unix(), + )*/ + //assert.Equal(t, expected, string(scriptResult)) +} diff --git a/integration/emulator/tests/result_test.go b/integration/emulator/tests/result_test.go new file mode 100644 index 00000000000..c50e890395e --- /dev/null +++ b/integration/emulator/tests/result_test.go @@ -0,0 +1,85 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "errors" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/model/flow" + "testing" + + "github.com/onflow/cadence" + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/test" + "github.com/stretchr/testify/assert" +) + +func TestResult(t *testing.T) { + + t.Parallel() + + t.Run("should return correct boolean", func(t *testing.T) { + + t.Parallel() + + idGenerator := test.IdentifierGenerator() + + trSucceed := &emulator.TransactionResult{ + TransactionID: idGenerator.New(), + ComputationUsed: 20, + MemoryEstimate: 2048, + Error: nil, + Logs: []string{}, + Events: []flowsdk.Event{}, + } + assert.True(t, trSucceed.Succeeded()) + assert.False(t, trSucceed.Reverted()) + + trReverted := &emulator.TransactionResult{ + TransactionID: idGenerator.New(), + ComputationUsed: 20, + MemoryEstimate: 2048, + Error: errors.New("transaction execution error"), + Logs: []string{}, + Events: []flowsdk.Event{}, + } + assert.True(t, trReverted.Reverted()) + assert.False(t, trReverted.Succeeded()) + + srSucceed := &emulator.ScriptResult{ + ScriptID: emulator.SDKIdentifierToFlow(idGenerator.New()), + Value: cadence.Value(cadence.NewInt(1)), + Error: nil, + Logs: []string{}, + Events: []flow.Event{}, + } + assert.True(t, srSucceed.Succeeded()) + assert.False(t, srSucceed.Reverted()) + + srReverted := &emulator.ScriptResult{ + ScriptID: emulator.SDKIdentifierToFlow(idGenerator.New()), + Value: cadence.Value(cadence.NewInt(1)), + Error: errors.New("transaction execution error"), + Logs: []string{}, + Events: []flow.Event{}, + } + assert.True(t, srReverted.Reverted()) + assert.False(t, srReverted.Succeeded()) + }) +} diff --git a/integration/emulator/tests/script_test.go b/integration/emulator/tests/script_test.go new file mode 100644 index 00000000000..3d4f763d266 --- /dev/null +++ b/integration/emulator/tests/script_test.go @@ -0,0 +1,315 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "testing" + + "github.com/onflow/cadence" + jsoncdc "github.com/onflow/cadence/encoding/json" + flowsdk "github.com/onflow/flow-go-sdk" + fvmerrors "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/stdlib" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExecuteScript(t *testing.T) { + + t.Parallel() + + b, err := emulator.New( + emulator.WithStorageLimitEnabled(false), + ) + require.NoError(t, err) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + logger := zerolog.Nop() + adapter := adapters.NewSDKAdapter(&logger, b) + + addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + callScript := GenerateGetCounterCountScript(counterAddress, serviceAccountAddress) + + // Sample call (value is 0) + scriptResult, err := b.ExecuteScript([]byte(callScript), nil) + require.NoError(t, err) + assert.Equal(t, cadence.NewInt(0), scriptResult.Value) + + // Submit tx (script adds 2) + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + txResult, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, txResult) + + t.Run("BeforeCommit", func(t *testing.T) { + t.Skip("TODO: fix stored ledger") + + // Sample call (value is still 0) + result, err := b.ExecuteScript([]byte(callScript), nil) + require.NoError(t, err) + assert.Equal(t, cadence.NewInt(0), result.Value) + }) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + t.Run("AfterCommit", func(t *testing.T) { + // Sample call (value is 2) + result, err := b.ExecuteScript([]byte(callScript), nil) + require.NoError(t, err) + assert.Equal(t, cadence.NewInt(2), result.Value) + }) +} + +func TestExecuteScript_WithArguments(t *testing.T) { + + t.Parallel() + + t.Run("Int", func(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + scriptWithArgs := ` + access(all) fun main(n: Int): Int { + return n + } + ` + + arg, err := jsoncdc.Encode(cadence.NewInt(10)) + require.NoError(t, err) + + scriptResult, err := b.ExecuteScript([]byte(scriptWithArgs), [][]byte{arg}) + require.NoError(t, err) + + assert.Equal(t, cadence.NewInt(10), scriptResult.Value) + }) + + t.Run("String", func(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + scriptWithArgs := ` + access(all) fun main(n: String): Int { + log(n) + return 0 + } + ` + + arg, err := jsoncdc.Encode(cadence.String("Hello, World")) + require.NoError(t, err) + scriptResult, err := b.ExecuteScript([]byte(scriptWithArgs), [][]byte{arg}) + require.NoError(t, err) + assert.Contains(t, scriptResult.Logs, "\"Hello, World\"") + }) +} + +func TestExecuteScript_FlowServiceAccountBalance(t *testing.T) { + + t.Parallel() + + b, err := emulator.New() + require.NoError(t, err) + + code := fmt.Sprintf( + ` + import FlowServiceAccount from %[1]s + + access(all) + fun main(): UFix64 { + let acct = getAccount(%[1]s) + return FlowServiceAccount.defaultTokenBalance(acct) + } + `, + b.GetChain().ServiceAddress().HexWithPrefix(), + ) + + res, err := b.ExecuteScript([]byte(code), nil) + require.NoError(t, err) + require.NoError(t, res.Error) + + require.Positive(t, res.Value) +} + +func TestInfiniteScript(t *testing.T) { + + t.Parallel() + + const limit = 18 + b, err := emulator.New( + emulator.WithScriptGasLimit(limit), + ) + require.NoError(t, err) + + const code = ` + access(all) fun main() { + main() + } + ` + result, err := b.ExecuteScript([]byte(code), nil) + require.NoError(t, err) + + require.True(t, fvmerrors.IsComputationLimitExceededError(result.Error)) +} + +func TestScriptExecutionLimit(t *testing.T) { + + t.Parallel() + + const code = ` + access(all) fun main() { + var s: Int256 = 1024102410241024 + var i: Int256 = 0 + var a: Int256 = 7 + var b: Int256 = 5 + var c: Int256 = 2 + + while i < 150000 { + s = s * a + s = s / b + s = s / c + i = i + 1 + } + } + ` + + t.Run("ExceedingLimit", func(t *testing.T) { + + t.Parallel() + + const limit = 2000 + b, err := emulator.New( + emulator.WithScriptGasLimit(limit), + ) + require.NoError(t, err) + + result, err := b.ExecuteScript([]byte(code), nil) + require.NoError(t, err) + + require.True(t, fvmerrors.IsComputationLimitExceededError(result.Error)) + }) + + t.Run("SufficientLimit", func(t *testing.T) { + + t.Parallel() + + const limit = 19000 + b, err := emulator.New( + emulator.WithScriptGasLimit(limit), + ) + require.NoError(t, err) + + result, err := b.ExecuteScript([]byte(code), nil) + require.NoError(t, err) + require.NoError(t, result.Error) + }) +} + +// TestScriptWithCadenceRandom checks Cadence's random function works +// within a script +func TestScriptWithCadenceRandom(t *testing.T) { + + //language=cadence + code := ` + access(all) + fun main() { + assert(revertibleRandom() >= 0) + } + ` + + const limit = 200 + b, err := emulator.New( + emulator.WithScriptGasLimit(limit), + ) + require.NoError(t, err) + + result, err := b.ExecuteScript([]byte(code), nil) + require.NoError(t, err) + require.NoError(t, result.Error) +} + +// TestEVM checks evm functionality +func TestEVM(t *testing.T) { + serviceAddr := flowgo.Emulator.Chain().ServiceAddress() + code := []byte(fmt.Sprintf( + ` + import EVM from 0x%s + + access(all) + fun main(bytes: [UInt8; 20]) { + log(EVM.EVMAddress(bytes: bytes)) + } + `, + serviceAddr, + )) + + gasLimit := uint64(100_000) + + b, err := emulator.New( + emulator.WithScriptGasLimit(gasLimit), + ) + require.NoError(t, err) + + addressBytesArray := cadence.NewArray([]cadence.Value{ + cadence.UInt8(1), cadence.UInt8(1), + cadence.UInt8(2), cadence.UInt8(2), + cadence.UInt8(3), cadence.UInt8(3), + cadence.UInt8(4), cadence.UInt8(4), + cadence.UInt8(5), cadence.UInt8(5), + cadence.UInt8(6), cadence.UInt8(6), + cadence.UInt8(7), cadence.UInt8(7), + cadence.UInt8(8), cadence.UInt8(8), + cadence.UInt8(9), cadence.UInt8(9), + cadence.UInt8(10), cadence.UInt8(10), + }).WithType(stdlib.EVMAddressBytesCadenceType) + + result, err := b.ExecuteScript(code, [][]byte{jsoncdc.MustEncode(addressBytesArray)}) + require.NoError(t, err) + require.NoError(t, result.Error) + require.Len(t, result.Logs, 1) + require.Equal(t, result.Logs[0], fmt.Sprintf("A.%s.EVM.EVMAddress(bytes: %s)", serviceAddr, addressBytesArray.String())) + +} diff --git a/integration/emulator/tests/store_test.go b/integration/emulator/tests/store_test.go new file mode 100644 index 00000000000..bb05a9ce275 --- /dev/null +++ b/integration/emulator/tests/store_test.go @@ -0,0 +1,480 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests_test + +import ( + "context" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/utils/unittest" + "testing" + + "github.com/onflow/flow-go-sdk/test" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/model/flow" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBlocks(t *testing.T) { + + t.Parallel() + + store := setupStore(t) + + block1 := &flowgo.Block{ + Header: &flowgo.Header{ + Height: 1, + }, + } + block2 := &flowgo.Block{ + Header: &flowgo.Header{ + Height: 2, + }, + } + + t.Run("should return error for not found", func(t *testing.T) { + t.Run("BlockByID", func(t *testing.T) { + freshId := test.IdentifierGenerator().New() + _, err := store.BlockByID(context.Background(), flowgo.Identifier(freshId)) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + + t.Run("BlockByHeight", func(t *testing.T) { + _, err := store.BlockByHeight(context.Background(), block1.Header.Height) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + + t.Run("LatestBlock", func(t *testing.T) { + _, err := store.LatestBlock(context.Background()) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + }) + + t.Run("should be able to insert block", func(t *testing.T) { + err := store.StoreBlock(context.Background(), block1) + assert.NoError(t, err) + }) + + // insert block 1 + err := store.StoreBlock(context.Background(), block1) + assert.NoError(t, err) + + t.Run("should be able to get inserted block", func(t *testing.T) { + t.Run("BlockByHeight", func(t *testing.T) { + block, err := store.BlockByHeight(context.Background(), block1.Header.Height) + assert.NoError(t, err) + assert.Equal(t, block1, block) + }) + + t.Run("BlockByID", func(t *testing.T) { + block, err := store.BlockByID(context.Background(), block1.ID()) + assert.NoError(t, err) + assert.Equal(t, block1, block) + }) + + t.Run("LatestBlock", func(t *testing.T) { + block, err := store.LatestBlock(context.Background()) + assert.NoError(t, err) + assert.Equal(t, *block1, block) + }) + }) + + // insert block 2 + err = store.StoreBlock(context.Background(), block2) + assert.NoError(t, err) + + t.Run("Latest block should update", func(t *testing.T) { + block, err := store.LatestBlock(context.Background()) + assert.NoError(t, err) + assert.Equal(t, *block2, block) + }) +} + +func TestCollections(t *testing.T) { + + t.Parallel() + + store := setupStore(t) + + // collection with 3 transactions + col := unittest.FullCollectionFixture(3) + + t.Run("should return error for not found", func(t *testing.T) { + _, err := store.CollectionByID(context.Background(), col.ID()) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + + t.Run("should be able to insert collection", func(t *testing.T) { + err := store.InsertCollection(col.Light()) + assert.NoError(t, err) + + t.Run("should be able to get inserted collection", func(t *testing.T) { + storedCol, err := store.CollectionByID(context.Background(), col.ID()) + require.NoError(t, err) + assert.Equal(t, col.Light(), storedCol) + }) + }) +} + +func TestTransactions(t *testing.T) { + + t.Parallel() + + store := setupStore(t) + + tx := unittest.TransactionFixture() + + t.Run("should return error for not found", func(t *testing.T) { + _, err := store.TransactionByID(context.Background(), tx.ID()) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + + t.Run("should be able to insert tx", func(t *testing.T) { + err := store.InsertTransaction(tx.ID(), tx) + assert.NoError(t, err) + + t.Run("should be able to get inserted tx", func(t *testing.T) { + storedTx, err := store.TransactionByID(context.Background(), tx.ID()) + require.NoError(t, err) + assert.Equal(t, tx.ID(), storedTx.ID()) + }) + }) +} + +func TestFullCollection(t *testing.T) { + t.Parallel() + store := setupStore(t) + + col := unittest.FullCollectionFixture(3) + + t.Run("should be able to insert full collection", func(t *testing.T) { + _, err := store.CollectionByID(context.Background(), col.ID()) + require.Error(t, emulator.ErrNotFound, err) + + _, err = store.FullCollectionByID(context.Background(), col.ID()) + require.Error(t, emulator.ErrNotFound, err) + + err = store.InsertCollection(col.Light()) + require.NoError(t, err) + + for _, tx := range col.Transactions { + err = store.InsertTransaction(tx.ID(), *tx) + require.NoError(t, err) + } + + c, err := store.FullCollectionByID(context.Background(), col.ID()) + require.NoError(t, err) + require.Equal(t, col, c) + }) + +} + +func TestTransactionResults(t *testing.T) { + + t.Parallel() + + test := func(eventEncodingVersion entities.EventEncodingVersion) { + + t.Run(eventEncodingVersion.String(), func(t *testing.T) { + t.Parallel() + + store := setupStore(t) + + ids := test.IdentifierGenerator() + + result := unittest.StorableTransactionResultFixture(eventEncodingVersion) + + t.Run("should return error for not found", func(t *testing.T) { + txID := flowgo.Identifier(ids.New()) + + _, err := store.TransactionResultByID(context.Background(), txID) + if assert.Error(t, err) { + assert.Equal(t, emulator.ErrNotFound, err) + } + }) + + t.Run("should be able to insert result", func(t *testing.T) { + txID := flowgo.Identifier(ids.New()) + + err := store.InsertTransactionResult(txID, result) + assert.NoError(t, err) + + t.Run("should be able to get inserted result", func(t *testing.T) { + storedResult, err := store.TransactionResultByID(context.Background(), txID) + require.NoError(t, err) + assert.Equal(t, result, storedResult) + }) + }) + }) + } + + test(entities.EventEncodingVersion_CCF_V0) + test(entities.EventEncodingVersion_JSON_CDC_V0) +} + +func TestLedger(t *testing.T) { + + t.Parallel() + + t.Run("get/set", func(t *testing.T) { + + t.Parallel() + + store := setupStore(t) + + var blockHeight uint64 = 1 + + owner := flow.HexToAddress("0x01") + const key = "foo" + expected := []byte("bar") + + executionSnapshot := &snapshot.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + flow.NewRegisterID(owner, key): expected, + }, + } + + t.Run("should get able to set ledger", func(t *testing.T) { + err := store.InsertExecutionSnapshot( + blockHeight, + executionSnapshot) + assert.NoError(t, err) + }) + + t.Run("should be to get set ledger", func(t *testing.T) { + gotLedger, err := store.LedgerByHeight(context.Background(), blockHeight) + assert.NoError(t, err) + actual, err := gotLedger.Get(flow.NewRegisterID(owner, key)) + assert.NoError(t, err) + assert.Equal(t, expected, actual) + }) + }) + + t.Run("versioning", func(t *testing.T) { + + t.Parallel() + store := setupStore(t) + + owner := flow.HexToAddress("0x01") + + // Create a list of ledgers, where the ledger at index i has + // keys (i+2)-1->(i+2)+1 set to value i-1. + totalBlocks := 10 + var snapshots []*snapshot.ExecutionSnapshot + for i := 2; i < totalBlocks+2; i++ { + writeSet := map[flow.RegisterID]flow.RegisterValue{} + for j := i - 1; j <= i+1; j++ { + key := fmt.Sprintf("%d", j) + writeSet[flow.NewRegisterID(owner, key)] = []byte{byte(i - 1)} + } + snapshots = append( + snapshots, + &snapshot.ExecutionSnapshot{WriteSet: writeSet}) + } + require.Equal(t, totalBlocks, len(snapshots)) + + // Insert all the ledgers, starting with block 1. + // This will result in a ledger state that looks like this: + // Block 1: {1: 1, 2: 1, 3: 1} + // Block 2: {2: 2, 3: 2, 4: 2} + // ... + // The combined state at block N looks like: + // {1: 1, 2: 2, 3: 3, ..., N+1: N, N+2: N} + for i, snapshot := range snapshots { + err := store.InsertExecutionSnapshot( + uint64(i+1), + snapshot) + require.NoError(t, err) + } + + // View at block 1 should have keys 1, 2, 3 + t.Run("should version the first written block", func(t *testing.T) { + gotLedger, err := store.LedgerByHeight(context.Background(), 1) + assert.NoError(t, err) + for i := 1; i <= 3; i++ { + val, err := gotLedger.Get(flow.NewRegisterID(owner, fmt.Sprintf("%d", i))) + assert.NoError(t, err) + assert.Equal(t, []byte{byte(1)}, val) + } + }) + + // View at block N should have values 1->N+2 + t.Run("should version all blocks", func(t *testing.T) { + for block := 2; block < totalBlocks; block++ { + gotLedger, err := store.LedgerByHeight(context.Background(), uint64(block)) + assert.NoError(t, err) + // The keys 1->N-1 are defined in previous blocks + for i := 1; i < block; i++ { + val, err := gotLedger.Get(flow.NewRegisterID(owner, fmt.Sprintf("%d", i))) + assert.NoError(t, err) + assert.Equal(t, []byte{byte(i)}, val) + } + // The keys N->N+2 are defined in the queried block + for i := block; i <= block+2; i++ { + val, err := gotLedger.Get(flow.NewRegisterID(owner, fmt.Sprintf("%d", i))) + assert.NoError(t, err) + assert.Equal(t, []byte{byte(block)}, val) + } + } + }) + }) +} + +func TestInsertEvents(t *testing.T) { + + t.Parallel() + + test := func(eventEncodingVersion entities.EventEncodingVersion) { + + t.Run(eventEncodingVersion.String(), func(t *testing.T) { + t.Parallel() + + store := setupStore(t) + + events := test.EventGenerator(eventEncodingVersion) + + t.Run("should be able to insert events", func(t *testing.T) { + event, _ := emulator.SDKEventToFlow(events.New()) + events := []flowgo.Event{event} + + var blockHeight uint64 = 1 + + err := store.InsertEvents(blockHeight, events) + assert.NoError(t, err) + + t.Run("should be able to get inserted events", func(t *testing.T) { + gotEvents, err := store.EventsByHeight(context.Background(), blockHeight, "") + assert.NoError(t, err) + assert.Equal(t, events, gotEvents) + }) + }) + }) + } + + test(entities.EventEncodingVersion_CCF_V0) + test(entities.EventEncodingVersion_JSON_CDC_V0) +} + +func TestEventsByHeight(t *testing.T) { + + t.Parallel() + test := func(eventEncodingVersion entities.EventEncodingVersion) { + + t.Run(eventEncodingVersion.String(), func(t *testing.T) { + t.Parallel() + + store := setupStore(t) + + events := test.EventGenerator(eventEncodingVersion) + + var ( + nonEmptyBlockHeight uint64 = 1 + emptyBlockHeight uint64 = 2 + nonExistentBlockHeight uint64 = 3 + + allEvents = make([]flowgo.Event, 10) + eventsA = make([]flowgo.Event, 0, 5) + eventsB = make([]flowgo.Event, 0, 5) + ) + + for i := range allEvents { + event, _ := emulator.SDKEventToFlow(events.New()) + + event.TransactionIndex = uint32(i) + event.EventIndex = uint32(i * 2) + + // interleave events of both types + if i%2 == 0 { + event.Type = "A" + eventsA = append(eventsA, event) + } else { + event.Type = "B" + eventsB = append(eventsB, event) + } + + allEvents[i] = event + } + + err := store.InsertEvents(nonEmptyBlockHeight, allEvents) + assert.NoError(t, err) + + err = store.InsertEvents(emptyBlockHeight, nil) + assert.NoError(t, err) + + t.Run("should be able to query by block", func(t *testing.T) { + t.Run("non-empty block", func(t *testing.T) { + events, err := store.EventsByHeight(context.Background(), nonEmptyBlockHeight, "") + assert.NoError(t, err) + assert.Equal(t, allEvents, events) + }) + + t.Run("empty block", func(t *testing.T) { + events, err := store.EventsByHeight(context.Background(), emptyBlockHeight, "") + assert.NoError(t, err) + assert.Empty(t, events) + }) + + t.Run("non-existent block", func(t *testing.T) { + events, err := store.EventsByHeight(context.Background(), nonExistentBlockHeight, "") + assert.NoError(t, err) + assert.Empty(t, events) + }) + }) + + t.Run("should be able to query by event type", func(t *testing.T) { + t.Run("type=A, block=1", func(t *testing.T) { + // should be one event type=1 in block 1 + events, err := store.EventsByHeight(context.Background(), nonEmptyBlockHeight, "A") + assert.NoError(t, err) + assert.Equal(t, eventsA, events) + }) + + t.Run("type=B, block=1", func(t *testing.T) { + // should be 0 type=2 events here + events, err := store.EventsByHeight(context.Background(), nonEmptyBlockHeight, "B") + assert.NoError(t, err) + assert.Equal(t, eventsB, events) + }) + }) + }) + } + + test(entities.EventEncodingVersion_CCF_V0) + test(entities.EventEncodingVersion_JSON_CDC_V0) +} + +// setupStore creates a temporary file for the Sqlite and creates a +// sqlite.Store instance. The caller is responsible for closing the store +// and deleting the temporary directory. +func setupStore(t *testing.T) *emulator.Store { + return emulator.NewMemoryStore() +} diff --git a/integration/emulator/tests/temp_dep_test.go b/integration/emulator/tests/temp_dep_test.go new file mode 100644 index 00000000000..6bd6219f1b6 --- /dev/null +++ b/integration/emulator/tests/temp_dep_test.go @@ -0,0 +1,25 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tests + +import "github.com/btcsuite/btcd/chaincfg/chainhash" + +// this is added to resolve the issue with chainhash ambiguous import, +// the code is not used, but it's needed to force go.mod specify and retain chainhash version +// workaround for issue: https://github.com/golang/go/issues/27899 +var _ = chainhash.Hash{} diff --git a/integration/emulator/tests/transaction_test.go b/integration/emulator/tests/transaction_test.go new file mode 100644 index 00000000000..7e6f96b0e98 --- /dev/null +++ b/integration/emulator/tests/transaction_test.go @@ -0,0 +1,2143 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" + "io" + "strings" + "testing" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/flow-go-sdk" + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/crypto" + "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go-sdk/test" + fvmerrors "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/stdlib" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTransactionTests(t *testing.T, opts ...emulator.Option) ( + *emulator.Blockchain, + *adapters.SDKAdapter, +) { + b, err := emulator.New(opts...) + require.NoError(t, err) + + logger := zerolog.Nop() + return b, adapters.NewSDKAdapter(&logger, b) +} + +func TestSubmitTransaction(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx1 + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Execute tx1 + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // tx1 status becomes TransactionStatusSealed + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx1.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) +} + +// TODO: Add test case for missing ReferenceBlockID +// TODO: Add test case for missing ProposalKey +func TestSubmitTransaction_Invalid(t *testing.T) { + + t.Parallel() + + t.Run("Empty transaction", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + // Create empty transaction (no required fields) + tx := flowsdk.NewTransaction() + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, err, &emulator.IncompleteTransactionError{}) + }) + + t.Run("Missing script", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + // Create transaction with no Script field + tx := flowsdk.NewTransaction(). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, err, &emulator.IncompleteTransactionError{}) + }) + + t.Run("Invalid script", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + // Create transaction with invalid Script field + tx := flowsdk.NewTransaction(). + SetScript([]byte("this script cannot be parsed")). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.InvalidTransactionScriptError{}, err) + }) + + t.Run("Missing gas limit", func(t *testing.T) { + + t.Parallel() + + t.Skip("TODO: transaction validation") + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // Create transaction with no GasLimit field + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.IncompleteTransactionError{}, err) + }) + + t.Run("Missing payer account", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // Create transaction with no PayerAccount field + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, err, &emulator.IncompleteTransactionError{}) + }) + + t.Run("Missing proposal key", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // Create transaction with no PayerAccount field + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit) + + tx.ProposalKey = flowsdk.ProposalKey{} + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.IncompleteTransactionError{}, err) + }) + + t.Run("Invalid sequence number", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + invalidSequenceNumber := b.ServiceKey().SequenceNumber + 2137 + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetPayer(serviceAccountAddress). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, invalidSequenceNumber). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + + require.Error(t, result.Error) + + assert.IsType(t, &emulator.FVMError{}, result.Error) + seqErr := fvmerrors.InvalidProposalSeqNumberError{} + ok := errors.As(result.Error, &seqErr) + assert.True(t, ok) + assert.Equal(t, invalidSequenceNumber, seqErr.ProvidedSeqNumber()) + }) + + const expiry = 10 + + t.Run("Missing reference block ID", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithTransactionExpiry(expiry), + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.IncompleteTransactionError{}, err) + }) + + t.Run("Expired transaction", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithTransactionExpiry(expiry), + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + expiredBlock, err := b.GetLatestBlock() + require.NoError(t, err) + + // commit blocks until expiry window is exceeded + for i := 0; i < expiry+1; i++ { + _, _, err := b.ExecuteAndCommitBlock() + require.NoError(t, err) + } + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetReferenceBlockID(flowsdk.Identifier(expiredBlock.ID())). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, &emulator.ExpiredTransactionError{}, err) + }) + + t.Run("Invalid signature for provided data", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + tx.SetComputeLimit(100) // change data after signing + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + debug := emulator.NewTransactionInvalidSignature(&flowgo.TransactionBody{ + ReferenceBlockID: flowgo.Identifier{}, + Script: nil, + Arguments: nil, + GasLimit: flowgo.DefaultMaxTransactionGasLimit, + ProposalKey: flowgo.ProposalKey{ + Address: emulator.SDKAddressToFlow(serviceAccountAddress), + KeyIndex: b.ServiceKey().Index, + SequenceNumber: b.ServiceKey().SequenceNumber, + }, + Payer: emulator.SDKAddressToFlow(serviceAccountAddress), + Authorizers: emulator.SDKAddressesToFlow([]flowsdk.Address{serviceAccountAddress}), + PayloadSignatures: nil, + EnvelopeSignatures: nil, + }) + + assert.NotNil(t, result.Error) + assert.IsType(t, result.Debug, debug) + }) +} + +func TestSubmitTransaction_Duplicate(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // Submit same tx again (errors) + err = adapter.SendTransaction(context.Background(), *tx) + assert.IsType(t, err, &emulator.DuplicateTransactionError{}) +} + +func TestSubmitTransaction_Reverted(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(`transaction { execute { panic("revert!") } }`)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit invalid tx1 + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Reverted()) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // tx1 status becomes TransactionStatusSealed + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) + assert.Error(t, tx1Result.Error) +} + +func TestSubmitTransaction_Authorizers(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + accountKeys := test.AccountKeyGenerator() + + accountKeyB, signerB := accountKeys.NewWithSigner() + accountKeyB.SetWeight(flowsdk.AccountKeyWeightThreshold) + + accountAddressB, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKeyB}, nil) + assert.NoError(t, err) + + t.Run("Extra authorizers", func(t *testing.T) { + // script only supports one account + script := []byte(` + transaction { + prepare(signer: &Account) {} + } + `) + + // create transaction with two authorizing accounts + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress). + AddAuthorizer(accountAddressB) + + err = tx.SignPayload(accountAddressB, 0, signerB) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + assert.True(t, result.Reverted()) + + _, err = b.CommitBlock() + assert.NoError(t, err) + }) + + t.Run("Insufficient authorizers", func(t *testing.T) { + // script requires two accounts + script := []byte(` + transaction { + prepare(signerA: &Account, signerB: &Account) {} + } + `) + + // create transaction with two accounts + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.True(t, result.Reverted()) + + _, err = b.CommitBlock() + assert.NoError(t, err) + }) +} + +func TestSubmitTransaction_EnvelopeSignature(t *testing.T) { + + t.Parallel() + + t.Run("Missing envelope signature", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignPayload(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeAccountAuthorizationError)) + }) + + t.Run("Invalid account", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addresses := flowsdk.NewAddressGenerator(flowsdk.Emulator) + for { + _, err := adapter.GetAccount(context.Background(), addresses.NextAddress()) + if err != nil { + break + } + } + + nonExistentAccountAddress := addresses.Address() + + script := []byte(` + transaction { + prepare(signer: &Account) {} + } + `) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(nonExistentAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignPayload(nonExistentAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.Error(t, result.Error) + assert.True(t, fvmerrors.IsAccountPublicKeyNotFoundError(result.Error)) + }) + + t.Run("Mismatched authorizer count", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithTransactionValidationEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addresses := flowsdk.NewAddressGenerator(flowsdk.Emulator) + for { + _, err := adapter.GetAccount(context.Background(), addresses.NextAddress()) + if err != nil { + break + } + } + + nonExistentAccountAddress := addresses.Address() + + script := []byte(` + transaction { + prepare() {} + } + `) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(nonExistentAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignPayload(nonExistentAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.ErrorContains(t, result.Error, "authorizer count mismatch") + }) + + t.Run("Invalid key", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // use key that does not exist on service account + invalidKey, _ := crypto.GeneratePrivateKey(crypto.ECDSA_P256, + []byte("invalid key invalid key invalid key invalid key invalid key invalid key")) + invalidSigner, err := crypto.NewNaiveSigner(invalidKey, crypto.SHA3_256) + require.NoError(t, err) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, invalidSigner) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeInvalidProposalSignatureError)) + }) + + t.Run("Key weights", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + + accountKeys := test.AccountKeyGenerator() + + accountKeyA, signerA := accountKeys.NewWithSigner() + accountKeyA.SetWeight(flowsdk.AccountKeyWeightThreshold / 2) + + accountKeyB, signerB := accountKeys.NewWithSigner() + accountKeyB.SetWeight(flowsdk.AccountKeyWeightThreshold / 2) + + accountAddressA, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKeyA, accountKeyB}, nil) + assert.NoError(t, err) + + script := []byte(` + transaction { + prepare(signer: &Account) {} + } + `) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(accountAddressA, 1, 0). + SetPayer(accountAddressA). + AddAuthorizer(accountAddressA) + + // Insufficient keys + err = tx.SignEnvelope(accountAddressA, 1, signerB) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Add key so we have sufficient keys + err = tx.SignEnvelope(accountAddressA, 0, signerA) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + t.Run("Insufficient key weight", func(t *testing.T) { + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeAccountAuthorizationError)) + }) + + t.Run("Sufficient key weight", func(t *testing.T) { + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + AssertTransactionSucceeded(t, result) + }) + }) +} + +func TestSubmitTransaction_PayloadSignatures(t *testing.T) { + + t.Parallel() + + t.Run("Missing payload signature", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + // create a new account, + // authorizer must be different from payer + + accountKeys := test.AccountKeyGenerator() + + accountKeyB, _ := accountKeys.NewWithSigner() + accountKeyB.SetWeight(flowsdk.AccountKeyWeightThreshold) + + accountAddressB, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKeyB}, nil) + assert.NoError(t, err) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(accountAddressB) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + assert.True(t, fvmerrors.HasErrorCode(result.Error, fvmerrors.ErrCodeAccountAuthorizationError)) + }) + + t.Run("Multiple payload signers", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + accountKeys := test.AccountKeyGenerator() + + accountKeyB, signerB := accountKeys.NewWithSigner() + accountKeyB.SetWeight(flowsdk.AccountKeyWeightThreshold) + + accountAddressB, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKeyB}, nil) + assert.NoError(t, err) + + multipleAccountScript := []byte(` + transaction { + prepare(signerA: &Account, signerB: &Account) { + log(signerA.address) + log(signerB.address) + } + } + `) + + tx := flowsdk.NewTransaction(). + SetScript(multipleAccountScript). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress). + AddAuthorizer(accountAddressB) + + err = tx.SignPayload(accountAddressB, 0, signerB) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + require.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + assert.Contains(t, + result.Logs, + interpreter.NewUnmeteredAddressValueFromBytes(serviceAccountAddress.Bytes()).String(), + ) + + assert.Contains(t, + result.Logs, + interpreter.NewUnmeteredAddressValueFromBytes(accountAddressB.Bytes()).String(), + ) + }) +} + +func TestSubmitTransaction_Arguments(t *testing.T) { + + t.Parallel() + + addresses := test.AddressGenerator() + + fix64Value, _ := cadence.NewFix64("123456.00000") + uFix64Value, _ := cadence.NewUFix64("123456.00000") + + var tests = []struct { + argType cadence.Type + arg cadence.Value + }{ + { + cadence.BoolType, + cadence.NewBool(true), + }, + { + cadence.StringType, + cadence.String("foo"), + }, + { + cadence.AddressType, + cadence.NewAddress(addresses.New()), + }, + { + cadence.IntType, + cadence.NewInt(42), + }, + { + cadence.Int8Type, + cadence.NewInt8(42), + }, + { + cadence.Int16Type, + cadence.NewInt16(42), + }, + { + cadence.Int32Type, + cadence.NewInt32(42), + }, + { + cadence.Int64Type, + cadence.NewInt64(42), + }, + { + cadence.Int128Type, + cadence.NewInt128(42), + }, + { + cadence.Int256Type, + cadence.NewInt256(42), + }, + { + cadence.UIntType, + cadence.NewUInt(42), + }, + { + cadence.UInt8Type, + cadence.NewUInt8(42), + }, + { + cadence.UInt16Type, + cadence.NewUInt16(42), + }, + { + cadence.UInt32Type, + cadence.NewUInt32(42), + }, + { + cadence.UInt64Type, + cadence.NewUInt64(42), + }, + { + cadence.UInt128Type, + cadence.NewUInt128(42), + }, + { + cadence.UInt256Type, + cadence.NewUInt256(42), + }, + { + cadence.Word8Type, + cadence.NewWord8(42), + }, + { + cadence.Word16Type, + cadence.NewWord16(42), + }, + { + cadence.Word32Type, + cadence.NewWord32(42), + }, + { + cadence.Word64Type, + cadence.NewWord64(42), + }, + { + cadence.Fix64Type, + fix64Value, + }, + { + cadence.UFix64Type, + uFix64Value, + }, + { + &cadence.ConstantSizedArrayType{ + Size: 3, + ElementType: cadence.IntType, + }, + cadence.NewArray([]cadence.Value{ + cadence.NewInt(1), + cadence.NewInt(2), + cadence.NewInt(3), + }), + }, + { + &cadence.DictionaryType{ + KeyType: cadence.StringType, + ElementType: cadence.IntType, + }, + cadence.NewDictionary([]cadence.KeyValuePair{ + { + Key: cadence.String("a"), + Value: cadence.NewInt(1), + }, + { + Key: cadence.String("b"), + Value: cadence.NewInt(2), + }, + { + Key: cadence.String("c"), + Value: cadence.NewInt(3), + }, + }), + }, + } + + var script = func(argType cadence.Type) []byte { + return []byte(fmt.Sprintf(` + transaction(x: %s) { + execute { + log(x) + } + } + `, argType.ID())) + } + + for _, tt := range tests { + t.Run(tt.argType.ID(), func(t *testing.T) { + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx := flowsdk.NewTransaction(). + SetScript(script(tt.argType)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err := tx.AddArgument(tt.arg) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + assert.Len(t, result.Logs, 1) + }) + } + + t.Run("Log", func(t *testing.T) { + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + script := []byte(` + transaction(x: Int) { + execute { + log(x * 6) + } + } + `) + + x := 7 + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err := tx.AddArgument(cadence.NewInt(x)) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + require.Len(t, result.Logs, 1) + assert.Equal(t, "42", result.Logs[0]) + }) +} + +func TestSubmitTransaction_ProposerSequence(t *testing.T) { + + t.Parallel() + + t.Run("Valid transaction increases sequence number", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + script := []byte(` + transaction { + prepare(signer: &Account) {} + } + `) + prevSeq := b.ServiceKey().SequenceNumber + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) + + assert.Equal(t, prevSeq+1, b.ServiceKey().SequenceNumber) + }) + + t.Run("Reverted transaction increases sequence number", func(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + prevSeq := b.ServiceKey().SequenceNumber + script := []byte(` + transaction { + prepare(signer: &Account) {} + execute { panic("revert!") } + } + `) + + tx := flowsdk.NewTransaction(). + SetScript(script). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + _, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, prevSeq+1, b.ServiceKey().SequenceNumber) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) + assert.Len(t, tx1Result.Events, 0) + assert.IsType(t, &emulator.ExecutionError{}, tx1Result.Error) + }) +} + +func TestGetTransaction(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + t.Run("Nonexistent", func(t *testing.T) { + _, err := adapter.GetTransaction(context.Background(), flowsdk.EmptyID) + if assert.Error(t, err) { + assert.IsType(t, &emulator.TransactionNotFoundError{}, err) + } + }) + + t.Run("Existent", func(t *testing.T) { + tx2, err := adapter.GetTransaction(context.Background(), tx1.ID()) + require.NoError(t, err) + + assert.Equal(t, tx1.ID(), tx2.ID()) + }) +} + +func TestGetTransactionResult(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + result, err := adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusUnknown, result.Status) + require.Empty(t, result.Events) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, result.Status) + require.Empty(t, result.Events) + + _, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, result.Status) + require.Empty(t, result.Events) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, result.Status) + + require.Len(t, result.Events, 3) + + event1 := result.Events[0] + assert.Equal(t, tx.ID(), event1.TransactionID) + assert.Equal(t, "flow.StorageCapabilityControllerIssued", event1.Type) + assert.Equal(t, 0, event1.EventIndex) + + event2 := result.Events[1] + assert.Equal(t, tx.ID(), event2.TransactionID) + assert.Equal(t, "flow.CapabilityPublished", event2.Type) + assert.Equal(t, 1, event2.EventIndex) + + event3 := result.Events[2] + addr, _ := common.BytesToAddress(counterAddress.Bytes()) + location := common.AddressLocation{ + Address: addr, + Name: "Counting", + } + assert.Equal(t, tx.ID(), event3.TransactionID) + assert.Equal(t, + string(location.TypeID(nil, "Counting.CountIncremented")), + event3.Type, + ) + assert.Equal(t, 2, event3.EventIndex) + fields := cadence.FieldsMappedByName(event3.Value) + assert.Len(t, fields, 1) + assert.Equal(t, + cadence.NewInt(2), + fields["count"], + ) +} + +// TestGetTxByBlockIDMethods tests the GetTransactionByBlockID and GetTransactionResultByBlockID +// methods return the correct transaction and transaction result for a given block ID. +func TestGetTxByBlockIDMethods(t *testing.T) { + + t.Parallel() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + + const code = ` + transaction { + execute { + log("Hello, World!") + } + } + ` + + serviceKey := b.ServiceKey() + serviceAccountAddress := flowsdk.Address(serviceKey.Address) + + signer, err := serviceKey.Signer() + require.NoError(t, err) + + submittedTx := make([]*flowsdk.Transaction, 0) + + // submit 5 tx to be executed in a single block + for i := uint64(0); i < 5; i++ { + tx := flowsdk.NewTransaction(). + SetScript([]byte(code)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, serviceKey.Index, serviceKey.SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + err = tx.SignEnvelope(serviceAccountAddress, serviceKey.Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // added to fix tx matching (nil vs empty slice) + tx.PayloadSignatures = []flow.TransactionSignature{} + + submittedTx = append(submittedTx, tx) + + // tx will be executed in the order they were submitted + serviceKey.SequenceNumber++ + } + + // execute the batch of transactions + block, expectedResults, err := b.ExecuteAndCommitBlock() + assert.NoError(t, err) + assert.Len(t, expectedResults, len(submittedTx)) + + results, err := adapter.GetTransactionResultsByBlockID(context.Background(), flowsdk.Identifier(block.ID())) + require.NoError(t, err) + assert.Len(t, results, len(submittedTx)) + + transactions, err := adapter.GetTransactionsByBlockID(context.Background(), flowsdk.Identifier(block.ID())) + require.NoError(t, err) + assert.Len(t, transactions, len(submittedTx)) + + // make sure the results and transactions returned match the transactions submitted, and are in + // the same order + for i, tx := range submittedTx { + assert.Equal(t, tx.ID(), transactions[i].ID()) + assert.Equal(t, submittedTx[i], transactions[i]) + + assert.Equal(t, tx.ID(), results[i].TransactionID) + assert.Equal(t, tx.ID(), expectedResults[i].TransactionID) + // note: expectedResults from ExecuteAndCommitBlock and results from GetTransactionResultsByBlockID + // use different representations. results is missing some data included in the flow.TransactionResult + // struct, so we can't compare them directly. + } +} + +const helloWorldContract = ` + access(all) contract HelloWorld { + + access(all) fun hello(): String { + return "Hello, World!" + } + } +` + +const callHelloTxTemplate = ` + import HelloWorld from 0x%s + transaction { + prepare() { + assert(HelloWorld.hello() == "Hello, World!") + } + } +` + +func TestHelloWorld_NewAccount(t *testing.T) { + + t.Parallel() + + accountKeys := test.AccountKeyGenerator() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + accountKey, accountSigner := accountKeys.NewWithSigner() + + contracts := []templates.Contract{ + { + Name: "HelloWorld", + Source: helloWorldContract, + }, + } + + createAccountTx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + contracts, + serviceAccountAddress, + ) + require.NoError(t, err) + + createAccountTx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = createAccountTx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *createAccountTx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // createAccountTx status becomes TransactionStatusSealed + createAccountTxResult, err := adapter.GetTransactionResult(context.Background(), createAccountTx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, createAccountTxResult.Status) + + var newAccountAddress flowsdk.Address + for _, event := range createAccountTxResult.Events { + if event.Type != flowsdk.EventAccountCreated { + continue + } + accountCreatedEvent := flowsdk.AccountCreatedEvent(event) + newAccountAddress = accountCreatedEvent.Address() + break + } + + if newAccountAddress == flowsdk.EmptyAddress { + assert.Fail(t, "missing account created event") + } + + t.Logf("new account address: 0x%s", newAccountAddress.Hex()) + + account, err := adapter.GetAccount(context.Background(), newAccountAddress) + assert.NoError(t, err) + + assert.Equal(t, newAccountAddress, account.Address) + + // call hello world code + + accountKey = account.Keys[0] + + callHelloCode := []byte(fmt.Sprintf(callHelloTxTemplate, newAccountAddress.Hex())) + callHelloTx := flowsdk.NewTransaction(). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetScript(callHelloCode). + SetProposalKey(newAccountAddress, accountKey.Index, accountKey.SequenceNumber). + SetPayer(newAccountAddress) + + err = callHelloTx.SignEnvelope(newAccountAddress, accountKey.Index, accountSigner) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *callHelloTx) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) +} + +func TestHelloWorld_UpdateAccount(t *testing.T) { + + t.Parallel() + + accountKeys := test.AccountKeyGenerator() + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + accountKey, accountSigner := accountKeys.NewWithSigner() + _ = accountSigner + + contracts := []templates.Contract{ + { + Name: "HelloWorld", + Source: `access(all) contract HelloWorld {}`, + }, + } + + createAccountTx, err := templates.CreateAccount( + []*flowsdk.AccountKey{accountKey}, + contracts, + serviceAccountAddress, + ) + assert.NoError(t, err) + + createAccountTx. + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = createAccountTx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *createAccountTx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // createAccountTx status becomes TransactionStatusSealed + createAccountTxResult, err := adapter.GetTransactionResult(context.Background(), createAccountTx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, createAccountTxResult.Status) + + var newAccountAddress flowsdk.Address + for _, event := range createAccountTxResult.Events { + if event.Type != flowsdk.EventAccountCreated { + continue + } + accountCreatedEvent := flowsdk.AccountCreatedEvent(event) + newAccountAddress = accountCreatedEvent.Address() + break + } + + if newAccountAddress == flowsdk.EmptyAddress { + assert.Fail(t, "missing account created event") + } + + t.Logf("new account address: 0x%s", newAccountAddress.Hex()) + + account, err := adapter.GetAccount(context.Background(), newAccountAddress) + assert.NoError(t, err) + + accountKey = account.Keys[0] + + updateAccountCodeTx := templates.UpdateAccountContract( + newAccountAddress, + templates.Contract{ + Name: "HelloWorld", + Source: helloWorldContract, + }, + ) + updateAccountCodeTx. + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(newAccountAddress, accountKey.Index, accountKey.SequenceNumber). + SetPayer(newAccountAddress) + + err = updateAccountCodeTx.SignEnvelope(newAccountAddress, accountKey.Index, accountSigner) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *updateAccountCodeTx) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // call hello world code + + accountKey.SequenceNumber++ + + callHelloCode := []byte(fmt.Sprintf(callHelloTxTemplate, newAccountAddress.Hex())) + callHelloTx := flowsdk.NewTransaction(). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetScript(callHelloCode). + SetProposalKey(newAccountAddress, accountKey.Index, accountKey.SequenceNumber). + SetPayer(newAccountAddress) + + err = callHelloTx.SignEnvelope(newAccountAddress, accountKey.Index, accountSigner) + assert.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *callHelloTx) + assert.NoError(t, err) + + result, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) +} + +func TestInfiniteTransaction(t *testing.T) { + + t.Parallel() + + const limit = 18 + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + emulator.WithTransactionMaxGasLimit(limit), + ) + + const code = ` + access(all) fun test() { + test() + } + + transaction { + execute { + test() + } + } + ` + + // Create a new account + + accountKeys := test.AccountKeyGenerator() + accountKey, signer := accountKeys.NewWithSigner() + accountAddress, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKey}, nil) + assert.NoError(t, err) + + // Sign the transaction using the new account. + // Do not test using the service account, + // as the computation limit is disabled for it + + tx := flowsdk.NewTransaction(). + SetScript([]byte(code)). + SetComputeLimit(limit). + SetProposalKey(accountAddress, 0, 0). + SetPayer(accountAddress) + + err = tx.SignEnvelope(accountAddress, 0, signer) + assert.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Execute tx + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + require.True(t, fvmerrors.IsComputationLimitExceededError(result.Error)) +} + +func TestTransactionExecutionLimit(t *testing.T) { + + t.Parallel() + + const code = ` + transaction { + execute { + var s: Int256 = 1024102410241024 + var i: Int256 = 0 + var a: Int256 = 7 + var b: Int256 = 5 + var c: Int256 = 2 + + while i < 150000 { + s = s * a + s = s / b + s = s / c + i = i + 1 + } + } + } + ` + + t.Run("ExceedingLimit", func(t *testing.T) { + + t.Parallel() + + const limit = 2000 + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + emulator.WithTransactionMaxGasLimit(limit), + ) + + // Create a new account + + accountKeys := test.AccountKeyGenerator() + accountKey, signer := accountKeys.NewWithSigner() + accountAddress, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKey}, nil) + assert.NoError(t, err) + + // Sign the transaction using the new account. + // Do not test using the service account, + // as the computation limit is disabled for it + + tx := flowsdk.NewTransaction(). + SetScript([]byte(code)). + SetComputeLimit(limit). + SetProposalKey(accountAddress, 0, 0). + SetPayer(accountAddress) + + err = tx.SignEnvelope(accountAddress, 0, signer) + assert.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Execute tx + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + + require.True(t, fvmerrors.IsComputationLimitExceededError(result.Error)) + }) + + t.Run("SufficientLimit", func(t *testing.T) { + + t.Parallel() + + const limit = 19000 + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + emulator.WithTransactionMaxGasLimit(limit), + ) + + // Create a new account + + accountKeys := test.AccountKeyGenerator() + accountKey, signer := accountKeys.NewWithSigner() + accountAddress, err := adapter.CreateAccount(context.Background(), []*flowsdk.AccountKey{accountKey}, nil) + assert.NoError(t, err) + + // Sign the transaction using the new account. + // Do not test using the service account, + // as the computation limit is disabled for it + + tx := flowsdk.NewTransaction(). + SetScript([]byte(code)). + SetComputeLimit(limit). + SetProposalKey(accountAddress, 0, 0). + SetPayer(accountAddress) + + err = tx.SignEnvelope(accountAddress, 0, signer) + assert.NoError(t, err) + + // Submit tx + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + // Execute tx + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + assert.NoError(t, result.Error) + }) +} + +func TestSubmitTransactionWithCustomLogger(t *testing.T) { + + t.Parallel() + + var memlog bytes.Buffer + memlogWrite := io.Writer(&memlog) + logger := zerolog.New(memlogWrite).Level(zerolog.DebugLevel) + + b, adapter := setupTransactionTests( + t, + emulator.WithStorageLimitEnabled(false), + emulator.WithLogger(logger), + emulator.WithTransactionFeesEnabled(true), + ) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) + + tx1 := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx1.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + // Submit tx1 + err = adapter.SendTransaction(context.Background(), *tx1) + assert.NoError(t, err) + + // Execute tx1 + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + // tx1 status becomes TransactionStatusSealed + tx1Result, err := adapter.GetTransactionResult(context.Background(), tx1.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) + + var meter Meter + scanner := bufio.NewScanner(&memlog) + for scanner.Scan() { + txt := scanner.Text() + if strings.Contains(txt, "transaction execution data") { + err = json.Unmarshal([]byte(txt), &meter) + } + } + + assert.NoError(t, err) + assert.Greater(t, meter.LedgerInteractionUsed, 0) + assert.Greater(t, meter.ComputationUsed, 0) + assert.Greater(t, meter.MemoryEstimate, 0) + assert.Greater(t, len(meter.ComputationIntensities), 0) + assert.Greater(t, len(meter.MemoryIntensities), 0) + +} + +type Meter struct { + LedgerInteractionUsed int `json:"ledgerInteractionUsed"` + ComputationUsed int `json:"computationUsed"` + MemoryEstimate int `json:"memoryEstimate"` + ComputationIntensities MeteredComputationIntensities `json:"computationIntensities"` + MemoryIntensities MeteredMemoryIntensities `json:"memoryIntensities"` +} + +type MeteredComputationIntensities map[common.ComputationKind]uint + +type MeteredMemoryIntensities map[common.MemoryKind]uint + +func IncrementHelper( + t *testing.T, + b emulator.Emulator, + adapter *adapters.SDKAdapter, + counterAddress flowsdk.Address, + addTwoScript string, + expected int, + expectSetup bool, +) { + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + tx := flowsdk.NewTransaction(). + SetScript([]byte(addTwoScript)). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress). + AddAuthorizer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + result, err := adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusUnknown, result.Status) + require.Empty(t, result.Events) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, result.Status) + require.Empty(t, result.Events) + + _, err = b.ExecuteNextTransaction() + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusPending, result.Status) + require.Empty(t, result.Events) + + _, err = b.CommitBlock() + assert.NoError(t, err) + + result, err = adapter.GetTransactionResult(context.Background(), tx.ID()) + assert.NoError(t, err) + assert.Equal(t, flowsdk.TransactionStatusSealed, result.Status) + + var expectedEventIndex int + if expectSetup { + require.Len(t, result.Events, 3) + + event1 := result.Events[0] + assert.Equal(t, tx.ID(), event1.TransactionID) + assert.Equal(t, "flow.StorageCapabilityControllerIssued", event1.Type) + assert.Equal(t, 0, event1.EventIndex) + + event2 := result.Events[1] + assert.Equal(t, tx.ID(), event2.TransactionID) + assert.Equal(t, "flow.CapabilityPublished", event2.Type) + assert.Equal(t, 1, event2.EventIndex) + + expectedEventIndex = 2 + } else { + require.Len(t, result.Events, 1) + expectedEventIndex = 0 + } + incrementedEvent := result.Events[expectedEventIndex] + + addr, _ := common.BytesToAddress(counterAddress.Bytes()) + location := common.AddressLocation{ + Address: addr, + Name: "Counting", + } + assert.Equal(t, tx.ID(), incrementedEvent.TransactionID) + assert.Equal(t, + string(location.TypeID(nil, "Counting.CountIncremented")), + incrementedEvent.Type, + ) + assert.Equal(t, expectedEventIndex, incrementedEvent.EventIndex) + fields := cadence.FieldsMappedByName(incrementedEvent.Value) + assert.Len(t, fields, 1) + assert.Equal(t, + cadence.NewInt(expected), + fields["count"], + ) +} + +// TestTransactionWithCadenceRandom checks Cadence's random function works +// within a transaction +func TestTransactionWithCadenceRandom(t *testing.T) { + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + code := ` + transaction { + prepare() { + assert(revertibleRandom() >= 0) + } + } + ` + callRandomTx := flowsdk.NewTransaction(). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetScript([]byte(code)). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = callRandomTx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *callRandomTx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + assert.NoError(t, err) + AssertTransactionSucceeded(t, result) + + _, err = b.CommitBlock() + assert.NoError(t, err) +} + +func TestEVMTransaction(t *testing.T) { + serviceAddr := flowgo.Emulator.Chain().ServiceAddress() + code := []byte(fmt.Sprintf( + ` + import EVM from %s + + transaction(bytes: [UInt8; 20]) { + execute { + let addr = EVM.EVMAddress(bytes: bytes) + log(addr) + } + } + `, + serviceAddr.HexWithPrefix(), + )) + + b, adapter := setupTransactionTests(t) + serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) + + // generate random address + genArr := make([]cadence.Value, 20) + for i := range genArr { + genArr[i] = cadence.UInt8(i) + } + addressBytesArray := cadence.NewArray(genArr).WithType(stdlib.EVMAddressBytesCadenceType) + + tx := flowsdk.NewTransaction(). + SetScript(code). + SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). + SetProposalKey(serviceAccountAddress, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(serviceAccountAddress) + + err := tx.AddArgument(addressBytesArray) + assert.NoError(t, err) + + signer, err := b.ServiceKey().Signer() + require.NoError(t, err) + + err = tx.SignEnvelope(serviceAccountAddress, b.ServiceKey().Index, signer) + require.NoError(t, err) + + err = adapter.SendTransaction(context.Background(), *tx) + assert.NoError(t, err) + + result, err := b.ExecuteNextTransaction() + require.NoError(t, err) + AssertTransactionSucceeded(t, result) + + require.Len(t, result.Logs, 1) + require.Equal(t, result.Logs[0], fmt.Sprintf("A.%s.EVM.EVMAddress(bytes: %s)", serviceAddr, addressBytesArray.String())) +} diff --git a/integration/emulator/tests/vm_test.go b/integration/emulator/tests/vm_test.go new file mode 100644 index 00000000000..2d94991ff64 --- /dev/null +++ b/integration/emulator/tests/vm_test.go @@ -0,0 +1,81 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests_test + +import ( + "github.com/onflow/flow-go/integration/emulator" + "testing" + + "github.com/onflow/flow-go-sdk/test" + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/stretchr/testify/assert" + + flowgo "github.com/onflow/flow-go/model/flow" +) + +func TestVm(t *testing.T) { + + t.Parallel() + + test := func(eventEncodingVersion entities.EventEncodingVersion) { + t.Run(eventEncodingVersion.String(), func(t *testing.T) { + t.Parallel() + t.Run("should be able to convert", func(t *testing.T) { + + t.Parallel() + + idGenerator := test.IdentifierGenerator() + + eventGenerator := test.EventGenerator(eventEncodingVersion) + event1, err := emulator.SDKEventToFlow(eventGenerator.New()) + assert.NoError(t, err) + + event2, err := emulator.SDKEventToFlow(eventGenerator.New()) + assert.NoError(t, err) + + txnId := flowgo.Identifier(idGenerator.New()) + output := fvm.ProcedureOutput{ + Logs: []string{"TestLog1", "TestLog2"}, + Events: []flowgo.Event{event1, event2}, + ComputationUsed: 5, + MemoryEstimate: 1211, + Err: nil, + } + + tr, err := emulator.VMTransactionResultToEmulator(txnId, output) + assert.NoError(t, err) + + assert.Equal(t, txnId, flowgo.Identifier(tr.TransactionID)) + assert.Equal(t, output.Logs, tr.Logs) + + flowEvents, err := emulator.FlowEventsToSDK(output.Events) + assert.NoError(t, err) + assert.Equal(t, flowEvents, tr.Events) + + assert.Equal(t, output.ComputationUsed, tr.ComputationUsed) + assert.Equal(t, output.MemoryEstimate, tr.MemoryEstimate) + assert.Equal(t, output.Err, tr.Error) + }) + }) + } + + test(entities.EventEncodingVersion_JSON_CDC_V0) + test(entities.EventEncodingVersion_CCF_V0) +} diff --git a/integration/emulator/utils/unittest/fixtures.go b/integration/emulator/utils/unittest/fixtures.go new file mode 100644 index 00000000000..3ae77772759 --- /dev/null +++ b/integration/emulator/utils/unittest/fixtures.go @@ -0,0 +1,59 @@ +/* + * Flow Emulator + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package unittest + +import ( + "github.com/onflow/flow-go-sdk/test" + "github.com/onflow/flow-go/integration/emulator" + flowgo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" +) + +func TransactionFixture() flowgo.TransactionBody { + return *emulator.SDKTransactionToFlow(*test.TransactionGenerator().New()) +} + +func StorableTransactionResultFixture(eventEncodingVersion entities.EventEncodingVersion) emulator.StorableTransactionResult { + events := test.EventGenerator(eventEncodingVersion) + + eventA, _ := emulator.SDKEventToFlow(events.New()) + eventB, _ := emulator.SDKEventToFlow(events.New()) + + return emulator.StorableTransactionResult{ + ErrorCode: 42, + ErrorMessage: "foo", + Logs: []string{"a", "b", "c"}, + Events: []flowgo.Event{ + eventA, + eventB, + }, + } +} + +func FullCollectionFixture(n int) flowgo.Collection { + transactions := make([]*flowgo.TransactionBody, n) + for i := 0; i < n; i++ { + tx := TransactionFixture() + transactions[i] = &tx + } + + return flowgo.Collection{ + Transactions: transactions, + } +} diff --git a/integration/epochs/cluster_epoch_test.go b/integration/epochs/cluster_epoch_test.go index 953a684e3e7..50d4ca5f48d 100644 --- a/integration/epochs/cluster_epoch_test.go +++ b/integration/epochs/cluster_epoch_test.go @@ -13,7 +13,7 @@ import ( "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" - emulator "github.com/onflow/flow-emulator/emulator" + emulator "github.com/onflow/flow-go/integration/emulator" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" @@ -31,10 +31,10 @@ import ( type Suite struct { suite.Suite - env templates.Environment - blockchain *emulator.Blockchain - emulatorClient *utils.EmulatorClient - + env templates.Environment + blockchain *emulator.Blockchain + emulatorClient *utils.EmulatorClient + serviceAccountAddress sdk.Address // Quorum Certificate deployed account and address qcAddress sdk.Address qcAccountKey *sdk.AccountKey @@ -51,7 +51,7 @@ func (s *Suite) SetupTest() { ) s.Require().NoError(err) s.emulatorClient = utils.NewEmulatorClient(s.blockchain) - + s.serviceAccountAddress = sdk.Address(s.blockchain.ServiceKey().Address) // deploy epoch qc contract s.deployEpochQCContract() } @@ -104,16 +104,16 @@ func (s *Suite) PublishVoter() { publishVoterTx := sdk.NewTransaction(). SetScript(templates.GeneratePublishVoterScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.qcAddress) signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) s.SignAndSubmit(publishVoterTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.qcAddress}, + []sdk.Address{s.serviceAccountAddress, s.qcAddress}, []sdkcrypto.Signer{signer, s.qcSigner}) } @@ -124,9 +124,9 @@ func (s *Suite) StartVoting(clustering flow.ClusterList, clusterCount, nodesPerC startVotingTx := sdk.NewTransaction(). SetScript(templates.GenerateStartVotingScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.qcAddress) clusterIndices := make([]cadence.Value, 0, clusterCount) @@ -170,7 +170,7 @@ func (s *Suite) StartVoting(clustering flow.ClusterList, clusterCount, nodesPerC require.NoError(s.T(), err) s.SignAndSubmit(startVotingTx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.qcAddress}, + []sdk.Address{s.serviceAccountAddress, s.qcAddress}, []sdkcrypto.Signer{signer, s.qcSigner}) } @@ -180,9 +180,9 @@ func (s *Suite) CreateVoterResource(address sdk.Address, nodeID flow.Identifier, registerVoterTx := sdk.NewTransaction(). SetScript(templates.GenerateCreateVoterScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(address) err := registerVoterTx.AddArgument(cadence.NewAddress(s.qcAddress)) @@ -202,7 +202,7 @@ func (s *Suite) CreateVoterResource(address sdk.Address, nodeID flow.Identifier, require.NoError(s.T(), err) s.SignAndSubmit(registerVoterTx, - []sdk.Address{s.blockchain.ServiceKey().Address, address}, + []sdk.Address{s.serviceAccountAddress, address}, []sdkcrypto.Signer{signer, nodeSigner}) } @@ -210,16 +210,16 @@ func (s *Suite) StopVoting() { tx := sdk.NewTransaction(). SetScript(templates.GenerateStopVotingScript(s.env)). SetComputeLimit(9999). - SetProposalKey(s.blockchain.ServiceKey().Address, + SetProposalKey(s.serviceAccountAddress, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). - SetPayer(s.blockchain.ServiceKey().Address). + SetPayer(s.serviceAccountAddress). AddAuthorizer(s.qcAddress) signer, err := s.blockchain.ServiceKey().Signer() require.NoError(s.T(), err) s.SignAndSubmit(tx, - []sdk.Address{s.blockchain.ServiceKey().Address, s.qcAddress}, + []sdk.Address{s.serviceAccountAddress, s.qcAddress}, []sdkcrypto.Signer{signer, s.qcSigner}) } diff --git a/integration/go.mod b/integration/go.mod index a81636cd243..35ad728d5be 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -20,11 +20,11 @@ require ( github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ds-pebble v0.3.1-0.20240828032824-d745b9d3200b github.com/libp2p/go-libp2p v0.32.2 + github.com/logrusorgru/aurora v2.0.3+incompatible github.com/onflow/cadence v1.1.0 github.com/onflow/crypto v0.25.2 github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 - github.com/onflow/flow-emulator v1.0.2-0.20241018193523-2601797fe0f2 github.com/onflow/flow-go v0.38.0-preview.0.0.20241018193026-4b778232480b github.com/onflow/flow-go-sdk v1.1.0 github.com/onflow/flow-go/insecure v0.0.0-00010101000000-000000000000 @@ -33,10 +33,12 @@ require ( github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.46.0 + github.com/psiemens/graceland v1.0.0 github.com/rs/zerolog v1.29.0 github.com/stretchr/testify v1.9.0 go.einride.tech/pid v0.1.0 go.uber.org/atomic v1.11.0 + go.uber.org/mock v0.4.0 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a golang.org/x/sync v0.8.0 google.golang.org/grpc v1.63.2 @@ -104,7 +106,6 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect @@ -129,7 +130,6 @@ require ( github.com/gammazero/workerpool v1.1.2 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-kit/kit v0.12.0 // indirect @@ -141,7 +141,6 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.1 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect @@ -215,7 +214,6 @@ require ( github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect - github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora/v4 v4.0.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -249,7 +247,6 @@ require ( github.com/onflow/flow-ft/lib/go/templates v1.0.0 // indirect github.com/onflow/flow-nft/lib/go/contracts v1.2.1 // indirect github.com/onflow/flow-nft/lib/go/templates v1.2.0 // indirect - github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 // indirect github.com/onflow/sdks v0.6.0-preview.1 // indirect github.com/onflow/wal v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect @@ -267,14 +264,12 @@ require ( github.com/polydawn/refmt v0.89.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/psiemens/graceland v1.0.0 // indirect github.com/psiemens/sconfig v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/quic-go v0.40.1 // indirect github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rootless-containers/rootlesskit v1.1.1 // indirect @@ -327,7 +322,6 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/fx v1.20.1 // indirect - go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.26.0 // indirect @@ -350,10 +344,6 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect lukechampine.com/blake3 v1.3.0 // indirect - modernc.org/libc v1.37.6 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/integration/go.sum b/integration/go.sum index e68cc3cf061..c3af92c64e6 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -1296,8 +1296,6 @@ github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMa github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= @@ -1422,8 +1420,6 @@ github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgoo github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= -github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= @@ -1492,8 +1488,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -2153,8 +2147,6 @@ github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 h1:q9tXLIALwQ76bO4 github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1/go.mod h1:u/mkP/B+PbV33tEG3qfkhhBlydSvAKxfLZSfB4lsJHg= github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 h1:FfhMBAb78p6VAWkJ+iqdKLErGQVQgxk5w6DP5ZruWX8= github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1/go.mod h1:NgbMOYnMh0GN48VsNKZuiwK7uyk38Wyo8jN9+C9QE30= -github.com/onflow/flow-emulator v1.0.2-0.20241018193523-2601797fe0f2 h1:bCp07I6BKfDGu8CFiWS0OY7FiqELAZNbeZ2M70o1gXY= -github.com/onflow/flow-emulator v1.0.2-0.20241018193523-2601797fe0f2/go.mod h1:QMKsUPYQh4PPvSF3ryYzkbL6HzWAhoRr55FtbKCC5Jc= github.com/onflow/flow-ft/lib/go/contracts v1.0.0 h1:mToacZ5NWqtlWwk/7RgIl/jeKB/Sy/tIXdw90yKHcV0= github.com/onflow/flow-ft/lib/go/contracts v1.0.0/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.0 h1:6cMS/lUJJ17HjKBfMO/eh0GGvnpElPgBXx7h5aoWJhs= @@ -2171,8 +2163,6 @@ github.com/onflow/flow/protobuf/go/flow v0.4.7 h1:iP6DFx4wZ3ETORsyeqzHu7neFT3d1C github.com/onflow/flow/protobuf/go/flow v0.4.7/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-ethereum v1.14.7 h1:gg3awYqI02e3AypRdpJKEvNTJ6kz/OhAqRti0h54Wlc= github.com/onflow/go-ethereum v1.14.7/go.mod h1:zV14QLrXyYu5ucvcwHUA0r6UaqveqbXaehAVQJlSW+I= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0/go.mod h1:kMeq9zUwCrgrSojEbTUTTJpZ4WwacVm2pA7LVFr+glk= github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= github.com/onflow/sdks v0.6.0-preview.1 h1:mb/cUezuqWEP1gFZNAgUI4boBltudv4nlfxke1KBp9k= github.com/onflow/sdks v0.6.0-preview.1/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= @@ -2302,7 +2292,6 @@ github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1ab github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -3627,27 +3616,19 @@ modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= -modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= -modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= diff --git a/integration/utils/emulator_client.go b/integration/utils/emulator_client.go index 8d42e1388fd..7a981cf126d 100644 --- a/integration/utils/emulator_client.go +++ b/integration/utils/emulator_client.go @@ -6,8 +6,8 @@ import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" - "github.com/onflow/flow-emulator/adapters" - emulator "github.com/onflow/flow-emulator/emulator" + emulator "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/adapters" "github.com/rs/zerolog" sdk "github.com/onflow/flow-go-sdk" diff --git a/storage/operation/reads_bench_test.txt b/storage/operation/reads_bench_test.txt new file mode 100644 index 00000000000..7b490254e73 --- /dev/null +++ b/storage/operation/reads_bench_test.txt @@ -0,0 +1,63 @@ +package operation_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/storage" + "github.com/onflow/flow-go/storage/operation" + "github.com/onflow/flow-go/storage/operation/dbtest" +) + +func BenchmarkRetrieve(t *testing.B) { + dbtest.BenchWithStorages(t, func(t *testing.B, r storage.Reader, withWriter dbtest.WithWriter) { + e := Entity{ID: 1337} + require.NoError(t, withWriter(operation.Upsert(e.Key(), e))) + + t.ResetTimer() + + for i := 0; i < t.N; i++ { + var readBack Entity + require.NoError(t, operation.Retrieve(e.Key(), &readBack)(r)) + } + }) +} + +func BenchmarkNonExist(t *testing.B) { + dbtest.BenchWithStorages(t, func(t *testing.B, r storage.Reader, withWriter dbtest.WithWriter) { + for i := 0; i < t.N; i++ { + e := Entity{ID: uint64(i)} + require.NoError(t, withWriter(operation.Upsert(e.Key(), e))) + } + + t.ResetTimer() + nonExist := Entity{ID: uint64(t.N + 1)} + for i := 0; i < t.N; i++ { + var exists bool + require.NoError(t, operation.Exists(nonExist.Key(), &exists)(r)) + } + }) +} + +func BenchmarkIterate(t *testing.B) { + dbtest.BenchWithStorages(t, func(t *testing.B, r storage.Reader, withWriter dbtest.WithWriter) { + prefix1 := []byte("prefix-1") + prefix2 := []byte("prefix-2") + for i := 0; i < t.N; i++ { + e := Entity{ID: uint64(i)} + key1 := append(prefix1, e.Key()...) + key2 := append(prefix2, e.Key()...) + + require.NoError(t, withWriter(operation.Upsert(key1, e))) + require.NoError(t, withWriter(operation.Upsert(key2, e))) + } + + t.ResetTimer() + var found [][]byte + require.NoError(t, operation.IterateKeysInPrefixRange(prefix1, prefix2, func(key []byte) error { + found = append(found, key) + return nil + })(r), "should iterate forward without error") + }) +} From abe2a25e5b4f4acea34cdbb2d17967670b3e9b22 Mon Sep 17 00:00:00 2001 From: Deniz Mert Edincik Date: Sat, 19 Oct 2024 18:26:17 +0200 Subject: [PATCH 2/9] wip --- integration/emulator/adapters/access.go | 660 --------------- .../emulator/adapters/tests/access_test.go | 785 ------------------ integration/emulator/blockchain.go | 11 +- integration/emulator/log.go | 22 - integration/emulator/logging.go | 93 --- integration/emulator/{adapters => }/sdk.go | 54 +- integration/emulator/tests/accounts_test.go | 5 +- integration/emulator/tests/block_info_test.go | 3 +- integration/emulator/tests/block_test.go | 5 +- integration/emulator/tests/blockchain_test.go | 5 +- integration/emulator/tests/collection_test.go | 3 +- integration/emulator/tests/events_test.go | 3 +- .../emulator/tests/pendingBlock_test.go | 7 +- integration/emulator/tests/script_test.go | 3 +- .../emulator/tests/transaction_test.go | 7 +- integration/utils/emulator_client.go | 5 +- 16 files changed, 47 insertions(+), 1624 deletions(-) delete mode 100644 integration/emulator/adapters/access.go delete mode 100644 integration/emulator/adapters/tests/access_test.go delete mode 100644 integration/emulator/log.go delete mode 100644 integration/emulator/logging.go rename integration/emulator/{adapters => }/sdk.go (88%) diff --git a/integration/emulator/adapters/access.go b/integration/emulator/adapters/access.go deleted file mode 100644 index e706968a2b4..00000000000 --- a/integration/emulator/adapters/access.go +++ /dev/null @@ -1,660 +0,0 @@ -/* - * Flow Emulator - * - * Copyright Flow Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package adapters - -import ( - "context" - jsoncdc "github.com/onflow/cadence/encoding/json" - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/subscription" - "github.com/onflow/flow-go/engine/common/rpc/convert" - "github.com/onflow/flow-go/integration/emulator" - flowgo "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/rs/zerolog" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -var _ access.API = &AccessAdapter{} - -// AccessAdapter wraps the emulator adapters to be compatible with access.API. -type AccessAdapter struct { - logger *zerolog.Logger - emulator emulator.Emulator -} - -// NewAccessAdapter returns a new AccessAdapter. -func NewAccessAdapter(logger *zerolog.Logger, emulator emulator.Emulator) *AccessAdapter { - return &AccessAdapter{ - logger: logger, - emulator: emulator, - } -} - -func convertError(err error, defaultStatusCode codes.Code) error { - if err != nil { - switch err.(type) { - case emulator.InvalidArgumentError: - return status.Error(codes.InvalidArgument, err.Error()) - case emulator.NotFoundError: - return status.Error(codes.NotFound, err.Error()) - default: - return status.Error(defaultStatusCode, err.Error()) - } - } - return nil -} - -func (a *AccessAdapter) Ping(_ context.Context) error { - return convertError(a.emulator.Ping(), codes.Internal) -} - -func (a *AccessAdapter) GetNetworkParameters(_ context.Context) access.NetworkParameters { - return a.emulator.GetNetworkParameters() -} - -func (a *AccessAdapter) GetLatestBlockHeader(_ context.Context, _ bool) (*flowgo.Header, flowgo.BlockStatus, error) { - block, err := a.emulator.GetLatestBlock() - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetLatestBlockHeader called") - - return block.Header, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetBlockHeaderByHeight(_ context.Context, height uint64) (*flowgo.Header, flowgo.BlockStatus, error) { - block, err := a.emulator.GetBlockByHeight(height) - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetBlockHeaderByHeight called") - - return block.Header, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetBlockHeaderByID(_ context.Context, id flowgo.Identifier) (*flowgo.Header, flowgo.BlockStatus, error) { - block, err := a.emulator.GetBlockByID(id) - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetBlockHeaderByID called") - - return block.Header, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetLatestBlock(_ context.Context, _ bool) (*flowgo.Block, flowgo.BlockStatus, error) { - block, err := a.emulator.GetLatestBlock() - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetLatestBlock called") - - return block, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetBlockByHeight(_ context.Context, height uint64) (*flowgo.Block, flowgo.BlockStatus, error) { - block, err := a.emulator.GetBlockByHeight(height) - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetBlockByHeight called") - - return block, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetBlockByID(_ context.Context, id flowgo.Identifier) (*flowgo.Block, flowgo.BlockStatus, error) { - block, err := a.emulator.GetBlockByID(id) - if err != nil { - return nil, flowgo.BlockStatusUnknown, convertError(err, codes.Internal) - } - - a.logger.Debug().Fields(map[string]any{ - "blockHeight": block.Header.Height, - "blockID": block.ID().String(), - }).Msg("🎁 GetBlockByID called") - - return block, flowgo.BlockStatusSealed, nil -} - -func (a *AccessAdapter) GetCollectionByID(_ context.Context, id flowgo.Identifier) (*flowgo.LightCollection, error) { - collection, err := a.emulator.GetCollectionByID(id) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Str("colID", id.String()). - Msg("📚 GetCollectionByID called") - - return collection, nil -} - -func (a *AccessAdapter) GetFullCollectionByID(_ context.Context, id flowgo.Identifier) (*flowgo.Collection, error) { - collection, err := a.emulator.GetFullCollectionByID(id) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Str("colID", id.String()). - Msg("📚 GetFullCollectionByID called") - - return collection, nil - -} - -func (a *AccessAdapter) GetTransaction(_ context.Context, id flowgo.Identifier) (*flowgo.TransactionBody, error) { - tx, err := a.emulator.GetTransaction(id) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Str("txID", id.String()). - Msg("💵 GetTransaction called") - - return tx, nil -} - -func (a *AccessAdapter) GetTransactionResult( - _ context.Context, - id flowgo.Identifier, - _ flowgo.Identifier, - _ flowgo.Identifier, - requiredEventEncodingVersion entities.EventEncodingVersion, -) ( - *access.TransactionResult, - error, -) { - result, err := a.emulator.GetTransactionResult(id) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - // Convert CCF events to JSON events, else return CCF encoded version - if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { - result.Events, err = ConvertCCFEventsToJsonEvents(result.Events) - if err != nil { - return nil, convertError(err, codes.Internal) - } - } - a.logger.Debug(). - Str("txID", id.String()). - Msg("📝 GetTransactionResult called") - - return result, nil -} - -func (a *AccessAdapter) GetAccount(_ context.Context, address flowgo.Address) (*flowgo.Account, error) { - account, err := a.emulator.GetAccount(address) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Msg("👤 GetAccount called") - - return account, nil -} - -func (a *AccessAdapter) GetAccountAtLatestBlock(ctx context.Context, address flowgo.Address) (*flowgo.Account, error) { - account, err := a.GetAccount(ctx, address) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Msg("👤 GetAccountAtLatestBlock called") - - return account, nil -} - -func (a *AccessAdapter) GetAccountAtBlockHeight( - _ context.Context, - address flowgo.Address, - height uint64, -) (*flowgo.Account, error) { - - a.logger.Debug(). - Stringer("address", address). - Uint64("height", height). - Msg("👤 GetAccountAtBlockHeight called") - - account, err := a.emulator.GetAccountAtBlockHeight(address, height) - if err != nil { - return nil, convertError(err, codes.Internal) - } - return account, nil -} - -func (a *AccessAdapter) ConvertScriptResult(result *emulator.ScriptResult, err error) ([]byte, error) { - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - if !result.Succeeded() { - return nil, status.Error(codes.InvalidArgument, result.Error.Error()) - } - - valueBytes, err := jsoncdc.Encode(result.Value) - if err != nil { - return nil, convertError(err, codes.InvalidArgument) - } - - return valueBytes, nil -} - -func (a *AccessAdapter) ExecuteScriptAtLatestBlock( - _ context.Context, - script []byte, - arguments [][]byte, -) ([]byte, error) { - latestBlock, err := a.emulator.GetLatestBlock() - if err != nil { - return nil, err - } - a.logger.Debug(). - Uint64("blockHeight", latestBlock.Header.Height). - Msg("👤 ExecuteScriptAtLatestBlock called") - - result, err := a.emulator.ExecuteScript(script, arguments) - if err == nil { - emulator.PrintScriptResult(a.logger, result) - } - return a.ConvertScriptResult(result, err) -} - -func (a *AccessAdapter) ExecuteScriptAtBlockHeight( - _ context.Context, - blockHeight uint64, - script []byte, - arguments [][]byte, -) ([]byte, error) { - - a.logger.Debug(). - Uint64("blockHeight", blockHeight). - Msg("👤 ExecuteScriptAtBlockHeight called") - - result, err := a.emulator.ExecuteScriptAtBlockHeight(script, arguments, blockHeight) - if err == nil { - emulator.PrintScriptResult(a.logger, result) - } - return a.ConvertScriptResult(result, err) -} - -func (a *AccessAdapter) ExecuteScriptAtBlockID( - _ context.Context, - blockID flowgo.Identifier, - script []byte, - arguments [][]byte, -) ([]byte, error) { - - a.logger.Debug(). - Stringer("blockID", blockID). - Msg("👤 ExecuteScriptAtBlockID called") - - result, err := a.emulator.ExecuteScriptAtBlockID(script, arguments, blockID) - if err == nil { - emulator.PrintScriptResult(a.logger, result) - } - return a.ConvertScriptResult(result, err) -} - -func (a *AccessAdapter) GetEventsForHeightRange( - _ context.Context, - eventType string, - startHeight, endHeight uint64, - requiredEventEncodingVersion entities.EventEncodingVersion, -) ([]flowgo.BlockEvents, error) { - events, err := a.emulator.GetEventsForHeightRange(eventType, startHeight, endHeight) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - eventCount := 0 - - // Convert CCF events to JSON events, else return CCF encoded version - if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { - for i := range events { - events[i].Events, err = ConvertCCFEventsToJsonEvents(events[i].Events) - eventCount = eventCount + len(events[i].Events) - if err != nil { - return nil, convertError(err, codes.Internal) - } - } - } - - a.logger.Debug().Fields(map[string]any{ - "eventType": eventType, - "startHeight": startHeight, - "endHeight": endHeight, - "eventCount": eventCount, - }).Msg("🎁 GetEventsForHeightRange called") - - return events, nil -} - -func (a *AccessAdapter) GetEventsForBlockIDs( - _ context.Context, - eventType string, - blockIDs []flowgo.Identifier, - requiredEventEncodingVersion entities.EventEncodingVersion, -) ([]flowgo.BlockEvents, error) { - events, err := a.emulator.GetEventsForBlockIDs(eventType, blockIDs) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - eventCount := 0 - - // Convert CCF events to JSON events, else return CCF encoded version - if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { - for i := range events { - events[i].Events, err = ConvertCCFEventsToJsonEvents(events[i].Events) - eventCount = eventCount + len(events[i].Events) - if err != nil { - return nil, convertError(err, codes.Internal) - } - } - } - - a.logger.Debug().Fields(map[string]any{ - "eventType": eventType, - "eventCount": eventCount, - }).Msg("🎁 GetEventsForBlockIDs called") - - return events, nil -} - -func (a *AccessAdapter) GetLatestProtocolStateSnapshot(_ context.Context) ([]byte, error) { - return nil, nil -} - -func (a *AccessAdapter) GetProtocolStateSnapshotByBlockID(_ context.Context, _ flowgo.Identifier) ([]byte, error) { - return nil, nil -} - -func (a *AccessAdapter) GetProtocolStateSnapshotByHeight(_ context.Context, _ uint64) ([]byte, error) { - return nil, nil -} - -func (a *AccessAdapter) GetExecutionResultForBlockID(_ context.Context, _ flowgo.Identifier) (*flowgo.ExecutionResult, error) { - return nil, nil -} - -func (a *AccessAdapter) GetExecutionResultByID(_ context.Context, _ flowgo.Identifier) (*flowgo.ExecutionResult, error) { - return nil, nil -} - -func (a *AccessAdapter) GetSystemTransaction(_ context.Context, _ flowgo.Identifier) (*flowgo.TransactionBody, error) { - return nil, nil -} - -func (a *AccessAdapter) GetSystemTransactionResult(_ context.Context, _ flowgo.Identifier, _ entities.EventEncodingVersion) (*access.TransactionResult, error) { - return nil, nil -} - -func (a *AccessAdapter) GetAccountBalanceAtLatestBlock(_ context.Context, address flowgo.Address) (uint64, error) { - - account, err := a.emulator.GetAccount(address) - if err != nil { - return 0, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Msg("👤 GetAccountBalanceAtLatestBlock called") - - return account.Balance, nil -} - -func (a *AccessAdapter) GetAccountBalanceAtBlockHeight(ctx context.Context, address flowgo.Address, height uint64) (uint64, error) { - account, err := a.emulator.GetAccountAtBlockHeight(address, height) - if err != nil { - return 0, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Uint64("height", height). - Msg("👤 GetAccountBalanceAtBlockHeight called") - - return account.Balance, nil -} - -func (a *AccessAdapter) GetAccountKeyAtLatestBlock(_ context.Context, address flowgo.Address, keyIndex uint32) (*flowgo.AccountPublicKey, error) { - account, err := a.emulator.GetAccount(address) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - for _, key := range account.Keys { - if key.Index == keyIndex { - return &key, nil - } - } - - a.logger.Debug(). - Stringer("address", address). - Uint32("keyIndex", keyIndex). - Msg("👤 GetAccountKeyAtLatestBlock called") - - return nil, status.Errorf(codes.NotFound, "failed to get account key by index: %d", keyIndex) -} - -func (a *AccessAdapter) GetAccountKeyAtBlockHeight(_ context.Context, address flowgo.Address, keyIndex uint32, height uint64) (*flowgo.AccountPublicKey, error) { - account, err := a.emulator.GetAccountAtBlockHeight(address, height) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - for _, key := range account.Keys { - if key.Index == keyIndex { - return &key, nil - } - } - - a.logger.Debug(). - Stringer("address", address). - Uint32("keyIndex", keyIndex). - Uint64("height", height). - Msg("👤 GetAccountKeyAtBlockHeight called") - - return nil, status.Errorf(codes.NotFound, "failed to get account key by index: %d", keyIndex) -} - -func (a *AccessAdapter) GetAccountKeysAtLatestBlock(_ context.Context, address flowgo.Address) ([]flowgo.AccountPublicKey, error) { - account, err := a.emulator.GetAccount(address) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Msg("👤 GetAccountKeysAtLatestBlock called") - - return account.Keys, nil -} - -func (a *AccessAdapter) GetAccountKeysAtBlockHeight(_ context.Context, address flowgo.Address, height uint64) ([]flowgo.AccountPublicKey, error) { - account, err := a.emulator.GetAccountAtBlockHeight(address, height) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - a.logger.Debug(). - Stringer("address", address). - Uint64("height", height). - Msg("👤 GetAccountKeysAtBlockHeight called") - - return account.Keys, nil -} - -func (a *AccessAdapter) GetTransactionResultByIndex( - _ context.Context, - blockID flowgo.Identifier, - index uint32, - requiredEventEncodingVersion entities.EventEncodingVersion, -) (*access.TransactionResult, error) { - results, err := a.emulator.GetTransactionResultsByBlockID(blockID) - if err != nil { - return nil, convertError(err, codes.Internal) - } - if len(results) <= int(index) { - return nil, convertError(&emulator.TransactionNotFoundError{ID: flowgo.Identifier{}}, codes.Internal) - } - - // Convert CCF events to JSON events, else return CCF encoded version - if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { - for i := range results { - results[i].Events, err = ConvertCCFEventsToJsonEvents(results[i].Events) - if err != nil { - return nil, convertError(err, codes.Internal) - } - } - } - - return results[index], nil -} - -func (a *AccessAdapter) GetTransactionsByBlockID(_ context.Context, blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) { - result, err := a.emulator.GetTransactionsByBlockID(blockID) - if err != nil { - return nil, convertError(err, codes.Internal) - } - return result, nil -} - -func (a *AccessAdapter) GetTransactionResultsByBlockID( - _ context.Context, - blockID flowgo.Identifier, - requiredEventEncodingVersion entities.EventEncodingVersion, -) ([]*access.TransactionResult, error) { - result, err := a.emulator.GetTransactionResultsByBlockID(blockID) - if err != nil { - return nil, convertError(err, codes.Internal) - } - - // Convert CCF events to JSON events, else return CCF encoded version - if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { - for i := range result { - result[i].Events, err = ConvertCCFEventsToJsonEvents(result[i].Events) - if err != nil { - return nil, convertError(err, codes.Internal) - } - } - } - - return result, nil -} - -func (a *AccessAdapter) SendTransaction(_ context.Context, tx *flowgo.TransactionBody) error { - a.logger.Debug(). - Str("txID", tx.ID().String()). - Msg(`✉️ Transaction submitted`) - - return convertError(a.emulator.SendTransaction(tx), codes.Internal) -} - -func (a *AccessAdapter) GetNodeVersionInfo( - _ context.Context, -) ( - *access.NodeVersionInfo, - error, -) { - return &access.NodeVersionInfo{}, nil -} - -func (a *AccessAdapter) SubscribeBlocksFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlocksFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlocksFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockHeadersFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockHeadersFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockHeadersFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockDigestsFromStartBlockID(ctx context.Context, startBlockID flowgo.Identifier, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockDigestsFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeBlockDigestsFromLatest(ctx context.Context, blockStatus flowgo.BlockStatus) subscription.Subscription { - return nil -} - -func (a *AccessAdapter) SubscribeTransactionStatuses(ctx context.Context, tx *flowgo.TransactionBody, _ entities.EventEncodingVersion) subscription.Subscription { - return nil -} - -func ConvertCCFEventsToJsonEvents(events []flowgo.Event) ([]flowgo.Event, error) { - converted := make([]flowgo.Event, 0, len(events)) - - for _, event := range events { - evt, err := convert.CcfEventToJsonEvent(event) - if err != nil { - return nil, err - } - converted = append(converted, *evt) - } - - return converted, nil -} diff --git a/integration/emulator/adapters/tests/access_test.go b/integration/emulator/adapters/tests/access_test.go deleted file mode 100644 index da521706537..00000000000 --- a/integration/emulator/adapters/tests/access_test.go +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Flow Emulator - * - * Copyright Flow Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package tests - -import ( - "context" - "fmt" - "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" - "github.com/onflow/flow-go/integration/emulator/mocks" - "testing" - - "github.com/onflow/cadence" - "github.com/onflow/cadence/encoding/ccf" - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/common/rpc/convert" - flowgo "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func accessTest(f func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator)) func(t *testing.T) { - return func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - emu := mocks.NewMockEmulator(mockCtrl) - logger := zerolog.Nop() - back := adapters.NewAccessAdapter(&logger, emu) - - f(t, back, emu) - } -} - -func TestAccess(t *testing.T) { - - t.Run("Ping", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - emu.EXPECT(). - Ping(). - Times(1) - - err := adapter.Ping(context.Background()) - assert.NoError(t, err) - })) - - t.Run("GetNetworkParameters", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - expected := access.NetworkParameters{ - ChainID: flowgo.MonotonicEmulator, - } - - emu.EXPECT(). - GetNetworkParameters(). - Return(expected). - Times(1) - - result := adapter.GetNetworkParameters(context.Background()) - assert.Equal(t, expected, result) - - })) - - t.Run("GetLatestBlockHeader", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetLatestBlock(). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetLatestBlockHeader(context.Background(), true) - assert.Equal(t, expected.Header, result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetLatestBlock(). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetLatestBlockHeader(context.Background(), true) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetBlockHeaderByHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetBlockByHeight(uint64(42)). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetBlockHeaderByHeight(context.Background(), 42) - assert.Equal(t, expected.Header, result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetBlockByHeight(uint64(42)). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetBlockHeaderByHeight(context.Background(), 42) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetBlockHeaderByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - id := flowgo.Identifier{} - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetBlockByID(id). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetBlockHeaderByID(context.Background(), id) - assert.Equal(t, expected.Header, result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetBlockByID(id). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetBlockHeaderByID(context.Background(), id) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetLatestBlock(). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetLatestBlock(context.Background(), true) - assert.Equal(t, expected, *result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetLatestBlock(). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetLatestBlock(context.Background(), true) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetBlockByHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetBlockByHeight(uint64(42)). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetBlockByHeight(context.Background(), 42) - assert.Equal(t, expected, *result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetBlockByHeight(uint64(42)). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetBlockByHeight(context.Background(), 42) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetBlockByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - id := flowgo.Identifier{} - header := flowgo.Header{ - Height: 42, - } - expected := flowgo.Block{Header: &header} - - //success - emu.EXPECT(). - GetBlockByID(id). - Return(&expected, nil). - Times(1) - - result, blockStatus, err := adapter.GetBlockByID(context.Background(), id) - assert.Equal(t, expected, *result) - assert.Equal(t, flowgo.BlockStatusSealed, blockStatus) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetBlockByID(id). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, blockStatus, err = adapter.GetBlockByID(context.Background(), id) - assert.Nil(t, result) - assert.Equal(t, flowgo.BlockStatusUnknown, blockStatus) - assert.Error(t, err) - - })) - - t.Run("GetCollectionByID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - id := flowgo.Identifier{} - expected := flowgo.LightCollection{} - - //success - emu.EXPECT(). - GetCollectionByID(id). - Return(&expected, nil). - Times(1) - - result, err := adapter.GetCollectionByID(context.Background(), id) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetCollectionByID(id). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetCollectionByID(context.Background(), id) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetTransaction", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - id := flowgo.Identifier{} - expected := flowgo.TransactionBody{} - - //success - emu.EXPECT(). - GetTransaction(id). - Return(&expected, nil). - Times(1) - - result, err := adapter.GetTransaction(context.Background(), id) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetTransaction(id). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetTransaction(context.Background(), id) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetTransactionResult", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - txID := flowgo.Identifier{} - blockID := flowgo.Identifier{} - collectionID := flowgo.Identifier{} - - emuResult := access.TransactionResult{ - Events: []flowgo.Event{ - ccfEventFixture(t), - }, - } - expected := access.TransactionResult{ - Events: []flowgo.Event{ - jsonCDCEventFixture(t), - }, - } - - //success - emu.EXPECT(). - GetTransactionResult(txID). - Return(&emuResult, nil). - Times(1) - - result, err := adapter.GetTransactionResult(context.Background(), txID, blockID, collectionID, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetTransactionResult(txID). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetTransactionResult(context.Background(), txID, blockID, collectionID, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetAccount", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - address := flowgo.Address{} - expected := flowgo.Account{} - - //success - emu.EXPECT(). - GetAccount(address). - Return(&expected, nil). - Times(1) - - result, err := adapter.GetAccount(context.Background(), address) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetAccount(address). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetAccount(context.Background(), address) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetAccountAtLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - address := flowgo.Address{} - expected := flowgo.Account{} - - //success - emu.EXPECT(). - GetAccount(address). - Return(&expected, nil). - Times(1) - - result, err := adapter.GetAccountAtLatestBlock(context.Background(), address) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetAccount(address). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetAccountAtLatestBlock(context.Background(), address) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetAccountAtBlockHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - address := flowgo.Address{} - height := uint64(42) - - expected := flowgo.Account{} - - //success - emu.EXPECT(). - GetAccountAtBlockHeight(address, height). - Return(&expected, nil). - Times(1) - - result, err := adapter.GetAccountAtBlockHeight(context.Background(), address, height) - assert.Equal(t, expected, *result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetAccountAtBlockHeight(address, height). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetAccountAtBlockHeight(context.Background(), address, height) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("ExecuteScriptAtLatestBlock", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - script := []byte("some cadence code here") - var arguments [][]byte - - stringValue, _ := cadence.NewString("42") - emulatorResult := emulator.ScriptResult{Value: stringValue} - expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) - - // called once for each script execution - emu.EXPECT(). - GetLatestBlock(). - Return(&flowgo.Block{Header: &flowgo.Header{}}, nil). - Times(2) - - //success - emu.EXPECT(). - ExecuteScript(script, arguments). - Return(&emulatorResult, nil). - Times(1) - - result, err := adapter.ExecuteScriptAtLatestBlock(context.Background(), script, arguments) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - ExecuteScript(script, arguments). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.ExecuteScriptAtLatestBlock(context.Background(), script, arguments) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("ExecuteScriptAtBlockHeight", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - script := []byte("some cadence code here") - var arguments [][]byte - - height := uint64(42) - stringValue, _ := cadence.NewString("42") - emulatorResult := emulator.ScriptResult{Value: stringValue} - expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) - - //success - emu.EXPECT(). - ExecuteScriptAtBlockHeight(script, arguments, height). - Return(&emulatorResult, nil). - Times(1) - - result, err := adapter.ExecuteScriptAtBlockHeight(context.Background(), height, script, arguments) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - ExecuteScriptAtBlockHeight(script, arguments, height). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.ExecuteScriptAtBlockHeight(context.Background(), height, script, arguments) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("ExecuteScriptAtBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - script := []byte("some cadence code here") - var arguments [][]byte - - id := flowgo.Identifier{} - stringValue, _ := cadence.NewString("42") - emulatorResult := emulator.ScriptResult{Value: stringValue} - expected, _ := adapter.ConvertScriptResult(&emulatorResult, nil) - - //success - emu.EXPECT(). - ExecuteScriptAtBlockID(script, arguments, id). - Return(&emulatorResult, nil). - Times(1) - - result, err := adapter.ExecuteScriptAtBlockID(context.Background(), id, script, arguments) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - ExecuteScriptAtBlockID(script, arguments, id). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.ExecuteScriptAtBlockID(context.Background(), id, script, arguments) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetEventsForHeightRange", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - eventType := "testEvent" - startHeight := uint64(0) - endHeight := uint64(42) - - blockEvents := []flowgo.BlockEvents{ - { - Events: []flowgo.Event{ - ccfEventFixture(t), - }, - }, - } - expected := []flowgo.BlockEvents{ - { - Events: []flowgo.Event{ - jsonCDCEventFixture(t), - }, - }, - } - - //success - emu.EXPECT(). - GetEventsForHeightRange(eventType, startHeight, endHeight). - Return(blockEvents, nil). - Times(1) - - result, err := adapter.GetEventsForHeightRange(context.Background(), eventType, startHeight, endHeight, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetEventsForHeightRange(eventType, startHeight, endHeight). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetEventsForHeightRange(context.Background(), eventType, startHeight, endHeight, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetEventsForBlockIDs", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - eventType := "testEvent" - blockIDs := []flowgo.Identifier{flowgo.Identifier{}} - - blockEvents := []flowgo.BlockEvents{ - { - Events: []flowgo.Event{ - ccfEventFixture(t), - }, - }, - } - expected := []flowgo.BlockEvents{ - { - Events: []flowgo.Event{ - jsonCDCEventFixture(t), - }, - }, - } - - //success - emu.EXPECT(). - GetEventsForBlockIDs(eventType, blockIDs). - Return(blockEvents, nil). - Times(1) - - result, err := adapter.GetEventsForBlockIDs(context.Background(), eventType, blockIDs, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetEventsForBlockIDs(eventType, blockIDs). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetEventsForBlockIDs(context.Background(), eventType, blockIDs, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetTransactionResultByIndex", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - blockID := flowgo.Identifier{} - index := uint32(0) - - txResult := &access.TransactionResult{ - Events: []flowgo.Event{ - ccfEventFixture(t), - }, - } - results := []*access.TransactionResult{txResult} - convertedTXResult := &access.TransactionResult{ - Events: []flowgo.Event{ - jsonCDCEventFixture(t), - }, - } - - //success - emu.EXPECT(). - GetTransactionResultsByBlockID(blockID). - Return(results, nil). - Times(1) - - result, err := adapter.GetTransactionResultByIndex(context.Background(), blockID, index, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Equal(t, convertedTXResult, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetTransactionResultsByBlockID(blockID). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetTransactionResultByIndex(context.Background(), blockID, index, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetTransactionsByBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - blockID := flowgo.Identifier{} - - expected := []*flowgo.TransactionBody{} - - //success - emu.EXPECT(). - GetTransactionsByBlockID(blockID). - Return(expected, nil). - Times(1) - - result, err := adapter.GetTransactionsByBlockID(context.Background(), blockID) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetTransactionsByBlockID(blockID). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetTransactionsByBlockID(context.Background(), blockID) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("GetTransactionResultsByBlockID", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - blockID := flowgo.Identifier{} - - results := []*access.TransactionResult{ - { - Events: []flowgo.Event{ - ccfEventFixture(t), - }, - }, - } - expected := []*access.TransactionResult{ - { - Events: []flowgo.Event{ - jsonCDCEventFixture(t), - }, - }, - } - - //success - emu.EXPECT(). - GetTransactionResultsByBlockID(blockID). - Return(results, nil). - Times(1) - - result, err := adapter.GetTransactionResultsByBlockID(context.Background(), blockID, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Equal(t, expected, result) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - GetTransactionResultsByBlockID(blockID). - Return(nil, fmt.Errorf("some error")). - Times(1) - - result, err = adapter.GetTransactionResultsByBlockID(context.Background(), blockID, entities.EventEncodingVersion_JSON_CDC_V0) - assert.Nil(t, result) - assert.Error(t, err) - - })) - - t.Run("SendTransaction", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - - transaction := flowgo.TransactionBody{} - - //success - emu.EXPECT(). - SendTransaction(&transaction). - Return(nil). - Times(1) - - err := adapter.SendTransaction(context.Background(), &transaction) - assert.NoError(t, err) - - //fail - emu.EXPECT(). - SendTransaction(&transaction). - Return(fmt.Errorf("some error")). - Times(1) - - err = adapter.SendTransaction(context.Background(), &transaction) - assert.Error(t, err) - - })) - - t.Run("GetNodeVersionInfo", accessTest(func(t *testing.T, adapter *adapters.AccessAdapter, emu *mocks.MockEmulator) { - info, err := adapter.GetNodeVersionInfo(context.Background()) - assert.NoError(t, err) - require.Equal(t, uint64(0), info.NodeRootBlockHeight) - })) - -} - -func ccfEventFixture(t *testing.T) flowgo.Event { - cadenceValue := cadence.NewInt(2) - - ccfEvent, err := ccf.EventsEncMode.Encode(cadenceValue) - require.NoError(t, err) - - return flowgo.Event{ - Payload: ccfEvent, - } -} - -func jsonCDCEventFixture(t *testing.T) flowgo.Event { - converted, err := convert.CcfEventToJsonEvent(ccfEventFixture(t)) - require.NoError(t, err) - return *converted -} diff --git a/integration/emulator/blockchain.go b/integration/emulator/blockchain.go index 987bc161c50..59f56c60b05 100644 --- a/integration/emulator/blockchain.go +++ b/integration/emulator/blockchain.go @@ -113,17 +113,15 @@ func (b *Blockchain) Broadcaster() *engine.Broadcaster { func (b *Blockchain) ReloadBlockchain() (*Blockchain, error) { - cadenceLogger := b.conf.Logger.Hook(CadenceHook{MainLogger: &b.conf.ServerLogger}).Level(zerolog.DebugLevel) - b.vm = fvm.NewVirtualMachine() b.vmCtx = fvm.NewContext( - fvm.WithLogger(cadenceLogger), + fvm.WithLogger(b.conf.Logger), + fvm.WithCadenceLogging(true), fvm.WithChain(b.conf.GetChainID().Chain()), fvm.WithBlocks(b.storage), fvm.WithContractDeploymentRestricted(false), fvm.WithContractRemovalRestricted(!b.conf.ContractRemovalEnabled), fvm.WithComputationLimit(b.conf.ScriptGasLimit), - fvm.WithCadenceLogging(true), fvm.WithAccountStorageLimit(b.conf.StorageLimitEnabled), fvm.WithTransactionFeesEnabled(b.conf.TransactionFeesEnabled), fvm.WithReusableCadenceRuntimePool( @@ -771,10 +769,6 @@ func (b *Blockchain) executeAndCommitBlock() (*flowgo.Block, []*TransactionResul return nil, results, err } - for _, result := range results { - PrintTransactionResult(&b.conf.ServerLogger, result) - } - blockID := block.ID() b.conf.ServerLogger.Debug().Fields(map[string]any{ "blockHeight": block.Header.Height, @@ -985,6 +979,7 @@ func (b *Blockchain) GetLogs(identifier flowgo.Identifier) ([]string, error) { // SetClock sets the given clock on blockchain's pending block. func (b *Blockchain) SetClock(clock func() time.Time) { b.clockOverride = clock + b.pendingBlock.SetTimestamp(clock()) } // NewScriptEnvironment returns an environment.Environment by diff --git a/integration/emulator/log.go b/integration/emulator/log.go deleted file mode 100644 index 0da17361d22..00000000000 --- a/integration/emulator/log.go +++ /dev/null @@ -1,22 +0,0 @@ -package emulator - -import ( - "github.com/logrusorgru/aurora" - "github.com/rs/zerolog" - "strings" -) - -type CadenceHook struct { - MainLogger *zerolog.Logger -} - -func (h CadenceHook) Run(_ *zerolog.Event, level zerolog.Level, msg string) { - const logPrefix = "Cadence log:" - if level != zerolog.NoLevel && strings.HasPrefix(msg, logPrefix) { - h.MainLogger.Info().Msg( - strings.Replace(msg, - logPrefix, - aurora.Colorize("LOG:", aurora.BlueFg|aurora.BoldFm).String(), - 1)) - } -} diff --git a/integration/emulator/logging.go b/integration/emulator/logging.go deleted file mode 100644 index 27800804e9e..00000000000 --- a/integration/emulator/logging.go +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Flow Emulator - * - * Copyright Flow Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package emulator - -import ( - "fmt" - "github.com/logrusorgru/aurora" - sdk "github.com/onflow/flow-go-sdk" - "github.com/rs/zerolog" -) - -func PrintScriptResult(logger *zerolog.Logger, result *ScriptResult) { - if result.Succeeded() { - logger.Debug(). - Str("scriptID", result.ScriptID.String()). - Uint64("computationUsed", result.ComputationUsed). - Uint64("memoryEstimate", result.MemoryEstimate). - Msg("⭐ Script executed") - } else { - logger.Warn(). - Str("scriptID", result.ScriptID.String()). - Uint64("computationUsed", result.ComputationUsed). - Uint64("memoryEstimate", result.MemoryEstimate). - Msg("❗ Script reverted") - } - - if !result.Succeeded() { - logger.Warn().Msgf( - "%s %s", - logPrefix("ERR", FlowIdentifierToSDK(result.ScriptID), aurora.RedFg), - result.Error.Error(), - ) - } -} - -func PrintTransactionResult(logger *zerolog.Logger, result *TransactionResult) { - if result.Succeeded() { - logger.Debug(). - Str("txID", result.TransactionID.String()). - Uint64("computationUsed", result.ComputationUsed). - Uint64("memoryEstimate", result.MemoryEstimate). - Msg("⭐ Transaction executed") - } else { - logger.Warn(). - Str("txID", result.TransactionID.String()). - Uint64("computationUsed", result.ComputationUsed). - Uint64("memoryEstimate", result.MemoryEstimate). - Msg("❗ Transaction reverted") - } - - for _, event := range result.Events { - logger.Debug().Msgf( - "%s %s", - logPrefix("EVT", result.TransactionID, aurora.GreenFg), - event, - ) - } - - if !result.Succeeded() { - logger.Warn().Msgf( - "%s %s", - logPrefix("ERR", result.TransactionID, aurora.RedFg), - result.Error.Error(), - ) - - if result.Debug != nil { - logger.Debug().Fields(result.Debug.Meta).Msgf("%s %s", "❗ Transaction Signature Error", result.Debug.Message) - } - } -} - -func logPrefix(prefix string, id sdk.Identifier, color aurora.Color) string { - prefix = aurora.Colorize(prefix, color|aurora.BoldFm).String() - shortID := fmt.Sprintf("[%s]", id.String()[:6]) - shortID = aurora.Colorize(shortID, aurora.FaintFm).String() - return fmt.Sprintf("%s %s", prefix, shortID) -} diff --git a/integration/emulator/adapters/sdk.go b/integration/emulator/sdk.go similarity index 88% rename from integration/emulator/adapters/sdk.go rename to integration/emulator/sdk.go index aab4c2bd156..196ecf684ea 100644 --- a/integration/emulator/adapters/sdk.go +++ b/integration/emulator/sdk.go @@ -1,4 +1,4 @@ -package adapters +package emulator import ( "context" @@ -14,15 +14,13 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - "github.com/onflow/flow-go/integration/emulator" ) // SDKAdapter wraps an emulated emulator and implements the RPC handlers // required by the Access API. type SDKAdapter struct { logger *zerolog.Logger - emulator emulator.Emulator + emulator Emulator } func (b *SDKAdapter) EnableAutoMine() { @@ -32,12 +30,12 @@ func (b *SDKAdapter) DisableAutoMine() { b.emulator.DisableAutoMine() } -func (b *SDKAdapter) Emulator() emulator.Emulator { +func (b *SDKAdapter) Emulator() Emulator { return b.emulator } // NewSDKAdapter returns a new SDKAdapter. -func NewSDKAdapter(logger *zerolog.Logger, emulator emulator.Emulator) *SDKAdapter { +func NewSDKAdapter(logger *zerolog.Logger, emulator Emulator) *SDKAdapter { return &SDKAdapter{ logger: logger, emulator: emulator, @@ -105,7 +103,7 @@ func (b *SDKAdapter) GetBlockHeaderByID( sdk.BlockStatus, error, ) { - block, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(id)) + block, err := b.emulator.GetBlockByID(SDKIdentifierToFlow(id)) if err != nil { return nil, sdk.BlockStatusUnknown, err } @@ -177,7 +175,7 @@ func (b *SDKAdapter) GetBlockByID( sdk.BlockStatus, error, ) { - flowBlock, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(id)) + flowBlock, err := b.emulator.GetBlockByID(SDKIdentifierToFlow(id)) if err != nil { return nil, sdk.BlockStatusUnknown, status.Error(codes.Internal, err.Error()) } @@ -228,16 +226,16 @@ func (b *SDKAdapter) GetCollectionByID( _ context.Context, id sdk.Identifier, ) (*sdk.Collection, error) { - flowCollection, err := b.emulator.GetCollectionByID(emulator.SDKIdentifierToFlow(id)) + flowCollection, err := b.emulator.GetCollectionByID(SDKIdentifierToFlow(id)) if err != nil { return nil, err } - collection := emulator.FlowLightCollectionToSDK(*flowCollection) + collection := FlowLightCollectionToSDK(*flowCollection) return &collection, nil } func (b *SDKAdapter) SendTransaction(ctx context.Context, tx sdk.Transaction) error { - flowTx := emulator.SDKTransactionToFlow(tx) + flowTx := SDKTransactionToFlow(tx) return b.emulator.SendTransaction(flowTx) } @@ -246,11 +244,11 @@ func (b *SDKAdapter) GetTransaction( ctx context.Context, id sdk.Identifier, ) (*sdk.Transaction, error) { - tx, err := b.emulator.GetTransaction(emulator.SDKIdentifierToFlow(id)) + tx, err := b.emulator.GetTransaction(SDKIdentifierToFlow(id)) if err != nil { return nil, err } - sdkTx := emulator.FlowTransactionToSDK(*tx) + sdkTx := FlowTransactionToSDK(*tx) return &sdkTx, nil } @@ -259,11 +257,11 @@ func (b *SDKAdapter) GetTransactionResult( ctx context.Context, id sdk.Identifier, ) (*sdk.TransactionResult, error) { - flowResult, err := b.emulator.GetTransactionResult(emulator.SDKIdentifierToFlow(id)) + flowResult, err := b.emulator.GetTransactionResult(SDKIdentifierToFlow(id)) if err != nil { return nil, err } - return emulator.FlowTransactionResultToSDK(flowResult) + return FlowTransactionResultToSDK(flowResult) } // GetAccount returns an account by address at the latest sealed block. @@ -291,11 +289,11 @@ func (b *SDKAdapter) GetAccountAtLatestBlock( } func (b *SDKAdapter) getAccount(address sdk.Address) (*sdk.Account, error) { - account, err := b.emulator.GetAccount(emulator.SDKAddressToFlow(address)) + account, err := b.emulator.GetAccount(SDKAddressToFlow(address)) if err != nil { return nil, err } - return emulator.FlowAccountToSDK(*account) + return FlowAccountToSDK(*account) } func (b *SDKAdapter) GetAccountAtBlockHeight( @@ -303,11 +301,11 @@ func (b *SDKAdapter) GetAccountAtBlockHeight( address sdk.Address, height uint64, ) (*sdk.Account, error) { - account, err := b.emulator.GetAccountAtBlockHeight(emulator.SDKAddressToFlow(address), height) + account, err := b.emulator.GetAccountAtBlockHeight(SDKAddressToFlow(address), height) if err != nil { return nil, err } - return emulator.FlowAccountToSDK(*account) + return FlowAccountToSDK(*account) } // ExecuteScriptAtLatestBlock executes a script at a the latest block @@ -340,7 +338,7 @@ func (b *SDKAdapter) ExecuteScriptAtBlockID( script []byte, arguments [][]byte, ) ([]byte, error) { - block, err := b.emulator.GetBlockByID(emulator.SDKIdentifierToFlow(blockID)) + block, err := b.emulator.GetBlockByID(SDKIdentifierToFlow(blockID)) if err != nil { return nil, err } @@ -389,12 +387,12 @@ func (b *SDKAdapter) GetSystemTransactionResult(_ context.Context, _ flowgo.Iden func (b *SDKAdapter) GetTransactionsByBlockID(ctx context.Context, id sdk.Identifier) ([]*sdk.Transaction, error) { result := []*sdk.Transaction{} - transactions, err := b.emulator.GetTransactionsByBlockID(emulator.SDKIdentifierToFlow(id)) + transactions, err := b.emulator.GetTransactionsByBlockID(SDKIdentifierToFlow(id)) if err != nil { return nil, err } for _, transaction := range transactions { - sdkTransaction := emulator.FlowTransactionToSDK(*transaction) + sdkTransaction := FlowTransactionToSDK(*transaction) result = append(result, &sdkTransaction) } @@ -403,12 +401,12 @@ func (b *SDKAdapter) GetTransactionsByBlockID(ctx context.Context, id sdk.Identi func (b *SDKAdapter) GetTransactionResultsByBlockID(ctx context.Context, id sdk.Identifier) ([]*sdk.TransactionResult, error) { result := []*sdk.TransactionResult{} - transactionResults, err := b.emulator.GetTransactionResultsByBlockID(emulator.SDKIdentifierToFlow(id)) + transactionResults, err := b.emulator.GetTransactionResultsByBlockID(SDKIdentifierToFlow(id)) if err != nil { return nil, err } for _, transactionResult := range transactionResults { - sdkResult, err := emulator.FlowTransactionResultToSDK(transactionResult) + sdkResult, err := FlowTransactionResultToSDK(transactionResult) if err != nil { return nil, err } @@ -419,13 +417,13 @@ func (b *SDKAdapter) GetTransactionResultsByBlockID(ctx context.Context, id sdk. func (b *SDKAdapter) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []sdk.Identifier) ([]*sdk.BlockEvents, error) { result := []*sdk.BlockEvents{} - flowBlockEvents, err := b.emulator.GetEventsForBlockIDs(eventType, emulator.SDKIdentifiersToFlow(blockIDs)) + flowBlockEvents, err := b.emulator.GetEventsForBlockIDs(eventType, SDKIdentifiersToFlow(blockIDs)) if err != nil { return nil, err } for _, flowBlockEvent := range flowBlockEvents { - sdkEvents, err := emulator.FlowEventsToSDK(flowBlockEvent.Events) + sdkEvents, err := FlowEventsToSDK(flowBlockEvent.Events) if err != nil { return nil, err } @@ -453,7 +451,7 @@ func (b *SDKAdapter) GetEventsForHeightRange(ctx context.Context, eventType stri } for _, flowBlockEvent := range flowBlockEvents { - sdkEvents, err := emulator.FlowEventsToSDK(flowBlockEvent.Events) + sdkEvents, err := FlowEventsToSDK(flowBlockEvent.Events) if err != nil { return nil, err @@ -479,7 +477,7 @@ func (b *SDKAdapter) CreateAccount(ctx context.Context, publicKeys []*sdk.Accoun serviceKey := b.emulator.ServiceKey() latestBlock, err := b.emulator.GetLatestBlock() - serviceAddress := emulator.FlowAddressToSDK(serviceKey.Address) + serviceAddress := FlowAddressToSDK(serviceKey.Address) if err != nil { return sdk.Address{}, err diff --git a/integration/emulator/tests/accounts_test.go b/integration/emulator/tests/accounts_test.go index d2ee7482419..91b74698025 100644 --- a/integration/emulator/tests/accounts_test.go +++ b/integration/emulator/tests/accounts_test.go @@ -21,7 +21,6 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" flowsdk "github.com/onflow/flow-go-sdk" @@ -41,7 +40,7 @@ const testContract = "access(all) contract Test {}" func setupAccountTests(t *testing.T, opts ...emulator.Option) ( *emulator.Blockchain, - *adapters.SDKAdapter, + *emulator.SDKAdapter, ) { b, err := emulator.New( opts..., @@ -49,7 +48,7 @@ func setupAccountTests(t *testing.T, opts ...emulator.Option) ( require.NoError(t, err) logger := zerolog.Nop() - return b, adapters.NewSDKAdapter(&logger, b) + return b, emulator.NewSDKAdapter(&logger, b) } func TestGetAccount(t *testing.T) { diff --git a/integration/emulator/tests/block_info_test.go b/integration/emulator/tests/block_info_test.go index 3bd8e2ec9fd..2db2257d634 100644 --- a/integration/emulator/tests/block_info_test.go +++ b/integration/emulator/tests/block_info_test.go @@ -21,7 +21,6 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/rs/zerolog" @@ -44,7 +43,7 @@ func TestBlockInfo(t *testing.T) { serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) block1, err := b.CommitBlock() require.NoError(t, err) diff --git a/integration/emulator/tests/block_test.go b/integration/emulator/tests/block_test.go index 0e331865740..fd6c96d7ec9 100644 --- a/integration/emulator/tests/block_test.go +++ b/integration/emulator/tests/block_test.go @@ -21,7 +21,6 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/onflow/flow-go/integration/emulator" @@ -44,7 +43,7 @@ func TestCommitBlock(t *testing.T) { require.NoError(t, err) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) @@ -127,7 +126,7 @@ func TestBlockView(t *testing.T) { require.NoError(t, err) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) t.Run("genesis should have 0 view", func(t *testing.T) { diff --git a/integration/emulator/tests/blockchain_test.go b/integration/emulator/tests/blockchain_test.go index 7036891135f..4b1ef13ab74 100644 --- a/integration/emulator/tests/blockchain_test.go +++ b/integration/emulator/tests/blockchain_test.go @@ -21,7 +21,6 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/onflow/cadence/stdlib" @@ -87,7 +86,7 @@ func GenerateAddTwoToCounterScript(counterAddress flowsdk.Address) string { ) } -func DeployAndGenerateAddTwoScript(t *testing.T, adapter *adapters.SDKAdapter) (string, flowsdk.Address) { +func DeployAndGenerateAddTwoScript(t *testing.T, adapter *emulator.SDKAdapter) (string, flowsdk.Address) { contracts := []templates.Contract{ { @@ -128,7 +127,7 @@ func AssertTransactionSucceeded(t *testing.T, result *emulator.TransactionResult func LastCreatedAccount(b *emulator.Blockchain, result *emulator.TransactionResult) (*flowsdk.Account, error) { logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) address, err := LastCreatedAccountAddress(result) if err != nil { diff --git a/integration/emulator/tests/collection_test.go b/integration/emulator/tests/collection_test.go index 7681cc739a0..e0fa4e4c14d 100644 --- a/integration/emulator/tests/collection_test.go +++ b/integration/emulator/tests/collection_test.go @@ -20,7 +20,6 @@ package tests import ( "context" "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/rs/zerolog" @@ -61,7 +60,7 @@ func TestCollections(t *testing.T) { require.NoError(t, err) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) diff --git a/integration/emulator/tests/events_test.go b/integration/emulator/tests/events_test.go index ae52846413b..bed43faaad6 100644 --- a/integration/emulator/tests/events_test.go +++ b/integration/emulator/tests/events_test.go @@ -22,7 +22,6 @@ import ( "context" "fmt" "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/rs/zerolog" @@ -75,7 +74,7 @@ func TestEventEmitted(t *testing.T) { serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) accountContracts := []templates.Contract{ { diff --git a/integration/emulator/tests/pendingBlock_test.go b/integration/emulator/tests/pendingBlock_test.go index 3e512609947..3e54deb7d77 100644 --- a/integration/emulator/tests/pendingBlock_test.go +++ b/integration/emulator/tests/pendingBlock_test.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "time" @@ -35,7 +34,7 @@ import ( func setupPendingBlockTests(t *testing.T) ( *emulator.Blockchain, - *adapters.SDKAdapter, + *emulator.SDKAdapter, *flowsdk.Transaction, *flowsdk.Transaction, *flowsdk.Transaction, @@ -45,7 +44,7 @@ func setupPendingBlockTests(t *testing.T) ( ) require.NoError(t, err) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) @@ -336,7 +335,7 @@ func TestPendingBlockCommit(t *testing.T) { serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) diff --git a/integration/emulator/tests/script_test.go b/integration/emulator/tests/script_test.go index 3d4f763d266..39234f27bf7 100644 --- a/integration/emulator/tests/script_test.go +++ b/integration/emulator/tests/script_test.go @@ -22,7 +22,6 @@ import ( "context" "fmt" "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "testing" "github.com/onflow/cadence" @@ -47,7 +46,7 @@ func TestExecuteScript(t *testing.T) { serviceAccountAddress := flowsdk.Address(b.ServiceKey().Address) logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, b) + adapter := emulator.NewSDKAdapter(&logger, b) addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter) diff --git a/integration/emulator/tests/transaction_test.go b/integration/emulator/tests/transaction_test.go index 7e6f96b0e98..b4cffa09c43 100644 --- a/integration/emulator/tests/transaction_test.go +++ b/integration/emulator/tests/transaction_test.go @@ -26,7 +26,6 @@ import ( "errors" "fmt" "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "io" "strings" "testing" @@ -49,13 +48,13 @@ import ( func setupTransactionTests(t *testing.T, opts ...emulator.Option) ( *emulator.Blockchain, - *adapters.SDKAdapter, + *emulator.SDKAdapter, ) { b, err := emulator.New(opts...) require.NoError(t, err) logger := zerolog.Nop() - return b, adapters.NewSDKAdapter(&logger, b) + return b, emulator.NewSDKAdapter(&logger, b) } func TestSubmitTransaction(t *testing.T) { @@ -1965,7 +1964,7 @@ type MeteredMemoryIntensities map[common.MemoryKind]uint func IncrementHelper( t *testing.T, b emulator.Emulator, - adapter *adapters.SDKAdapter, + adapter *emulator.SDKAdapter, counterAddress flowsdk.Address, addTwoScript string, expected int, diff --git a/integration/utils/emulator_client.go b/integration/utils/emulator_client.go index 7a981cf126d..2fe85d2491b 100644 --- a/integration/utils/emulator_client.go +++ b/integration/utils/emulator_client.go @@ -7,7 +7,6 @@ import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" emulator "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/adapters" "github.com/rs/zerolog" sdk "github.com/onflow/flow-go-sdk" @@ -18,13 +17,13 @@ import ( // EmulatorClient is a wrapper around the emulator to implement the same interface // used by the SDK client. Used for testing against the emulator. type EmulatorClient struct { - adapter *adapters.SDKAdapter + adapter *emulator.SDKAdapter } func NewEmulatorClient(blockchain emulator.Emulator) *EmulatorClient { logger := zerolog.Nop() - adapter := adapters.NewSDKAdapter(&logger, blockchain) + adapter := emulator.NewSDKAdapter(&logger, blockchain) client := &EmulatorClient{ adapter: adapter, } From 32f90f998b022a5a4f4aed7c3febd56fa650e951 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 23 Oct 2024 10:03:48 -0700 Subject: [PATCH 3/9] make tidy --- integration/go.mod | 1 - integration/go.sum | 94 ---------------------------------------------- 2 files changed, 95 deletions(-) diff --git a/integration/go.mod b/integration/go.mod index 01c9ad634f6..949e50576be 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -246,7 +246,6 @@ require ( github.com/onflow/flow-ft/lib/go/templates v1.0.1 // indirect github.com/onflow/flow-nft/lib/go/contracts v1.2.2 // indirect github.com/onflow/flow-nft/lib/go/templates v1.2.1 // indirect - github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 // indirect github.com/onflow/sdks v0.6.0-preview.1 // indirect github.com/onflow/wal v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect diff --git a/integration/go.sum b/integration/go.sum index e5e973292b3..c52c541a3ed 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -287,10 +287,6 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= -github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= @@ -331,10 +327,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -628,8 +620,6 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -748,28 +738,10 @@ github.com/onflow/cadence v1.2.1 h1:hmSsgX3rTsp2E5qTSl1JXINt8qepdRrHTwDSYqN5Nxs= github.com/onflow/cadence v1.2.1/go.mod h1:fJxxOAp1wnWDfOHT8GOc1ypsU0RR5E3z51AhG8Yf5jg= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 h1:q9tXLIALwQ76bO4bmSrhtTkyc2cZF4/gH11ix9E3F5k= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1/go.mod h1:u/mkP/B+PbV33tEG3qfkhhBlydSvAKxfLZSfB4lsJHg= -github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 h1:FfhMBAb78p6VAWkJ+iqdKLErGQVQgxk5w6DP5ZruWX8= -github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1/go.mod h1:NgbMOYnMh0GN48VsNKZuiwK7uyk38Wyo8jN9+C9QE30= -github.com/onflow/flow-ft/lib/go/contracts v1.0.0 h1:mToacZ5NWqtlWwk/7RgIl/jeKB/Sy/tIXdw90yKHcV0= -github.com/onflow/flow-ft/lib/go/contracts v1.0.0/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= -github.com/onflow/flow-ft/lib/go/templates v1.0.0 h1:6cMS/lUJJ17HjKBfMO/eh0GGvnpElPgBXx7h5aoWJhs= -github.com/onflow/flow-ft/lib/go/templates v1.0.0/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go-sdk v1.0.0-M1/go.mod h1:TDW0MNuCs4SvqYRUzkbRnRmHQL1h4X8wURsCw9P9beo= -github.com/onflow/flow-go-sdk v1.1.0 h1:DT8P3B3oAicOOXugdev4s1IEKHsiLS9T7MovFcTzB2s= -github.com/onflow/flow-go-sdk v1.1.0/go.mod h1:21g1pqP9Wy8RBXdenNsjzADwbtWNOViUCnfNZwr3trM= -github.com/onflow/flow-nft/lib/go/contracts v1.2.1 h1:woAAS5z651sDpi7ihAHll8NvRS9uFXIXkL6xR+bKFZY= -github.com/onflow/flow-nft/lib/go/contracts v1.2.1/go.mod h1:2gpbza+uzs1k7x31hkpBPlggIRkI53Suo0n2AyA2HcE= -github.com/onflow/flow-nft/lib/go/templates v1.2.0 h1:JSQyh9rg0RC+D1930BiRXN8lrtMs+ubVMK6aQPon6Yc= -github.com/onflow/flow-nft/lib/go/templates v1.2.0/go.mod h1:p+2hRvtjLUR3MW1NsoJe5Gqgr2eeH49QB6+s6ze00w0= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231121210617-52ee94b830c2/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 h1:R86HaOuk6vpuECZnriEUE7bw9inC2AtdSn8lL/iwQLQ= github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0/go.mod h1:9asTBnB6Tw2UlVVtQKyS/egYv3xr4zVlJnJ75z1dfac= github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 h1:u2DAG8pk0xFH7TwS70t1gSZ/FtIIZWMSNyiu4SeXBYg= github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= -github.com/onflow/flow-emulator v1.0.2-0.20241021223526-a545558d37a2 h1:zs3/ctgI1KNFTcA1HpkDmyNuUybY/oFXDE1S+cYR9lU= -github.com/onflow/flow-emulator v1.0.2-0.20241021223526-a545558d37a2/go.mod h1:HgcZT9TZVOqmNGqN3fX+QqoiVovIHfLwqbXwuaEJiXE= github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3SsEftzXG2JlmSe24= github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= @@ -784,9 +756,6 @@ github.com/onflow/flow/protobuf/go/flow v0.4.7 h1:iP6DFx4wZ3ETORsyeqzHu7neFT3d1C github.com/onflow/flow/protobuf/go/flow v0.4.7/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-ethereum v1.14.7 h1:gg3awYqI02e3AypRdpJKEvNTJ6kz/OhAqRti0h54Wlc= github.com/onflow/go-ethereum v1.14.7/go.mod h1:zV14QLrXyYu5ucvcwHUA0r6UaqveqbXaehAVQJlSW+I= -github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0/go.mod h1:kMeq9zUwCrgrSojEbTUTTJpZ4WwacVm2pA7LVFr+glk= github.com/onflow/sdks v0.6.0-preview.1 h1:mb/cUezuqWEP1gFZNAgUI4boBltudv4nlfxke1KBp9k= github.com/onflow/sdks v0.6.0-preview.1/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= github.com/onflow/wal v1.0.2 h1:5bgsJVf2O3cfMNK12fiiTyYZ8cOrUiELt3heBJfHOhc= @@ -880,9 +849,6 @@ github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFD github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -1435,66 +1401,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= -modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= -modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= -modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= -modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= -modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= From c9b940282e2a28881ed24fc432ac44ff124e1fad Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 23 Oct 2024 12:59:48 -0700 Subject: [PATCH 4/9] omit emulator from math rand check --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8538c94d42d..60626c48d13 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ go-math-rand-check: # - "onflow/crypto/random" for deterministic randomness grep --include=\*.go \ --exclude=*test* --exclude=*helper* --exclude=*example* --exclude=*fixture* --exclude=*benchmark* --exclude=*profiler* \ - --exclude-dir=*test* --exclude-dir=*helper* --exclude-dir=*example* --exclude-dir=*fixture* --exclude-dir=*benchmark* --exclude-dir=*profiler* -rnw '"math/rand"'; \ + --exclude-dir=*test* --exclude-dir=*helper* --exclude-dir=*example* --exclude-dir=*fixture* --exclude-dir=*benchmark* --exclude-dir=*profiler* --exclude-dir=*emulator* -rnw '"math/rand"'; \ if [ $$? -ne 1 ]; then \ echo "[Error] Go production code should not use math/rand package"; exit 1; \ fi From a76f31a3b614b28b4b82dd38b520204387c7e488 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 23 Oct 2024 13:00:13 -0700 Subject: [PATCH 5/9] lint errors --- integration/benchmark/load/load_type_test.go | 4 ++-- integration/emulator/config.go | 5 ++++- integration/emulator/convert.go | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/integration/benchmark/load/load_type_test.go b/integration/benchmark/load/load_type_test.go index 0119a1b577e..3281a96cee6 100644 --- a/integration/benchmark/load/load_type_test.go +++ b/integration/benchmark/load/load_type_test.go @@ -8,7 +8,6 @@ import ( "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" - convert2 "github.com/onflow/flow-go/integration/convert" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -28,6 +27,7 @@ import ( "github.com/onflow/flow-go/integration/benchmark/common" "github.com/onflow/flow-go/integration/benchmark/load" "github.com/onflow/flow-go/integration/convert" + "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) @@ -254,7 +254,7 @@ func (t *testTransactionSender) Send(tx *sdk.Transaction) (sdk.TransactionResult Error: result.Err, BlockID: sdk.EmptyID, BlockHeight: 0, - TransactionID: convert2.FlowIdentifierToSDK(txBody.ID()), + TransactionID: emulator.FlowIdentifierToSDK(txBody.ID()), CollectionID: sdk.EmptyID, } diff --git a/integration/emulator/config.go b/integration/emulator/config.go index 6654d873d97..d87be0a162c 100644 --- a/integration/emulator/config.go +++ b/integration/emulator/config.go @@ -2,13 +2,16 @@ package emulator import ( "fmt" + + "github.com/rs/zerolog" + "github.com/onflow/cadence" "github.com/onflow/crypto" "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/meter" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/rs/zerolog" ) // config is a set of configuration options for an emulated emulator. diff --git a/integration/emulator/convert.go b/integration/emulator/convert.go index 85ba557d95e..f9f8f1dfb6a 100644 --- a/integration/emulator/convert.go +++ b/integration/emulator/convert.go @@ -20,9 +20,12 @@ package emulator import ( "fmt" + "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" + sdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/fvm" fvmerrors "github.com/onflow/flow-go/fvm/errors" From 09f62050af650ea8645e23f739e234ac3fdc409d Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 23 Oct 2024 13:20:27 -0700 Subject: [PATCH 6/9] goimports --- integration/emulator/blockchain.go | 4 +++- integration/emulator/emulator.go | 1 + integration/emulator/errors.go | 4 ++-- integration/emulator/ledger.go | 4 +++- integration/emulator/memstore.go | 4 ++-- integration/emulator/result.go | 2 ++ integration/emulator/sdk.go | 12 ++++++++---- integration/emulator/store.go | 4 +++- integration/emulator/tests/accounts_test.go | 11 ++++++----- integration/emulator/tests/attachments_test.go | 3 ++- integration/emulator/tests/block_info_test.go | 8 ++++---- integration/emulator/tests/block_test.go | 7 ++++--- integration/emulator/tests/blockchain_test.go | 11 ++++++----- integration/emulator/tests/capcons_test.go | 3 ++- integration/emulator/tests/collection_test.go | 7 ++++--- integration/emulator/tests/events_test.go | 11 ++++++----- integration/emulator/tests/logs_test.go | 3 ++- integration/emulator/tests/memstore_test.go | 7 ++++--- integration/emulator/tests/pendingBlock_test.go | 7 ++++--- integration/emulator/tests/result_test.go | 8 +++++--- integration/emulator/tests/script_test.go | 10 ++++++---- integration/emulator/tests/store_test.go | 12 +++++++----- integration/emulator/tests/transaction_test.go | 11 +++++++---- integration/emulator/tests/vm_test.go | 7 ++++--- integration/emulator/utils/unittest/fixtures.go | 4 +++- 25 files changed, 100 insertions(+), 65 deletions(-) diff --git a/integration/emulator/blockchain.go b/integration/emulator/blockchain.go index 59f56c60b05..b2198588ed3 100644 --- a/integration/emulator/blockchain.go +++ b/integration/emulator/blockchain.go @@ -38,10 +38,13 @@ import ( "sync" "time" + "github.com/rs/zerolog" + "github.com/onflow/cadence" "github.com/onflow/cadence/runtime" "github.com/onflow/flow-core-contracts/lib/go/templates" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/fvm" @@ -50,7 +53,6 @@ import ( reusableRuntime "github.com/onflow/flow-go/fvm/runtime" flowgo "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" - "github.com/rs/zerolog" ) // systemChunkTransactionTemplate looks for the RandomBeaconHistory diff --git a/integration/emulator/emulator.go b/integration/emulator/emulator.go index 707a6b2b23c..4a9772d3df8 100644 --- a/integration/emulator/emulator.go +++ b/integration/emulator/emulator.go @@ -20,6 +20,7 @@ package emulator import ( "fmt" + sdkcrypto "github.com/onflow/flow-go-sdk/crypto" "github.com/onflow/crypto" diff --git a/integration/emulator/errors.go b/integration/emulator/errors.go index fe275eb409d..e23b6822ce6 100644 --- a/integration/emulator/errors.go +++ b/integration/emulator/errors.go @@ -22,10 +22,10 @@ import ( "errors" "fmt" - fvmerrors "github.com/onflow/flow-go/fvm/errors" - "github.com/onflow/flow-go-sdk/crypto" + "github.com/onflow/flow-go/access" + fvmerrors "github.com/onflow/flow-go/fvm/errors" flowgo "github.com/onflow/flow-go/model/flow" ) diff --git a/integration/emulator/ledger.go b/integration/emulator/ledger.go index 7064ebbd4d6..f1bcdac7d79 100644 --- a/integration/emulator/ledger.go +++ b/integration/emulator/ledger.go @@ -4,13 +4,15 @@ import ( "context" "errors" "fmt" + "math" + "github.com/onflow/cadence" + "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/storage/snapshot" flowgo "github.com/onflow/flow-go/model/flow" - "math" ) func configureLedger( diff --git a/integration/emulator/memstore.go b/integration/emulator/memstore.go index 23bdfa7a0ac..191cd21f4d8 100644 --- a/integration/emulator/memstore.go +++ b/integration/emulator/memstore.go @@ -22,10 +22,10 @@ import ( "context" "errors" "fmt" - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/fvm/environment" "sync" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/storage/snapshot" flowgo "github.com/onflow/flow-go/model/flow" ) diff --git a/integration/emulator/result.go b/integration/emulator/result.go index 1ec6a920cbb..9b93d6a865a 100644 --- a/integration/emulator/result.go +++ b/integration/emulator/result.go @@ -20,7 +20,9 @@ package emulator import ( "fmt" + "github.com/onflow/cadence" + flowsdk "github.com/onflow/flow-go-sdk" flowgo "github.com/onflow/flow-go/model/flow" ) diff --git a/integration/emulator/sdk.go b/integration/emulator/sdk.go index 196ecf684ea..06b6085ec6a 100644 --- a/integration/emulator/sdk.go +++ b/integration/emulator/sdk.go @@ -3,17 +3,21 @@ package emulator import ( "context" "fmt" + + "github.com/rs/zerolog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/stdlib" + "github.com/onflow/flow/protobuf/go/flow/entities" + sdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go/access" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/rs/zerolog" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) // SDKAdapter wraps an emulated emulator and implements the RPC handlers diff --git a/integration/emulator/store.go b/integration/emulator/store.go index 7a157ae296a..2035268a488 100644 --- a/integration/emulator/store.go +++ b/integration/emulator/store.go @@ -22,11 +22,13 @@ package emulator import ( "context" + + "github.com/psiemens/graceland" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/storage/snapshot" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/psiemens/graceland" ) // EmulatorStorage defines the storage layer for persistent chain state. diff --git a/integration/emulator/tests/accounts_test.go b/integration/emulator/tests/accounts_test.go index 91b74698025..0dbe613f3d9 100644 --- a/integration/emulator/tests/accounts_test.go +++ b/integration/emulator/tests/accounts_test.go @@ -23,17 +23,18 @@ import ( "fmt" "testing" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + flowsdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/crypto" "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" - fvmerrors "github.com/onflow/flow-go/fvm/errors" - flowgo "github.com/onflow/flow-go/model/flow" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + fvmerrors "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/integration/emulator" + flowgo "github.com/onflow/flow-go/model/flow" ) const testContract = "access(all) contract Test {}" diff --git a/integration/emulator/tests/attachments_test.go b/integration/emulator/tests/attachments_test.go index 07670a1e1c5..76bc87974be 100644 --- a/integration/emulator/tests/attachments_test.go +++ b/integration/emulator/tests/attachments_test.go @@ -19,10 +19,11 @@ package tests import ( - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/integration/emulator" ) func TestAttachments(t *testing.T) { diff --git a/integration/emulator/tests/block_info_test.go b/integration/emulator/tests/block_info_test.go index 2db2257d634..5327c6fe6cb 100644 --- a/integration/emulator/tests/block_info_test.go +++ b/integration/emulator/tests/block_info_test.go @@ -24,13 +24,13 @@ import ( "testing" "github.com/rs/zerolog" - - "github.com/onflow/flow-go/integration/emulator" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" flowsdk "github.com/onflow/flow-go-sdk" + + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestBlockInfo(t *testing.T) { diff --git a/integration/emulator/tests/block_test.go b/integration/emulator/tests/block_test.go index fd6c96d7ec9..0efa3f46e57 100644 --- a/integration/emulator/tests/block_test.go +++ b/integration/emulator/tests/block_test.go @@ -23,13 +23,14 @@ import ( "fmt" "testing" - "github.com/onflow/flow-go/integration/emulator" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" flowsdk "github.com/onflow/flow-go-sdk" + + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCommitBlock(t *testing.T) { diff --git a/integration/emulator/tests/blockchain_test.go b/integration/emulator/tests/blockchain_test.go index 4b1ef13ab74..b904cf06dc9 100644 --- a/integration/emulator/tests/blockchain_test.go +++ b/integration/emulator/tests/blockchain_test.go @@ -23,16 +23,17 @@ import ( "fmt" "testing" - "github.com/onflow/cadence/stdlib" "github.com/rs/zerolog" - - "github.com/onflow/flow-go/integration/emulator" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/onflow/cadence" + "github.com/onflow/cadence/stdlib" + flowsdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/templates" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/integration/emulator" ) const counterScript = ` diff --git a/integration/emulator/tests/capcons_test.go b/integration/emulator/tests/capcons_test.go index cfe07ac191b..418920c40b6 100644 --- a/integration/emulator/tests/capcons_test.go +++ b/integration/emulator/tests/capcons_test.go @@ -19,10 +19,11 @@ package tests import ( - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/integration/emulator" ) func TestCapabilityControllers(t *testing.T) { diff --git a/integration/emulator/tests/collection_test.go b/integration/emulator/tests/collection_test.go index e0fa4e4c14d..4ac1b469f0b 100644 --- a/integration/emulator/tests/collection_test.go +++ b/integration/emulator/tests/collection_test.go @@ -19,15 +19,16 @@ package tests import ( "context" - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" flowsdk "github.com/onflow/flow-go-sdk" + + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCollections(t *testing.T) { diff --git a/integration/emulator/tests/events_test.go b/integration/emulator/tests/events_test.go index bed43faaad6..1c9bc2dc6c1 100644 --- a/integration/emulator/tests/events_test.go +++ b/integration/emulator/tests/events_test.go @@ -21,19 +21,20 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/rs/zerolog" - - "github.com/onflow/cadence/common" - "github.com/onflow/flow-go-sdk/templates" - flowgo "github.com/onflow/flow-go/model/flow" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence" + "github.com/onflow/cadence/common" + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/templates" + + "github.com/onflow/flow-go/integration/emulator" + flowgo "github.com/onflow/flow-go/model/flow" ) func TestEventEmitted(t *testing.T) { diff --git a/integration/emulator/tests/logs_test.go b/integration/emulator/tests/logs_test.go index 39c7d3e4ead..4089d9e4292 100644 --- a/integration/emulator/tests/logs_test.go +++ b/integration/emulator/tests/logs_test.go @@ -19,11 +19,12 @@ package tests import ( - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/integration/emulator" ) func TestRuntimeLogs(t *testing.T) { diff --git a/integration/emulator/tests/memstore_test.go b/integration/emulator/tests/memstore_test.go index 0c576126727..4cd69313568 100644 --- a/integration/emulator/tests/memstore_test.go +++ b/integration/emulator/tests/memstore_test.go @@ -20,15 +20,16 @@ package tests import ( "context" - "github.com/onflow/flow-go/integration/emulator" "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/flow-go/model/flow" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMemstore(t *testing.T) { diff --git a/integration/emulator/tests/pendingBlock_test.go b/integration/emulator/tests/pendingBlock_test.go index 3e54deb7d77..5aa07255ae5 100644 --- a/integration/emulator/tests/pendingBlock_test.go +++ b/integration/emulator/tests/pendingBlock_test.go @@ -20,16 +20,17 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator" "testing" "time" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" flowsdk "github.com/onflow/flow-go-sdk" + + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func setupPendingBlockTests(t *testing.T) ( diff --git a/integration/emulator/tests/result_test.go b/integration/emulator/tests/result_test.go index c50e890395e..67fb5bc9008 100644 --- a/integration/emulator/tests/result_test.go +++ b/integration/emulator/tests/result_test.go @@ -20,14 +20,16 @@ package tests import ( "errors" - "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/model/flow" "testing" "github.com/onflow/cadence" + "github.com/stretchr/testify/assert" + flowsdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/test" - "github.com/stretchr/testify/assert" + + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/model/flow" ) func TestResult(t *testing.T) { diff --git a/integration/emulator/tests/script_test.go b/integration/emulator/tests/script_test.go index 39234f27bf7..34dc114cf15 100644 --- a/integration/emulator/tests/script_test.go +++ b/integration/emulator/tests/script_test.go @@ -21,18 +21,20 @@ package tests import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator" "testing" "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + flowsdk "github.com/onflow/flow-go-sdk" + fvmerrors "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm/stdlib" + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestExecuteScript(t *testing.T) { diff --git a/integration/emulator/tests/store_test.go b/integration/emulator/tests/store_test.go index bb05a9ce275..77ac9fa8ff5 100644 --- a/integration/emulator/tests/store_test.go +++ b/integration/emulator/tests/store_test.go @@ -21,17 +21,19 @@ package tests_test import ( "context" "fmt" - "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/flow-go/integration/emulator/utils/unittest" "testing" + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/onflow/flow-go-sdk/test" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/integration/emulator" + "github.com/onflow/flow-go/integration/emulator/utils/unittest" "github.com/onflow/flow-go/model/flow" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestBlocks(t *testing.T) { diff --git a/integration/emulator/tests/transaction_test.go b/integration/emulator/tests/transaction_test.go index b4cffa09c43..6dd010158e5 100644 --- a/integration/emulator/tests/transaction_test.go +++ b/integration/emulator/tests/transaction_test.go @@ -25,25 +25,28 @@ import ( "encoding/json" "errors" "fmt" - "github.com/onflow/flow-go/integration/emulator" "io" "strings" "testing" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" + "github.com/onflow/flow-go-sdk" flowsdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/crypto" "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" + fvmerrors "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm/stdlib" + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func setupTransactionTests(t *testing.T, opts ...emulator.Option) ( diff --git a/integration/emulator/tests/vm_test.go b/integration/emulator/tests/vm_test.go index 2d94991ff64..5dd8358ed97 100644 --- a/integration/emulator/tests/vm_test.go +++ b/integration/emulator/tests/vm_test.go @@ -19,14 +19,15 @@ package tests_test import ( - "github.com/onflow/flow-go/integration/emulator" "testing" - "github.com/onflow/flow-go-sdk/test" - "github.com/onflow/flow-go/fvm" "github.com/onflow/flow/protobuf/go/flow/entities" "github.com/stretchr/testify/assert" + "github.com/onflow/flow-go-sdk/test" + + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" ) diff --git a/integration/emulator/utils/unittest/fixtures.go b/integration/emulator/utils/unittest/fixtures.go index 3ae77772759..01337580a5c 100644 --- a/integration/emulator/utils/unittest/fixtures.go +++ b/integration/emulator/utils/unittest/fixtures.go @@ -19,10 +19,12 @@ package unittest import ( + "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/onflow/flow-go-sdk/test" + "github.com/onflow/flow-go/integration/emulator" flowgo "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow/protobuf/go/flow/entities" ) func TransactionFixture() flowgo.TransactionBody { From 47e2da5da31564478174a8470836f7ff4c331a01 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 24 Oct 2024 09:14:24 -0700 Subject: [PATCH 7/9] goimports --- integration/emulator/emulator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration/emulator/emulator.go b/integration/emulator/emulator.go index 4a9772d3df8..91d59be517b 100644 --- a/integration/emulator/emulator.go +++ b/integration/emulator/emulator.go @@ -25,6 +25,7 @@ import ( "github.com/onflow/crypto" "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/access" flowgo "github.com/onflow/flow-go/model/flow" ) From f8f686bdefc73658724ed249ff783e19f437a961 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 24 Oct 2024 09:17:50 -0700 Subject: [PATCH 8/9] goimports --- integration/dkg/dkg_client_test.go | 4 +--- integration/dkg/dkg_emulator_suite.go | 5 ++--- integration/utils/emulator_client.go | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/integration/dkg/dkg_client_test.go b/integration/dkg/dkg_client_test.go index 3c842d7d721..045acdce126 100644 --- a/integration/dkg/dkg_client_test.go +++ b/integration/dkg/dkg_client_test.go @@ -10,10 +10,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/onflow/cadence" - jsoncdc "github.com/onflow/cadence/encoding/json" - emulator "github.com/onflow/flow-go/integration/emulator" - "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" @@ -23,6 +20,7 @@ import ( sdktemplates "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" + emulator "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/flow-go/integration/utils" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/dkg" diff --git a/integration/dkg/dkg_emulator_suite.go b/integration/dkg/dkg_emulator_suite.go index 8768dcddfa7..f8b2904c8c1 100644 --- a/integration/dkg/dkg_emulator_suite.go +++ b/integration/dkg/dkg_emulator_suite.go @@ -14,24 +14,23 @@ import ( jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" - emulator "github.com/onflow/flow-go/integration/emulator" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" sdktemplates "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" - "github.com/onflow/flow-go/module/metrics" - dkgeng "github.com/onflow/flow-go/engine/consensus/dkg" "github.com/onflow/flow-go/engine/testutil" "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/flow-go/integration/tests/lib" "github.com/onflow/flow-go/integration/utils" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/dkg" + "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network/stub" "github.com/onflow/flow-go/state/protocol/events/gadgets" "github.com/onflow/flow-go/storage/badger" diff --git a/integration/utils/emulator_client.go b/integration/utils/emulator_client.go index 9270339c79b..30e3273cddd 100644 --- a/integration/utils/emulator_client.go +++ b/integration/utils/emulator_client.go @@ -6,12 +6,12 @@ import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" - emulator "github.com/onflow/flow-go/integration/emulator" "github.com/rs/zerolog" sdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go/integration/emulator" "github.com/onflow/flow-go/model/flow" ) From 874f6339b2cb89b9a274db2bc725012bf77eb109 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 24 Oct 2024 09:19:43 -0700 Subject: [PATCH 9/9] fix ineffassign? --- integration/emulator/tests/pendingBlock_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/emulator/tests/pendingBlock_test.go b/integration/emulator/tests/pendingBlock_test.go index 5aa07255ae5..408f51a7210 100644 --- a/integration/emulator/tests/pendingBlock_test.go +++ b/integration/emulator/tests/pendingBlock_test.go @@ -444,7 +444,7 @@ func TestPendingBlockSetTimestamp(t *testing.T) { b.SetClock(clock.Now) _, _ = b.CommitBlock() - scriptResult, err = adapter.ExecuteScriptAtLatestBlock( + _, err = adapter.ExecuteScriptAtLatestBlock( context.Background(), script, [][]byte{},