From 6befac0b4ce13b9e4ddc25d858035154d1433f65 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 6 Nov 2024 12:53:30 -0600 Subject: [PATCH 1/5] test(integration): port x/bank tests to server/v2 app (#21912) (cherry picked from commit b5993d6b0357d0ee1ee5f3e95978d100a12ff226) # Conflicts: # server/v2/appmanager/appmanager.go # server/v2/stf/core_header_service.go # tests/go.mod --- codec/depinject.go | 7 + server/v2/appmanager/appmanager.go | 231 +++++++ server/v2/stf/core_header_service.go | 58 ++ simapp/v2/app_di.go | 5 +- tests/go.mod | 46 +- tests/go.sum | 4 + tests/integration/v2/app.go | 411 +++++++++++++ tests/integration/v2/bank/app_test.go | 469 ++++++++++++++ .../v2/bank/determinisitic_test.go | 570 ++++++++++++++++++ tests/integration/v2/genesis.go | 182 ++++++ tests/integration/v2/services.go | 119 ++++ testutil/configurator/configurator.go | 69 +++ testutil/testdata/grpc_query.go | 34 +- 13 files changed, 2198 insertions(+), 7 deletions(-) create mode 100644 server/v2/appmanager/appmanager.go create mode 100644 server/v2/stf/core_header_service.go create mode 100644 tests/integration/v2/app.go create mode 100644 tests/integration/v2/bank/app_test.go create mode 100644 tests/integration/v2/bank/determinisitic_test.go create mode 100644 tests/integration/v2/genesis.go create mode 100644 tests/integration/v2/services.go diff --git a/codec/depinject.go b/codec/depinject.go index c685a3763e15..d172a5813d9a 100644 --- a/codec/depinject.go +++ b/codec/depinject.go @@ -16,6 +16,13 @@ import ( "github.com/cosmos/cosmos-sdk/codec/types" ) +var DefaultProviders = depinject.Provide( + ProvideInterfaceRegistry, + ProvideLegacyAmino, + ProvideProtoCodec, + ProvideAddressCodec, +) + func ProvideInterfaceRegistry( addressCodec address.Codec, validatorAddressCodec address.ValidatorAddressCodec, diff --git a/server/v2/appmanager/appmanager.go b/server/v2/appmanager/appmanager.go new file mode 100644 index 000000000000..597d1c8f4110 --- /dev/null +++ b/server/v2/appmanager/appmanager.go @@ -0,0 +1,231 @@ +package appmanager + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + + "cosmossdk.io/core/server" + corestore "cosmossdk.io/core/store" + "cosmossdk.io/core/transaction" +) + +// AppManager is a coordinator for all things related to an application +// It is responsible for interacting with stf and store. +// Runtime/v2 is an extension of this interface. +type AppManager[T transaction.Tx] interface { + // InitGenesis initializes the genesis state of the application. + InitGenesis( + ctx context.Context, + blockRequest *server.BlockRequest[T], + initGenesisJSON []byte, + txDecoder transaction.Codec[T], + ) (*server.BlockResponse, corestore.WriterMap, error) + + // ExportGenesis exports the genesis state of the application. + ExportGenesis(ctx context.Context, version uint64) ([]byte, error) + + // DeliverBlock executes a block of transactions. + DeliverBlock( + ctx context.Context, + block *server.BlockRequest[T], + ) (*server.BlockResponse, corestore.WriterMap, error) + + // ValidateTx will validate the tx against the latest storage state. This means that + // only the stateful validation will be run, not the execution portion of the tx. + // If full execution is needed, Simulate must be used. + ValidateTx(ctx context.Context, tx T) (server.TxResult, error) + + // Simulate runs validation and execution flow of a Tx. + Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) + + // SimulateWithState runs validation and execution flow of a Tx, + // using the provided state instead of loading the latest state from the underlying database. + SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) + + // Query queries the application at the provided version. + // CONTRACT: Version must always be provided, if 0, get latest + Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) + + // QueryWithState executes a query with the provided state. This allows to process a query + // independently of the db state. For example, it can be used to process a query with temporary + // and uncommitted state + QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) +} + +// Store defines the underlying storage behavior needed by AppManager. +type Store interface { + // StateLatest returns a readonly view over the latest + // committed state of the store. Alongside the version + // associated with it. + StateLatest() (uint64, corestore.ReaderMap, error) + + // StateAt returns a readonly view over the provided + // state. Must error when the version does not exist. + StateAt(version uint64) (corestore.ReaderMap, error) +} + +// appManager is a coordinator for all things related to an application +type appManager[T transaction.Tx] struct { + // Gas limits for validating, querying, and simulating transactions. + config Config + // InitGenesis is a function that initializes the application state from a genesis file. + // It takes a context, a source reader for the genesis file, and a transaction handler function. + initGenesis InitGenesis + // ExportGenesis is a function that exports the application state to a genesis file. + // It takes a context and a version number for the genesis file. + exportGenesis ExportGenesis + // The database for storing application data. + db Store + // The state transition function for processing transactions. + stf StateTransitionFunction[T] +} + +func New[T transaction.Tx]( + config Config, + db Store, + stf StateTransitionFunction[T], + initGenesisImpl InitGenesis, + exportGenesisImpl ExportGenesis, +) AppManager[T] { + return &appManager[T]{ + config: config, + db: db, + stf: stf, + initGenesis: initGenesisImpl, + exportGenesis: exportGenesisImpl, + } +} + +// InitGenesis initializes the genesis state of the application. +func (a appManager[T]) InitGenesis( + ctx context.Context, + blockRequest *server.BlockRequest[T], + initGenesisJSON []byte, + txDecoder transaction.Codec[T], +) (*server.BlockResponse, corestore.WriterMap, error) { + var genTxs []T + genesisState, err := a.initGenesis( + ctx, + bytes.NewBuffer(initGenesisJSON), + func(jsonTx json.RawMessage) error { + genTx, err := txDecoder.DecodeJSON(jsonTx) + if err != nil { + return fmt.Errorf("failed to decode genesis transaction: %w", err) + } + genTxs = append(genTxs, genTx) + return nil + }, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to import genesis state: %w", err) + } + // run block + blockRequest.Txs = genTxs + + blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState) + if err != nil { + return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err) + } + + // after executing block 0, we extract the changes and apply them to the genesis state. + stateChanges, err := blockZeroState.GetStateChanges() + if err != nil { + return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err) + } + + err = genesisState.ApplyStateChanges(stateChanges) + if err != nil { + return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err) + } + + return blockResponse, genesisState, err +} + +// ExportGenesis exports the genesis state of the application. +func (a appManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) { + if a.exportGenesis == nil { + return nil, errors.New("export genesis function not set") + } + + return a.exportGenesis(ctx, version) +} + +// DeliverBlock executes a block of transactions. +func (a appManager[T]) DeliverBlock( + ctx context.Context, + block *server.BlockRequest[T], +) (*server.BlockResponse, corestore.WriterMap, error) { + latestVersion, currentState, err := a.db.StateLatest() + if err != nil { + return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err) + } + + if latestVersion+1 != block.Height { + return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height) + } + + blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState) + if err != nil { + return nil, nil, fmt.Errorf("block delivery failed: %w", err) + } + + return blockResponse, newState, nil +} + +// ValidateTx will validate the tx against the latest storage state. This means that +// only the stateful validation will be run, not the execution portion of the tx. +// If full execution is needed, Simulate must be used. +func (a appManager[T]) ValidateTx(ctx context.Context, tx T) (server.TxResult, error) { + _, latestState, err := a.db.StateLatest() + if err != nil { + return server.TxResult{}, err + } + res := a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx) + return res, res.Error +} + +// Simulate runs validation and execution flow of a Tx. +func (a appManager[T]) Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) { + _, state, err := a.db.StateLatest() + if err != nil { + return server.TxResult{}, nil, err + } + result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler + return result, cs, nil +} + +// SimulateWithState runs validation and execution flow of a Tx, +// using the provided state instead of loading the latest state from the underlying database. +func (a appManager[T]) SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) { + result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler + return result, cs, nil +} + +// Query queries the application at the provided version. +// CONTRACT: Version must always be provided, if 0, get latest +func (a appManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) { + var ( + queryState corestore.ReaderMap + err error + ) + // if version is provided attempt to do a height query. + if version != 0 { + queryState, err = a.db.StateAt(version) + } else { // otherwise rely on latest available state. + _, queryState, err = a.db.StateLatest() + } + if err != nil { + return nil, err + } + return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request) +} + +// QueryWithState executes a query with the provided state. This allows to process a query +// independently of the db state. For example, it can be used to process a query with temporary +// and uncommitted state +func (a appManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) { + return a.stf.Query(ctx, state, a.config.QueryGasLimit, request) +} diff --git a/server/v2/stf/core_header_service.go b/server/v2/stf/core_header_service.go new file mode 100644 index 000000000000..bef2ad8894ea --- /dev/null +++ b/server/v2/stf/core_header_service.go @@ -0,0 +1,58 @@ +package stf + +import ( + "context" + + "cosmossdk.io/core/header" + "cosmossdk.io/core/store" +) + +var _ header.Service = (*HeaderService)(nil) + +type HeaderService struct{} + +func (h HeaderService) HeaderInfo(ctx context.Context) header.Info { + exCtx, err := getExecutionCtxFromContext(ctx) + if err != nil { + panic(err) + } + + return exCtx.headerInfo +} + +const headerInfoPrefix = 0x37 + +// setHeaderInfo sets the header info in the state to be used by queries in the future. +func (s STF[T]) setHeaderInfo(state store.WriterMap, headerInfo header.Info) error { + runtimeStore, err := state.GetWriter(Identity) + if err != nil { + return err + } + bz, err := headerInfo.Bytes() + if err != nil { + return err + } + err = runtimeStore.Set([]byte{headerInfoPrefix}, bz) + if err != nil { + return err + } + return nil +} + +// getHeaderInfo gets the header info from the state. It should only be used for queries +func (s STF[T]) getHeaderInfo(state store.WriterMap) (i header.Info, err error) { + runtimeStore, err := state.GetWriter(Identity) + if err != nil { + return header.Info{}, err + } + v, err := runtimeStore.Get([]byte{headerInfoPrefix}) + if err != nil { + return header.Info{}, err + } + if v == nil { + return header.Info{}, nil + } + + err = i.FromBytes(v) + return i, err +} diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index 4d6c3078cbed..ec506e764d09 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -48,11 +48,8 @@ func AppConfig() depinject.Config { return depinject.Configs( ModuleConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) runtime.DefaultServiceBindings(), + codec.DefaultProviders, depinject.Provide( - codec.ProvideInterfaceRegistry, - codec.ProvideAddressCodec, - codec.ProvideProtoCodec, - codec.ProvideLegacyAmino, ProvideRootStoreConfig, // inject desired account types: multisigdepinject.ProvideAccount, diff --git a/tests/go.mod b/tests/go.mod index e2b0be912560..aba27a5a5537 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -11,7 +11,36 @@ require ( cosmossdk.io/log v1.4.1 cosmossdk.io/math v1.3.0 cosmossdk.io/simapp v0.0.0-20230309163709-87da587416ba +<<<<<<< HEAD cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 // main +======= + cosmossdk.io/store v1.1.1 + cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f + cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f + cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f // indirect + cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 + cosmossdk.io/x/tx v1.0.0-alpha.1 + cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f + github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f + github.com/cosmos/cosmos-proto v1.0.0-beta.5 + // this version is not used as it is always replaced by the latest Cosmos SDK version + github.com/cosmos/cosmos-sdk v0.53.0 + github.com/cosmos/gogoproto v1.7.0 + github.com/spf13/cobra v1.8.1 // indirect + github.com/stretchr/testify v1.9.0 + go.uber.org/mock v0.5.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + gotest.tools/v3 v3.5.1 + pgregory.net/rapid v1.1.0 +) + +require ( + cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 + cosmossdk.io/runtime/v2 v2.0.0-20240911143651-72620a577660 + cosmossdk.io/server/v2/stf v0.0.0-00010101000000-000000000000 + cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 +>>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) cosmossdk.io/x/accounts v0.0.0-20240913065641-0064ccbce64e cosmossdk.io/x/accounts/defaults/base v0.0.0-00010101000000-000000000000 cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5 @@ -45,11 +74,15 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/viper v1.19.0 +<<<<<<< HEAD github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 gotest.tools/v3 v3.5.1 pgregory.net/rapid v1.1.0 +======= + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b +>>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) ) require ( @@ -63,7 +96,9 @@ require ( cloud.google.com/go/storage v1.42.0 // indirect cosmossdk.io/client/v2 v2.0.0-20230630094428-02b760776860 // indirect cosmossdk.io/errors v1.0.1 // indirect + cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 // indirect cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac // indirect + cosmossdk.io/server/v2/appmanager v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/circuit v0.0.0-20230613133644-0a778132a60f // indirect cosmossdk.io/x/epochs v0.0.0-20240522060652-a1ae4c3e0337 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -159,6 +194,7 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -193,7 +229,6 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect go.etcd.io/bbolt v1.4.0-alpha.1 // indirect go.opencensus.io v0.24.0 // indirect @@ -234,8 +269,17 @@ replace ( // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main cosmossdk.io/client/v2 => ../client/v2 +<<<<<<< HEAD // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20240913190136-3bc707a5a214 // main +======= + cosmossdk.io/collections => ../collections + cosmossdk.io/runtime/v2 => ../runtime/v2 + cosmossdk.io/server/v2/appmanager => ../server/v2/appmanager + cosmossdk.io/server/v2/stf => ../server/v2/stf + cosmossdk.io/store => ../store + cosmossdk.io/store/v2 => ../store/v2 +>>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) cosmossdk.io/x/accounts => ../x/accounts cosmossdk.io/x/accounts/defaults/base => ../x/accounts/defaults/base cosmossdk.io/x/accounts/defaults/lockup => ../x/accounts/defaults/lockup diff --git a/tests/go.sum b/tests/go.sum index af4e7693abdc..0c3171cd3f73 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -204,6 +204,8 @@ cosmossdk.io/depinject v1.0.0 h1:dQaTu6+O6askNXO06+jyeUAnF2/ssKwrrszP9t5q050= cosmossdk.io/depinject v1.0.0/go.mod h1:zxK/h3HgHoA/eJVtiSsoaRaRA2D5U4cJ5thIG4ssbB8= cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= +cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 h1:IQNdY2kB+k+1OM2DvqFG1+UgeU1JzZrWtwuWzI3ZfwA= +cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5/go.mod h1:0CuYKkFHxc1vw2JC+t21THBCALJVROrWVR/3PQ1urpc= cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM= cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= @@ -654,6 +656,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= diff --git a/tests/integration/v2/app.go b/tests/integration/v2/app.go new file mode 100644 index 000000000000..7684a539a209 --- /dev/null +++ b/tests/integration/v2/app.go @@ -0,0 +1,411 @@ +package integration + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "math/rand" + "testing" + "time" + + cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" + cmtjson "github.com/cometbft/cometbft/libs/json" + cmttypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/comet" + corecontext "cosmossdk.io/core/context" + "cosmossdk.io/core/server" + corestore "cosmossdk.io/core/store" + "cosmossdk.io/core/transaction" + "cosmossdk.io/depinject" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/runtime/v2" + "cosmossdk.io/runtime/v2/services" + "cosmossdk.io/server/v2/stf" + "cosmossdk.io/server/v2/stf/branch" + "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/root" + bankkeeper "cosmossdk.io/x/bank/keeper" + banktypes "cosmossdk.io/x/bank/types" + consensustypes "cosmossdk.io/x/consensus/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +const DefaultGenTxGas = 10000000 +const ( + Genesis_COMMIT = iota + Genesis_NOCOMMIT + Genesis_SKIP +) + +type stateMachineTx = transaction.Tx + +// DefaultConsensusParams defines the default CometBFT consensus params used in +// SimApp testing. +var DefaultConsensusParams = &cmtproto.ConsensusParams{ + Version: &cmtproto.VersionParams{ + App: 1, + }, + Block: &cmtproto.BlockParams{ + MaxBytes: 200000, + MaxGas: 100_000_000, + }, + Evidence: &cmtproto.EvidenceParams{ + MaxAgeNumBlocks: 302400, + MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration + MaxBytes: 10000, + }, + Validator: &cmtproto.ValidatorParams{ + PubKeyTypes: []string{ + cmttypes.ABCIPubKeyTypeEd25519, + cmttypes.ABCIPubKeyTypeSecp256k1, + }, + }, +} + +// StartupConfig defines the startup configuration of a new test app. +type StartupConfig struct { + // ValidatorSet defines a custom validator set to be validating the app. + ValidatorSet func() (*cmttypes.ValidatorSet, error) + // AppOption defines the additional operations that will be run in the app builder phase. + AppOption runtime.AppBuilderOption[stateMachineTx] + // GenesisBehavior defines the behavior of the app at genesis. + GenesisBehavior int + // GenesisAccounts defines the genesis accounts to be used in the app. + GenesisAccounts []GenesisAccount + // HomeDir defines the home directory of the app where config and data will be stored. + HomeDir string +} + +func DefaultStartUpConfig(t *testing.T) StartupConfig { + t.Helper() + + priv := secp256k1.GenPrivKey() + ba := authtypes.NewBaseAccount( + priv.PubKey().Address().Bytes(), + priv.PubKey(), + 0, + 0, + ) + ga := GenesisAccount{ + ba, + sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000)), + ), + } + homedir := t.TempDir() + t.Logf("generated integration test app config; HomeDir=%s", homedir) + return StartupConfig{ + ValidatorSet: CreateRandomValidatorSet, + GenesisBehavior: Genesis_COMMIT, + GenesisAccounts: []GenesisAccount{ga}, + HomeDir: homedir, + } +} + +// NewApp initializes a new runtime.App. A Nop logger is set in runtime.App. +// appConfig defines the application configuration (f.e. app_config.go). +// extraOutputs defines the extra outputs to be assigned by the dependency injector (depinject). +func NewApp( + appConfig depinject.Config, + startupConfig StartupConfig, + extraOutputs ...interface{}, +) (*App, error) { + // create the app with depinject + var ( + storeBuilder = root.NewBuilder() + app *runtime.App[stateMachineTx] + appBuilder *runtime.AppBuilder[stateMachineTx] + txConfig client.TxConfig + txConfigOptions tx.ConfigOptions + cometService comet.Service = &cometServiceImpl{} + kvFactory corestore.KVStoreServiceFactory = func(actor []byte) corestore.KVStoreService { + return services.NewGenesisKVService(actor, &storeService{actor, stf.NewKVStoreService(actor)}) + } + cdc codec.Codec + err error + ) + + if err := depinject.Inject( + depinject.Configs( + appConfig, + codec.DefaultProviders, + depinject.Supply( + &root.Config{ + Home: startupConfig.HomeDir, + AppDBBackend: "goleveldb", + Options: root.DefaultStoreOptions(), + }, + runtime.GlobalConfig{ + "server": server.ConfigMap{ + "minimum-gas-prices": "0stake", + }, + }, + services.NewGenesisHeaderService(stf.HeaderService{}), + cometService, + kvFactory, + &eventService{}, + storeBuilder, + ), + depinject.Invoke( + std.RegisterInterfaces, + ), + ), + append(extraOutputs, &appBuilder, &cdc, &txConfigOptions, &txConfig, &storeBuilder)...); err != nil { + return nil, fmt.Errorf("failed to inject dependencies: %w", err) + } + + app, err = appBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build app: %w", err) + } + if err := app.LoadLatest(); err != nil { + return nil, fmt.Errorf("failed to load app: %w", err) + } + + store := storeBuilder.Get() + if store == nil { + return nil, fmt.Errorf("failed to build store: %w", err) + } + err = store.SetInitialVersion(1) + if err != nil { + return nil, fmt.Errorf("failed to set initial version: %w", err) + } + + integrationApp := &App{App: app, Store: store, txConfig: txConfig, lastHeight: 1} + if startupConfig.GenesisBehavior == Genesis_SKIP { + return integrationApp, nil + } + + // create validator set + valSet, err := startupConfig.ValidatorSet() + if err != nil { + return nil, errors.New("failed to create validator set") + } + + var ( + balances []banktypes.Balance + genAccounts []authtypes.GenesisAccount + ) + for _, ga := range startupConfig.GenesisAccounts { + genAccounts = append(genAccounts, ga.GenesisAccount) + balances = append( + balances, + banktypes.Balance{ + Address: ga.GenesisAccount.GetAddress().String(), + Coins: ga.Coins, + }, + ) + } + + genesisJSON, err := genesisStateWithValSet( + cdc, + app.DefaultGenesis(), + valSet, + genAccounts, + balances...) + if err != nil { + return nil, fmt.Errorf("failed to create genesis state: %w", err) + } + + // init chain must be called to stop deliverState from being nil + genesisJSONBytes, err := cmtjson.MarshalIndent(genesisJSON, "", " ") + if err != nil { + return nil, fmt.Errorf( + "failed to marshal default genesis state: %w", + err, + ) + } + + ctx := context.WithValue( + context.Background(), + corecontext.CometParamsInitInfoKey, + &consensustypes.MsgUpdateParams{ + Authority: "consensus", + Block: DefaultConsensusParams.Block, + Evidence: DefaultConsensusParams.Evidence, + Validator: DefaultConsensusParams.Validator, + Abci: DefaultConsensusParams.Abci, + Synchrony: DefaultConsensusParams.Synchrony, + Feature: DefaultConsensusParams.Feature, + }, + ) + + emptyHash := sha256.Sum256(nil) + _, genesisState, err := app.InitGenesis( + ctx, + &server.BlockRequest[stateMachineTx]{ + Height: 1, + Time: time.Now(), + Hash: emptyHash[:], + ChainId: "test-chain", + AppHash: emptyHash[:], + IsGenesis: true, + }, + genesisJSONBytes, + &genesisTxCodec{txConfigOptions}, + ) + if err != nil { + return nil, fmt.Errorf("failed init genesis: %w", err) + } + + if startupConfig.GenesisBehavior == Genesis_NOCOMMIT { + integrationApp.lastHeight = 0 + return integrationApp, nil + } + + _, err = integrationApp.Commit(genesisState) + if err != nil { + return nil, fmt.Errorf("failed to commit initial version: %w", err) + } + + return integrationApp, nil +} + +// App is a wrapper around runtime.App that provides additional testing utilities. +type App struct { + *runtime.App[stateMachineTx] + lastHeight uint64 + Store store.RootStore + txConfig client.TxConfig +} + +// Deliver delivers a block with the given transactions and returns the resulting state. +func (a *App) Deliver( + t *testing.T, ctx context.Context, txs []stateMachineTx, +) (*server.BlockResponse, corestore.WriterMap) { + t.Helper() + req := &server.BlockRequest[stateMachineTx]{ + Height: a.lastHeight + 1, + Txs: txs, + Hash: make([]byte, 32), + AppHash: make([]byte, 32), + } + resp, state, err := a.DeliverBlock(ctx, req) + require.NoError(t, err) + a.lastHeight++ + return resp, state +} + +// StateLatestContext creates returns a new context from context.Background() with the latest state. +func (a *App) StateLatestContext(t *testing.T) context.Context { + t.Helper() + _, state, err := a.Store.StateLatest() + require.NoError(t, err) + writeableState := branch.DefaultNewWriterMap(state) + iCtx := &integrationContext{state: writeableState} + return context.WithValue(context.Background(), contextKey, iCtx) +} + +// Commit commits the given state and returns the new state hash. +func (a *App) Commit(state corestore.WriterMap) ([]byte, error) { + changes, err := state.GetStateChanges() + if err != nil { + return nil, fmt.Errorf("failed to get state changes: %w", err) + } + cs := &corestore.Changeset{Changes: changes} + return a.Store.Commit(cs) +} + +// SignCheckDeliver signs and checks the given messages and delivers them. +func (a *App) SignCheckDeliver( + t *testing.T, ctx context.Context, msgs []sdk.Msg, + chainID string, accNums, accSeqs []uint64, privateKeys []cryptotypes.PrivKey, + txErrString string, +) server.TxResult { + t.Helper() + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + sigs := make([]signing.SignatureV2, len(privateKeys)) + + // create a random length memo + memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100)) + + signMode, err := authsign.APISignModeToInternal(a.txConfig.SignModeHandler().DefaultMode()) + require.NoError(t, err) + + // 1st round: set SignatureV2 with empty signatures, to set correct + // signer infos. + for i, p := range privateKeys { + sigs[i] = signing.SignatureV2{ + PubKey: p.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signMode, + }, + Sequence: accSeqs[i], + } + } + + txBuilder := a.txConfig.NewTxBuilder() + err = txBuilder.SetMsgs(msgs...) + require.NoError(t, err) + err = txBuilder.SetSignatures(sigs...) + require.NoError(t, err) + txBuilder.SetMemo(memo) + txBuilder.SetFeeAmount(sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}) + txBuilder.SetGasLimit(DefaultGenTxGas) + + // 2nd round: once all signer infos are set, every signer can sign. + for i, p := range privateKeys { + signerData := authsign.SignerData{ + Address: sdk.AccAddress(p.PubKey().Address()).String(), + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + PubKey: p.PubKey(), + } + + signBytes, err := authsign.GetSignBytesAdapter( + ctx, a.txConfig.SignModeHandler(), signMode, signerData, + // todo why fetch twice? + txBuilder.GetTx()) + require.NoError(t, err) + sig, err := p.Sign(signBytes) + require.NoError(t, err) + sigs[i].Data.(*signing.SingleSignatureData).Signature = sig + } + err = txBuilder.SetSignatures(sigs...) + require.NoError(t, err) + + builtTx := txBuilder.GetTx() + blockResponse, blockState := a.Deliver(t, ctx, []stateMachineTx{builtTx}) + + require.Equal(t, 1, len(blockResponse.TxResults)) + txResult := blockResponse.TxResults[0] + if txErrString != "" { + require.ErrorContains(t, txResult.Error, txErrString) + } else { + require.NoError(t, txResult.Error) + } + + _, err = a.Commit(blockState) + require.NoError(t, err) + + return txResult +} + +// CheckBalance checks the balance of the given address. +func (a *App) CheckBalance( + t *testing.T, ctx context.Context, addr sdk.AccAddress, expected sdk.Coins, keeper bankkeeper.Keeper, +) { + t.Helper() + balances := keeper.GetAllBalances(ctx, addr) + require.Equal(t, expected, balances) +} + +func (a *App) Close() error { + return a.Store.Close() +} diff --git a/tests/integration/v2/bank/app_test.go b/tests/integration/v2/bank/app_test.go new file mode 100644 index 000000000000..22a6c66d5bb9 --- /dev/null +++ b/tests/integration/v2/bank/app_test.go @@ -0,0 +1,469 @@ +package bank + +import ( + "testing" + + "github.com/stretchr/testify/require" + secp256k1_internal "gitlab.com/yawning/secp256k1-voi" + "gitlab.com/yawning/secp256k1-voi/secec" + + "cosmossdk.io/depinject" + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + _ "cosmossdk.io/x/accounts" + _ "cosmossdk.io/x/bank" + bankkeeper "cosmossdk.io/x/bank/keeper" + "cosmossdk.io/x/bank/testutil" + "cosmossdk.io/x/bank/types" + _ "cosmossdk.io/x/consensus" + _ "cosmossdk.io/x/distribution" + distrkeeper "cosmossdk.io/x/distribution/keeper" + _ "cosmossdk.io/x/gov" + govv1 "cosmossdk.io/x/gov/types/v1" + _ "cosmossdk.io/x/protocolpool" + _ "cosmossdk.io/x/staking" + stakingtypes "cosmossdk.io/x/staking/types" + + "github.com/cosmos/cosmos-sdk/client" + cdctestutil "github.com/cosmos/cosmos-sdk/codec/testutil" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" + "github.com/cosmos/cosmos-sdk/testutil/configurator" + sdk "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/x/auth" + _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var ( + stablePrivateKey, _ = secec.NewPrivateKeyFromScalar(secp256k1_internal.NewScalarFromUint64(100)) + priv1 = &secp256k1.PrivKey{Key: stablePrivateKey.Bytes()} + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + priv2 = secp256k1.GenPrivKey() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + halfCoins = sdk.Coins{sdk.NewInt64Coin("foocoin", 5)} + moduleAccAddr = authtypes.NewModuleAddress(stakingtypes.BondedPoolName) +) + +type suite struct { + BankKeeper bankkeeper.Keeper + AccountKeeper types.AccountKeeper + DistributionKeeper distrkeeper.Keeper + App *integration.App + TxConfig client.TxConfig +} + +type expectedBalance struct { + addr sdk.AccAddress + coins sdk.Coins +} + +type appTestCase struct { + desc string + msgs []sdk.Msg + accNums []uint64 + accSeqs []uint64 + privKeys []cryptotypes.PrivKey + expectedBalances []expectedBalance + expInError []string +} + +func createTestSuite(t *testing.T, genesisAccounts []authtypes.GenesisAccount) suite { + t.Helper() + res := suite{} + + moduleConfigs := []configurator.ModuleOption{ + configurator.AccountsModule(), + configurator.AuthModule(), + configurator.StakingModule(), + configurator.TxModule(), + configurator.ValidateModule(), + configurator.ConsensusModule(), + configurator.BankModule(), + configurator.GovModule(), + configurator.DistributionModule(), + configurator.ProtocolPoolModule(), + } + var err error + startupCfg := integration.DefaultStartUpConfig(t) + var genAccounts []integration.GenesisAccount + for _, acc := range genesisAccounts { + genAccounts = append(genAccounts, integration.GenesisAccount{GenesisAccount: acc}) + } + startupCfg.GenesisAccounts = genAccounts + res.App, err = integration.NewApp( + depinject.Configs(configurator.NewAppV2Config(moduleConfigs...), depinject.Supply(log.NewNopLogger())), + startupCfg, + &res.BankKeeper, &res.AccountKeeper, &res.DistributionKeeper, &res.TxConfig) + require.NoError(t, err) + + return res +} + +func TestSendNotEnoughBalance(t *testing.T) { + acc := &authtypes.BaseAccount{ + Address: addr1.String(), + } + + genAccs := []authtypes.GenesisAccount{acc} + s := createTestSuite(t, genAccs) + ctx := s.App.StateLatestContext(t) + + err := testutil.FundAccount( + ctx, s.BankKeeper, addr1, + sdk.NewCoins(sdk.NewInt64Coin("foocoin", 67))) + require.NoError(t, err) + res1 := s.AccountKeeper.GetAccount(ctx, addr1) + require.NotNil(t, res1) + require.Equal(t, acc, res1.(*authtypes.BaseAccount)) + + origAccNum := res1.GetAccountNumber() + origSeq := res1.GetSequence() + addr1Str, err := s.AccountKeeper.AddressCodec().BytesToString(addr1) + require.NoError(t, err) + addr2Str, err := s.AccountKeeper.AddressCodec().BytesToString(addr2) + require.NoError(t, err) + sendMsg := types.NewMsgSend(addr1Str, addr2Str, sdk.Coins{sdk.NewInt64Coin("foocoin", 100)}) + + // TODO how to auto-advance height with app v2 interface? + s.App.SignCheckDeliver( + t, ctx, []sdk.Msg{sendMsg}, "", []uint64{origAccNum}, []uint64{origSeq}, + []cryptotypes.PrivKey{priv1}, + "spendable balance 67foocoin is smaller than 100foocoin", + ) + s.App.CheckBalance(t, ctx, addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, s.BankKeeper) + res2 := s.AccountKeeper.GetAccount(ctx, addr1) + require.NotNil(t, res2) + + require.Equal(t, origAccNum, res2.GetAccountNumber()) + require.Equal(t, origSeq+1, res2.GetSequence()) +} + +func TestMsgMultiSendWithAccounts(t *testing.T) { + addr1Str, err := cdctestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr1) + require.NoError(t, err) + acc := &authtypes.BaseAccount{ + Address: addr1Str, + } + + addr2Str, err := cdctestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr2) + require.NoError(t, err) + + moduleStrAddr, err := cdctestutil.CodecOptions{}.GetAddressCodec().BytesToString(moduleAccAddr) + require.NoError(t, err) + + genAccs := []authtypes.GenesisAccount{acc} + s := createTestSuite(t, genAccs) + ctx := s.App.StateLatestContext(t) + + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 67)))) + + _, state := s.App.Deliver(t, ctx, nil) + _, err = s.App.Commit(state) + require.NoError(t, err) + + res1 := s.AccountKeeper.GetAccount(ctx, addr1) + require.NotNil(t, res1) + require.Equal(t, acc, res1.(*authtypes.BaseAccount)) + + testCases := []appTestCase{ + { + desc: "make a valid tx", + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins)}, + Outputs: []types.Output{types.NewOutput(addr2Str, coins)}, + }}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + privKeys: []cryptotypes.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, + }, + }, + { + desc: "wrong accNum should pass Simulate, but not Deliver", + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins)}, + Outputs: []types.Output{types.NewOutput(addr2Str, coins)}, + }}, + accNums: []uint64{1}, // wrong account number + accSeqs: []uint64{1}, + expInError: []string{"signature verification failed; please verify account number"}, + privKeys: []cryptotypes.PrivKey{priv1}, + }, + { + desc: "wrong accSeq should not pass Simulate", + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins)}, + Outputs: []types.Output{ + types.NewOutput(moduleStrAddr, coins), + }, + }}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, // wrong account sequence + expInError: []string{"account sequence mismatch"}, + privKeys: []cryptotypes.PrivKey{priv1}, + }, + { + desc: "multiple inputs not allowed", + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins), types.NewInput(addr2Str, coins)}, + Outputs: []types.Output{}, + }}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + expInError: []string{"invalid number of signatures"}, + privKeys: []cryptotypes.PrivKey{priv1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + var errString string + if len(tc.expInError) > 0 { + errString = tc.expInError[0] + } + s.App.SignCheckDeliver(t, ctx, tc.msgs, "", tc.accNums, tc.accSeqs, tc.privKeys, errString) + + for _, eb := range tc.expectedBalances { + s.App.CheckBalance(t, ctx, eb.addr, eb.coins, s.BankKeeper) + } + }) + } +} + +func TestMsgMultiSendMultipleOut(t *testing.T) { + ac := cdctestutil.CodecOptions{}.GetAddressCodec() + addr1Str, err := ac.BytesToString(addr1) + require.NoError(t, err) + acc1 := &authtypes.BaseAccount{ + Address: addr1Str, + } + addr2Str, err := ac.BytesToString(addr2) + require.NoError(t, err) + acc2 := &authtypes.BaseAccount{ + Address: addr2Str, + } + addr3Str, err := ac.BytesToString(addr3) + require.NoError(t, err) + + genAccs := []authtypes.GenesisAccount{acc1, acc2} + s := createTestSuite(t, genAccs) + ctx := s.App.StateLatestContext(t) + + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + _, state := s.App.Deliver(t, ctx, nil) + _, err = s.App.Commit(state) + require.NoError(t, err) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins)}, + Outputs: []types.Output{ + types.NewOutput(addr2Str, halfCoins), + types.NewOutput(addr3Str, halfCoins), + }, + }}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + privKeys: []cryptotypes.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}}, + {addr3, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}}, + }, + }, + } + + for _, tc := range testCases { + s.App.SignCheckDeliver(t, ctx, tc.msgs, "", tc.accNums, tc.accSeqs, tc.privKeys, "") + + for _, eb := range tc.expectedBalances { + s.App.CheckBalance(t, ctx, eb.addr, eb.coins, s.BankKeeper) + } + } +} + +func TestMsgMultiSendDependent(t *testing.T) { + ac := cdctestutil.CodecOptions{}.GetAddressCodec() + addr1Str, err := ac.BytesToString(addr1) + require.NoError(t, err) + addr2Str, err := ac.BytesToString(addr2) + require.NoError(t, err) + + acc1 := authtypes.NewBaseAccountWithAddress(addr1) + acc2 := authtypes.NewBaseAccountWithAddress(addr2) + err = acc2.SetAccountNumber(1) + require.NoError(t, err) + + genAccs := []authtypes.GenesisAccount{acc1, acc2} + s := createTestSuite(t, genAccs) + ctx := s.App.StateLatestContext(t) + + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + _, state := s.App.Deliver(t, ctx, nil) + _, err = s.App.Commit(state) + require.NoError(t, err) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr1Str, coins)}, + Outputs: []types.Output{types.NewOutput(addr2Str, coins)}, + }}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + privKeys: []cryptotypes.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, + }, + }, + { + msgs: []sdk.Msg{&types.MsgMultiSend{ + Inputs: []types.Input{types.NewInput(addr2Str, coins)}, + Outputs: []types.Output{ + types.NewOutput(addr1Str, coins), + }, + }}, + accNums: []uint64{1}, + accSeqs: []uint64{0}, + privKeys: []cryptotypes.PrivKey{priv2}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, + }, + }, + } + + for _, tc := range testCases { + s.App.SignCheckDeliver(t, ctx, tc.msgs, "", tc.accNums, tc.accSeqs, tc.privKeys, "") + + for _, eb := range tc.expectedBalances { + s.App.CheckBalance(t, ctx, eb.addr, eb.coins, s.BankKeeper) + } + } +} + +func TestMsgSetSendEnabled(t *testing.T) { + acc1 := authtypes.NewBaseAccountWithAddress(addr1) + + genAccs := []authtypes.GenesisAccount{acc1} + s := createTestSuite(t, genAccs) + + ctx := s.App.StateLatestContext(t) + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 101)))) + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("stake", 100000)))) + addr1Str := addr1.String() + govAddr := s.BankKeeper.GetAuthority() + goodGovProp, err := govv1.NewMsgSubmitProposal( + []sdk.Msg{ + types.NewMsgSetSendEnabled(govAddr, nil, nil), + }, + sdk.Coins{{Denom: "stake", Amount: sdkmath.NewInt(100000)}}, + addr1Str, + "set default send enabled to true", + "Change send enabled", + "Modify send enabled and set to true", + govv1.ProposalType_PROPOSAL_TYPE_STANDARD, + ) + require.NoError(t, err, "making goodGovProp") + + testCases := []appTestCase{ + { + desc: "wrong authority", + msgs: []sdk.Msg{ + types.NewMsgSetSendEnabled(addr1Str, nil, nil), + }, + accSeqs: []uint64{0}, + expInError: []string{ + "invalid authority", + "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", + addr1Str, + "expected authority account as only signer for proposal message", + }, + }, + { + desc: "right authority wrong signer", + msgs: []sdk.Msg{ + types.NewMsgSetSendEnabled(govAddr, nil, nil), + }, + accSeqs: []uint64{1}, // wrong signer, so this sequence doesn't actually get used. + expInError: []string{ + "cannot be claimed by public key with address", + govAddr, + }, + }, + { + desc: "submitted good as gov prop", + msgs: []sdk.Msg{ + goodGovProp, + }, + accSeqs: []uint64{1}, + expInError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(tt *testing.T) { + var errString string + if len(tc.expInError) > 0 { + errString = tc.expInError[0] + } + txResult := s.App.SignCheckDeliver( + tt, ctx, tc.msgs, "", []uint64{0}, tc.accSeqs, []cryptotypes.PrivKey{priv1}, errString) + if len(tc.expInError) > 0 { + require.Error(tt, txResult.Error) + for _, exp := range tc.expInError { + require.ErrorContains(tt, txResult.Error, exp) + } + } else { + require.NoError(tt, txResult.Error) + } + }) + } +} + +// TestSendToNonExistingAccount tests sending coins to an account that does not exist, and this account +// must not be created. +func TestSendToNonExistingAccount(t *testing.T) { + acc1 := authtypes.NewBaseAccountWithAddress(addr1) + genAccs := []authtypes.GenesisAccount{acc1} + s := createTestSuite(t, genAccs) + ctx := s.App.StateLatestContext(t) + + require.NoError(t, testutil.FundAccount(ctx, s.BankKeeper, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + _, state := s.App.Deliver(t, ctx, nil) + _, err := s.App.Commit(state) + require.NoError(t, err) + + addr2Str, err := s.AccountKeeper.AddressCodec().BytesToString(addr2) + require.NoError(t, err) + sendMsg := types.NewMsgSend(addr1.String(), addr2Str, coins) + res := s.App.SignCheckDeliver(t, ctx, []sdk.Msg{sendMsg}, "", []uint64{0}, []uint64{0}, []cryptotypes.PrivKey{priv1}, "") + require.NoError(t, res.Error) + + // Check that the account was not created + acc2 := s.AccountKeeper.GetAccount(ctx, addr2) + require.Nil(t, acc2) + + // But it does have a balance + s.App.CheckBalance(t, ctx, addr2, coins, s.BankKeeper) + + // Now we send coins back and the account should be created + sendMsg = types.NewMsgSend(addr2Str, addr1.String(), coins) + res = s.App.SignCheckDeliver(t, ctx, []sdk.Msg{sendMsg}, "", []uint64{0}, []uint64{0}, []cryptotypes.PrivKey{priv2}, "") + require.NoError(t, res.Error) + + // Balance has been reduced + s.App.CheckBalance(t, ctx, addr2, sdk.NewCoins(), s.BankKeeper) + + // Check that the account was created + acc2 = s.AccountKeeper.GetAccount(ctx, addr2) + require.NotNil(t, acc2, "account should have been created %s", addr2.String()) +} diff --git a/tests/integration/v2/bank/determinisitic_test.go b/tests/integration/v2/bank/determinisitic_test.go new file mode 100644 index 000000000000..28995a0e3f6f --- /dev/null +++ b/tests/integration/v2/bank/determinisitic_test.go @@ -0,0 +1,570 @@ +package bank + +import ( + "context" + "fmt" + "testing" + + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "pgregory.net/rapid" + + "cosmossdk.io/core/gas" + "cosmossdk.io/depinject" + "cosmossdk.io/log" + "cosmossdk.io/math" + bankkeeper "cosmossdk.io/x/bank/keeper" + banktestutil "cosmossdk.io/x/bank/testutil" + banktypes "cosmossdk.io/x/bank/types" + minttypes "cosmossdk.io/x/mint/types" + + codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" + "github.com/cosmos/cosmos-sdk/testutil/configurator" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + authtestutil "github.com/cosmos/cosmos-sdk/x/auth/testutil" +) + +var ( + denomRegex = `[a-zA-Z][a-zA-Z0-9/:._-]{2,127}` + coin1 = sdk.NewCoin("denom", math.NewInt(10)) + metadataAtom = banktypes.Metadata{ + Description: "The native staking token of the Cosmos Hub.", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "uatom", + Exponent: 0, + Aliases: []string{"microatom"}, + }, + { + Denom: "atom", + Exponent: 6, + Aliases: []string{"ATOM"}, + }, + }, + Base: "uatom", + Display: "atom", + } +) + +type deterministicFixture struct { + *testing.T + ctx context.Context + app *integration.App + bankKeeper bankkeeper.Keeper +} + +func queryFnFactory[RequestT, ResponseT proto.Message]( + f *deterministicFixture, +) func(RequestT) (ResponseT, error) { + return func(req RequestT) (ResponseT, error) { + var emptyResponse ResponseT + res, err := f.app.Query(f.ctx, 0, req) + if err != nil { + return emptyResponse, err + } + castedRes, ok := res.(ResponseT) + if !ok { + return emptyResponse, fmt.Errorf("unexpected response type: %T", res) + } + return castedRes, nil + } +} + +func fundAccount(f *deterministicFixture, addr sdk.AccAddress, coin ...sdk.Coin) { + err := banktestutil.FundAccount(f.ctx, f.bankKeeper, addr, sdk.NewCoins(coin...)) + require.NoError(f.T, err) +} + +func getCoin(rt *rapid.T) sdk.Coin { + return sdk.NewCoin( + rapid.StringMatching(denomRegex).Draw(rt, "denom"), + math.NewInt(rapid.Int64Min(1).Draw(rt, "amount")), + ) +} + +func initDeterministicFixture(t *testing.T) *deterministicFixture { + t.Helper() + + ctrl := gomock.NewController(t) + acctsModKeeper := authtestutil.NewMockAccountsModKeeper(ctrl) + accNum := uint64(0) + acctsModKeeper.EXPECT().NextAccountNumber(gomock.Any()).AnyTimes().DoAndReturn(func(ctx context.Context) ( + uint64, error, + ) { + currentNum := accNum + accNum++ + return currentNum, nil + }) + + startupConfig := integration.DefaultStartUpConfig(t) + startupConfig.GenesisBehavior = integration.Genesis_SKIP + diConfig := configurator.NewAppV2Config( + configurator.TxModule(), + configurator.AuthModule(), + configurator.BankModule(), + ) + + var bankKeeper bankkeeper.Keeper + diConfig = depinject.Configs(diConfig, depinject.Supply(acctsModKeeper, log.NewNopLogger())) + app, err := integration.NewApp(diConfig, startupConfig, &bankKeeper) + require.NoError(t, err) + require.NotNil(t, app) + return &deterministicFixture{app: app, bankKeeper: bankKeeper, T: t} +} + +func assertNonZeroGas(t *testing.T, gasUsed gas.Gas) { + t.Helper() + require.NotZero(t, gasUsed) +} + +func TestQueryBalance(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryBalanceRequest, *banktypes.QueryBalanceResponse](f) + assertBalance := func(coin sdk.Coin) func(t *testing.T, res *banktypes.QueryBalanceResponse) { + return func(t *testing.T, res *banktypes.QueryBalanceResponse) { + t.Helper() + require.Equal(t, coin.Denom, res.Balance.Denom) + require.Truef(t, coin.Amount.Equal(res.Balance.Amount), + "expected %s, got %s", coin.Amount, res.Balance.Amount) + } + } + + rapid.Check(t, func(rt *rapid.T) { + addr := testdata.AddressGenerator(rt).Draw(rt, "address") + coin := getCoin(rt) + fundAccount(f, addr, coin) + + addrStr, err := codectestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr) + require.NoError(t, err) + + req := banktypes.NewQueryBalanceRequest(addrStr, coin.GetDenom()) + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, assertBalance(coin)) + }) + + fundAccount(f, addr1, coin1) + addr1Str, err := codectestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr1) + require.NoError(t, err) + req := banktypes.NewQueryBalanceRequest(addr1Str, coin1.GetDenom()) + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, assertBalance(coin1)) +} + +func TestQueryAllBalances(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + addressCodec := codectestutil.CodecOptions{}.GetAddressCodec() + queryFn := queryFnFactory[*banktypes.QueryAllBalancesRequest, *banktypes.QueryAllBalancesResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + addr := testdata.AddressGenerator(rt).Draw(rt, "address") + numCoins := rapid.IntRange(1, 10).Draw(rt, "num-count") + coins := make(sdk.Coins, 0, numCoins) + + addrStr, err := addressCodec.BytesToString(addr) + require.NoError(t, err) + + for i := 0; i < numCoins; i++ { + coin := getCoin(rt) + if exists, _ := coins.Find(coin.Denom); exists { + t.Skip("duplicate denom") + } + // NewCoins sorts the denoms + coins = sdk.NewCoins(append(coins, coin)...) + } + + fundAccount(f, addr, coins...) + + req := banktypes.NewQueryAllBalancesRequest( + addrStr, testdata.PaginationGenerator(rt, uint64(numCoins)).Draw(rt, "pagination"), false) + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + coins := sdk.NewCoins( + sdk.NewCoin("stake", math.NewInt(10)), + sdk.NewCoin("denom", math.NewInt(100)), + ) + + fundAccount(f, addr1, coins...) + addr1Str, err := addressCodec.BytesToString(addr1) + require.NoError(t, err) + + req := banktypes.NewQueryAllBalancesRequest(addr1Str, nil, false) + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestQuerySpendableBalances(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QuerySpendableBalancesRequest, *banktypes.QuerySpendableBalancesResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + addr := testdata.AddressGenerator(rt).Draw(rt, "address") + addrStr, err := codectestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr) + require.NoError(t, err) + + // Denoms must be unique, otherwise sdk.NewCoins will panic. + denoms := rapid.SliceOfNDistinct(rapid.StringMatching(denomRegex), 1, 10, rapid.ID[string]).Draw(rt, "denoms") + coins := make(sdk.Coins, 0, len(denoms)) + for _, denom := range denoms { + coin := sdk.NewCoin( + denom, + math.NewInt(rapid.Int64Min(1).Draw(rt, "amount")), + ) + + // NewCoins sorts the denoms + coins = sdk.NewCoins(append(coins, coin)...) + } + + err = banktestutil.FundAccount(f.ctx, f.bankKeeper, addr, coins) + require.NoError(t, err) + + req := banktypes.NewQuerySpendableBalancesRequest(addrStr, testdata.PaginationGenerator(rt, uint64(len(denoms))).Draw(rt, "pagination")) + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + coins := sdk.NewCoins( + sdk.NewCoin("stake", math.NewInt(10)), + sdk.NewCoin("denom", math.NewInt(100)), + ) + + err := banktestutil.FundAccount(f.ctx, f.bankKeeper, addr1, coins) + require.NoError(t, err) + + addr1Str, err := codectestutil.CodecOptions{}.GetAddressCodec().BytesToString(addr1) + require.NoError(t, err) + + req := banktypes.NewQuerySpendableBalancesRequest(addr1Str, nil) + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestQueryTotalSupply(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryTotalSupplyRequest, *banktypes.QueryTotalSupplyResponse](f) + + res, err := queryFn(&banktypes.QueryTotalSupplyRequest{}) + require.NoError(t, err) + initialSupply := res.GetSupply() + + rapid.Check(t, func(rt *rapid.T) { + numCoins := rapid.IntRange(1, 3).Draw(rt, "num-count") + coins := make(sdk.Coins, 0, numCoins) + + for i := 0; i < numCoins; i++ { + coin := sdk.NewCoin( + rapid.StringMatching(denomRegex).Draw(rt, "denom"), + math.NewInt(rapid.Int64Min(1).Draw(rt, "amount")), + ) + + coins = coins.Add(coin) + } + + require.NoError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, coins)) + + initialSupply = initialSupply.Add(coins...) + + req := &banktypes.QueryTotalSupplyRequest{ + Pagination: testdata.PaginationGenerator(rt, uint64(len(initialSupply))).Draw(rt, "pagination"), + } + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + f = initDeterministicFixture(t) // reset + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory = integration.GasMeterFactory(f.ctx) + queryFn = queryFnFactory[*banktypes.QueryTotalSupplyRequest, *banktypes.QueryTotalSupplyResponse](f) + + coins := sdk.NewCoins( + sdk.NewCoin("foo", math.NewInt(10)), + sdk.NewCoin("bar", math.NewInt(100)), + ) + + require.NoError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, coins)) + + req := &banktypes.QueryTotalSupplyRequest{} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestQueryTotalSupplyOf(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QuerySupplyOfRequest, *banktypes.QuerySupplyOfResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + coin := sdk.NewCoin( + rapid.StringMatching(denomRegex).Draw(rt, "denom"), + math.NewInt(rapid.Int64Min(1).Draw(rt, "amount")), + ) + + require.NoError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, sdk.NewCoins(coin))) + + req := &banktypes.QuerySupplyOfRequest{Denom: coin.GetDenom()} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + coin := sdk.NewCoin("bar", math.NewInt(100)) + + require.NoError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, sdk.NewCoins(coin))) + req := &banktypes.QuerySupplyOfRequest{Denom: coin.GetDenom()} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestQueryParams(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryParamsRequest, *banktypes.QueryParamsResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + enabledStatus := banktypes.SendEnabled{ + Denom: rapid.StringMatching(denomRegex).Draw(rt, "denom"), + Enabled: rapid.Bool().Draw(rt, "status"), + } + + params := banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{&enabledStatus}, + DefaultSendEnabled: rapid.Bool().Draw(rt, "send"), + } + + err := f.bankKeeper.SetParams(f.ctx, params) + require.NoError(t, err) + + req := &banktypes.QueryParamsRequest{} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + enabledStatus := banktypes.SendEnabled{ + Denom: "denom", + Enabled: true, + } + + params := banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{&enabledStatus}, + DefaultSendEnabled: false, + } + + err := f.bankKeeper.SetParams(f.ctx, params) + require.NoError(t, err) + req := &banktypes.QueryParamsRequest{} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func createAndReturnMetadatas(t *rapid.T, count int) []banktypes.Metadata { + denomsMetadata := make([]banktypes.Metadata, 0, count) + for i := 0; i < count; i++ { + + denom := rapid.StringMatching(denomRegex).Draw(t, "denom") + + aliases := rapid.SliceOf(rapid.String()).Draw(t, "aliases") + // In the GRPC server code, empty arrays are returned as nil + if len(aliases) == 0 { + aliases = nil + } + + metadata := banktypes.Metadata{ + Description: rapid.StringN(1, 100, 100).Draw(t, "desc"), + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: denom, + Exponent: rapid.Uint32().Draw(t, "exponent"), + Aliases: aliases, + }, + }, + Base: denom, + Display: denom, + Name: rapid.String().Draw(t, "name"), + Symbol: rapid.String().Draw(t, "symbol"), + URI: rapid.String().Draw(t, "uri"), + URIHash: rapid.String().Draw(t, "uri-hash"), + } + + denomsMetadata = append(denomsMetadata, metadata) + } + + return denomsMetadata +} + +func TestDenomsMetadata(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryDenomsMetadataRequest, *banktypes.QueryDenomsMetadataResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + count := rapid.IntRange(1, 3).Draw(rt, "count") + denomsMetadata := createAndReturnMetadatas(rt, count) + require.True(t, len(denomsMetadata) == count) + + for i := 0; i < count; i++ { + f.bankKeeper.SetDenomMetaData(f.ctx, denomsMetadata[i]) + } + + req := &banktypes.QueryDenomsMetadataRequest{ + Pagination: testdata.PaginationGenerator(rt, uint64(count)).Draw(rt, "pagination"), + } + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + require.NoError(t, f.app.Close()) + + f = initDeterministicFixture(t) // reset + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory = integration.GasMeterFactory(f.ctx) + queryFn = queryFnFactory[*banktypes.QueryDenomsMetadataRequest, *banktypes.QueryDenomsMetadataResponse](f) + + f.bankKeeper.SetDenomMetaData(f.ctx, metadataAtom) + + req := &banktypes.QueryDenomsMetadataRequest{} + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestDenomMetadata(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryDenomMetadataRequest, *banktypes.QueryDenomMetadataResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + denomMetadata := createAndReturnMetadatas(rt, 1) + require.True(t, len(denomMetadata) == 1) + f.bankKeeper.SetDenomMetaData(f.ctx, denomMetadata[0]) + + req := &banktypes.QueryDenomMetadataRequest{ + Denom: denomMetadata[0].Base, + } + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + f.bankKeeper.SetDenomMetaData(f.ctx, metadataAtom) + + req := &banktypes.QueryDenomMetadataRequest{ + Denom: metadataAtom.Base, + } + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestSendEnabled(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QuerySendEnabledRequest, *banktypes.QuerySendEnabledResponse](f) + allDenoms := []string{} + + rapid.Check(t, func(rt *rapid.T) { + count := rapid.IntRange(0, 10).Draw(rt, "count") + denoms := make([]string, 0, count) + + for i := 0; i < count; i++ { + coin := banktypes.SendEnabled{ + Denom: rapid.StringMatching(denomRegex).Draw(rt, "denom"), + Enabled: rapid.Bool().Draw(rt, "enabled-status"), + } + + f.bankKeeper.SetSendEnabled(f.ctx, coin.Denom, coin.Enabled) + denoms = append(denoms, coin.Denom) + } + + allDenoms = append(allDenoms, denoms...) + + req := &banktypes.QuerySendEnabledRequest{ + Denoms: denoms, + // Pagination is only taken into account when `denoms` is an empty array + Pagination: testdata.PaginationGenerator(rt, uint64(len(allDenoms))).Draw(rt, "pagination"), + } + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + coin1 := banktypes.SendEnabled{ + Denom: "falsecoin", + Enabled: false, + } + coin2 := banktypes.SendEnabled{ + Denom: "truecoin", + Enabled: true, + } + + f.bankKeeper.SetSendEnabled(f.ctx, coin1.Denom, false) + f.bankKeeper.SetSendEnabled(f.ctx, coin2.Denom, true) + + req := &banktypes.QuerySendEnabledRequest{ + Denoms: []string{coin1.GetDenom(), coin2.GetDenom()}, + } + + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} + +func TestDenomOwners(t *testing.T) { + t.Parallel() + f := initDeterministicFixture(t) + f.ctx = f.app.StateLatestContext(t) + gasMeterFactory := integration.GasMeterFactory(f.ctx) + queryFn := queryFnFactory[*banktypes.QueryDenomOwnersRequest, *banktypes.QueryDenomOwnersResponse](f) + + rapid.Check(t, func(rt *rapid.T) { + denom := rapid.StringMatching(denomRegex).Draw(rt, "denom") + numAddr := rapid.IntRange(1, 10).Draw(rt, "number-address") + for i := 0; i < numAddr; i++ { + addr := testdata.AddressGenerator(rt).Draw(rt, "address") + + coin := sdk.NewCoin( + denom, + math.NewInt(rapid.Int64Min(1).Draw(rt, "amount")), + ) + + err := banktestutil.FundAccount(f.ctx, f.bankKeeper, addr, sdk.NewCoins(coin)) + require.NoError(t, err) + } + + req := &banktypes.QueryDenomOwnersRequest{ + Denom: denom, + Pagination: testdata.PaginationGenerator(rt, uint64(numAddr)).Draw(rt, "pagination"), + } + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) + }) + + denomOwners := []*banktypes.DenomOwner{ + { + Address: "cosmos1qg65a9q6k2sqq7l3ycp428sqqpmqcucgzze299", + Balance: coin1, + }, + { + Address: "cosmos1qglnsqgpq48l7qqzgs8qdshr6fh3gqq9ej3qut", + Balance: coin1, + }, + } + + for i := 0; i < len(denomOwners); i++ { + addr, err := sdk.AccAddressFromBech32(denomOwners[i].Address) + require.NoError(t, err) + + err = banktestutil.FundAccount(f.ctx, f.bankKeeper, addr, sdk.NewCoins(coin1)) + require.NoError(t, err) + } + + req := &banktypes.QueryDenomOwnersRequest{ + Denom: coin1.GetDenom(), + } + testdata.DeterministicIterationsV2(t, req, gasMeterFactory, queryFn, assertNonZeroGas, nil) +} diff --git a/tests/integration/v2/genesis.go b/tests/integration/v2/genesis.go new file mode 100644 index 000000000000..d101ce3e8672 --- /dev/null +++ b/tests/integration/v2/genesis.go @@ -0,0 +1,182 @@ +package integration + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + cmttypes "github.com/cometbft/cometbft/types" + + sdkmath "cosmossdk.io/math" + banktypes "cosmossdk.io/x/bank/types" + stakingtypes "cosmossdk.io/x/staking/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// genesisStateWithValSet returns a new genesis state with the validator set +func genesisStateWithValSet( + codec codec.Codec, + genesisState map[string]json.RawMessage, + valSet *cmttypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + balances ...banktypes.Balance, +) (map[string]json.RawMessage, error) { + if len(genAccs) == 0 { + return nil, errors.New("no genesis accounts provided") + } + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = codec.MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.DefaultPowerReduction + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromCmtPubKeyInterface(val.PubKey) + if err != nil { + return nil, fmt.Errorf("failed to convert pubkey: %w", err) + } + + pkAny, err := codectypes.NewAnyWithValue(pk) + if err != nil { + return nil, fmt.Errorf("failed to create new any: %w", err) + } + + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdkmath.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission( + sdkmath.LegacyZeroDec(), + sdkmath.LegacyZeroDec(), + sdkmath.LegacyZeroDec(), + ), + MinSelfDelegation: sdkmath.ZeroInt(), + } + validators = append(validators, validator) + delegations = append( + delegations, + stakingtypes.NewDelegation( + genAccs[0].GetAddress().String(), + sdk.ValAddress(val.Address).String(), + sdkmath.LegacyOneDec(), + ), + ) + + } + + // set validators and delegations + stakingGenesis := stakingtypes.NewGenesisState( + stakingtypes.DefaultParams(), + validators, + delegations, + ) + genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON( + stakingGenesis, + ) + + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens to total supply + totalSupply = totalSupply.Add(b.Coins...) + } + + for range delegations { + // add delegated tokens to total supply + totalSupply = totalSupply.Add( + sdk.NewCoin(sdk.DefaultBondDenom, bondAmt), + ) + } + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName). + String(), + Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)}, + }) + + // update total supply + bankGenesis := banktypes.NewGenesisState( + banktypes.DefaultGenesisState().Params, + balances, + totalSupply, + []banktypes.Metadata{}, + []banktypes.SendEnabled{}, + ) + genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis) + + return genesisState, nil +} + +// CreateRandomValidatorSet creates a validator set with one random validator +func CreateRandomValidatorSet() (*cmttypes.ValidatorSet, error) { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + if err != nil { + return nil, fmt.Errorf("failed to get pub key: %w", err) + } + + // create validator set with single validator + validator := cmttypes.NewValidator(pubKey, 1) + + return cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}), nil +} + +type GenesisAccount struct { + authtypes.GenesisAccount + Coins sdk.Coins +} + +type genesisTxCodec struct { + tx.ConfigOptions +} + +// Decode implements transaction.Codec. +func (t *genesisTxCodec) Decode(bz []byte) (stateMachineTx, error) { + var out stateMachineTx + tx, err := t.ProtoDecoder(bz) + if err != nil { + return out, err + } + + var ok bool + out, ok = tx.(stateMachineTx) + if !ok { + return out, errors.New("unexpected Tx type") + } + + return out, nil +} + +// DecodeJSON implements transaction.Codec. +func (t *genesisTxCodec) DecodeJSON(bz []byte) (stateMachineTx, error) { + var out stateMachineTx + tx, err := t.JSONDecoder(bz) + if err != nil { + return out, err + } + + var ok bool + out, ok = tx.(stateMachineTx) + if !ok { + return out, errors.New("unexpected Tx type") + } + + return out, nil +} diff --git a/tests/integration/v2/services.go b/tests/integration/v2/services.go new file mode 100644 index 000000000000..f69aa70574a0 --- /dev/null +++ b/tests/integration/v2/services.go @@ -0,0 +1,119 @@ +package integration + +import ( + "context" + "fmt" + + "cosmossdk.io/core/comet" + "cosmossdk.io/core/event" + "cosmossdk.io/core/gas" + "cosmossdk.io/core/server" + corestore "cosmossdk.io/core/store" + "cosmossdk.io/core/transaction" + stfgas "cosmossdk.io/server/v2/stf/gas" +) + +func (c cometServiceImpl) CometInfo(context.Context) comet.Info { + return comet.Info{} +} + +// Services + +var _ server.DynamicConfig = &dynamicConfigImpl{} + +type dynamicConfigImpl struct { + homeDir string +} + +func (d *dynamicConfigImpl) Get(key string) any { + return d.GetString(key) +} + +func (d *dynamicConfigImpl) GetString(key string) string { + switch key { + case "home": + return d.homeDir + case "store.app-db-backend": + return "goleveldb" + case "server.minimum-gas-prices": + return "0stake" + default: + panic(fmt.Sprintf("unknown key: %s", key)) + } +} + +func (d *dynamicConfigImpl) UnmarshalSub(string, any) (bool, error) { + return false, nil +} + +var _ comet.Service = &cometServiceImpl{} + +type cometServiceImpl struct{} + +type storeService struct { + actor []byte + executionService corestore.KVStoreService +} + +type contextKeyType struct{} + +var contextKey = contextKeyType{} + +type integrationContext struct { + state corestore.WriterMap + gasMeter gas.Meter +} + +func GasMeterFromContext(ctx context.Context) gas.Meter { + iCtx, ok := ctx.Value(contextKey).(*integrationContext) + if !ok { + return nil + } + return iCtx.gasMeter +} + +func GasMeterFactory(ctx context.Context) func() gas.Meter { + return func() gas.Meter { + return GasMeterFromContext(ctx) + } +} + +func (s storeService) OpenKVStore(ctx context.Context) corestore.KVStore { + const gasLimit = 100_000 + iCtx, ok := ctx.Value(contextKey).(*integrationContext) + if !ok { + return s.executionService.OpenKVStore(ctx) + } + + iCtx.gasMeter = stfgas.NewMeter(gasLimit) + writerMap := stfgas.NewMeteredWriterMap(stfgas.DefaultConfig, iCtx.gasMeter, iCtx.state) + state, err := writerMap.GetWriter(s.actor) + if err != nil { + panic(err) + } + return state +} + +var ( + _ event.Service = &eventService{} + _ event.Manager = &eventManager{} +) + +type eventService struct{} + +// EventManager implements event.Service. +func (e *eventService) EventManager(context.Context) event.Manager { + return &eventManager{} +} + +type eventManager struct{} + +// Emit implements event.Manager. +func (e *eventManager) Emit(event transaction.Msg) error { + return nil +} + +// EmitKV implements event.Manager. +func (e *eventManager) EmitKV(eventType string, attrs ...event.Attribute) error { + return nil +} diff --git a/testutil/configurator/configurator.go b/testutil/configurator/configurator.go index f6114e704f1f..98b248cb361b 100644 --- a/testutil/configurator/configurator.go +++ b/testutil/configurator/configurator.go @@ -3,6 +3,7 @@ package configurator import ( accountsmodulev1 "cosmossdk.io/api/cosmos/accounts/module/v1" runtimev1alpha1 "cosmossdk.io/api/cosmos/app/runtime/v1alpha1" + runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1" authzmodulev1 "cosmossdk.io/api/cosmos/authz/module/v1" @@ -440,3 +441,71 @@ func NewAppConfig(opts ...ModuleOption) depinject.Config { return appconfig.Compose(&appv1alpha1.Config{Modules: modules}) } + +func NewAppV2Config(opts ...ModuleOption) depinject.Config { + cfg := defaultConfig() + for _, opt := range opts { + opt(cfg) + } + + preBlockers := make([]string, 0) + beginBlockers := make([]string, 0) + endBlockers := make([]string, 0) + initGenesis := make([]string, 0) + overrides := make([]*runtimev2.StoreKeyConfig, 0) + + for _, s := range cfg.PreBlockersOrder { + if _, ok := cfg.ModuleConfigs[s]; ok { + preBlockers = append(preBlockers, s) + } + } + + for _, s := range cfg.BeginBlockersOrder { + if _, ok := cfg.ModuleConfigs[s]; ok { + beginBlockers = append(beginBlockers, s) + } + } + + for _, s := range cfg.EndBlockersOrder { + if _, ok := cfg.ModuleConfigs[s]; ok { + endBlockers = append(endBlockers, s) + } + } + + for _, s := range cfg.InitGenesisOrder { + if _, ok := cfg.ModuleConfigs[s]; ok { + initGenesis = append(initGenesis, s) + } + } + + if _, ok := cfg.ModuleConfigs[testutil.AuthModuleName]; ok { + overrides = append(overrides, &runtimev2.StoreKeyConfig{ModuleName: testutil.AuthModuleName, KvStoreKey: "acc"}) + } + + runtimeConfig := &runtimev2.Module{ + AppName: "TestApp", + PreBlockers: preBlockers, + BeginBlockers: beginBlockers, + EndBlockers: endBlockers, + OverrideStoreKeys: overrides, + GasConfig: &runtimev2.GasConfig{ + ValidateTxGasLimit: 100_000, + QueryGasLimit: 100_000, + SimulationGasLimit: 100_000, + }, + } + if cfg.setInitGenesis { + runtimeConfig.InitGenesis = initGenesis + } + + modules := []*appv1alpha1.ModuleConfig{{ + Name: "runtime", + Config: appconfig.WrapAny(runtimeConfig), + }} + + for _, m := range cfg.ModuleConfigs { + modules = append(modules, m) + } + + return appconfig.Compose(&appv1alpha1.Config{Modules: modules}) +} diff --git a/testutil/testdata/grpc_query.go b/testutil/testdata/grpc_query.go index d4ca6c73d888..5bccb5e8b04d 100644 --- a/testutil/testdata/grpc_query.go +++ b/testutil/testdata/grpc_query.go @@ -5,13 +5,15 @@ import ( "fmt" "testing" + "github.com/cosmos/gogoproto/proto" gogoprotoany "github.com/cosmos/gogoproto/types/any" "github.com/cosmos/gogoproto/types/any/test" - - "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" "google.golang.org/grpc" "gotest.tools/v3/assert" + "cosmossdk.io/core/gas" + "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -84,6 +86,7 @@ func DeterministicIterations[request, response proto.Message]( if gasOverwrite { // to handle regressions, i.e. check that gas consumption didn't change gasConsumed = ctx.GasMeter().GasConsumed() - before } + t.Logf("gas consumed: %d", gasConsumed) for i := 0; i < iterCount; i++ { before := ctx.GasMeter().GasConsumed() @@ -93,3 +96,30 @@ func DeterministicIterations[request, response proto.Message]( assert.DeepEqual(t, res, prevRes) } } + +func DeterministicIterationsV2[request, response proto.Message]( + t *testing.T, + req request, + meterFn func() gas.Meter, + queryFn func(request) (response, error), + assertGas func(*testing.T, gas.Gas), + assertResponse func(*testing.T, response), +) { + t.Helper() + prevRes, err := queryFn(req) + gasMeter := meterFn() + gasConsumed := gasMeter.Consumed() + require.NoError(t, err) + assertGas(t, gasConsumed) + + for i := 0; i < iterCount; i++ { + res, err := queryFn(req) + require.NoError(t, err) + sameGas := gasMeter.Consumed() + require.Equal(t, gasConsumed, sameGas) + require.Equal(t, res, prevRes) + if assertResponse != nil { + assertResponse(t, res) + } + } +} From 4589fa30d79e9a4e9eb6d1aa0e1aee0e2fd0cfcc Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 7 Nov 2024 12:57:44 -0600 Subject: [PATCH 2/5] try to fix up go.mod --- tests/go.mod | 61 ++++++++++++++-------------------------------------- tests/go.sum | 36 +++++++++++++++++++------------ 2 files changed, 38 insertions(+), 59 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index aba27a5a5537..2b5d0cd9130b 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,14 +6,11 @@ require ( cosmossdk.io/api v0.8.0 // main cosmossdk.io/collections v0.4.1-0.20241031202146-5b7fc8ae90a7 // main cosmossdk.io/core v1.0.0-alpha.5 // main - cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 // main - cosmossdk.io/depinject v1.0.0 + cosmossdk.io/core/testing v0.0.0 // main + cosmossdk.io/depinject v1.1.0 cosmossdk.io/log v1.4.1 cosmossdk.io/math v1.3.0 cosmossdk.io/simapp v0.0.0-20230309163709-87da587416ba -<<<<<<< HEAD - cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 // main -======= cosmossdk.io/store v1.1.1 cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f @@ -36,11 +33,7 @@ require ( ) require ( - cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 - cosmossdk.io/runtime/v2 v2.0.0-20240911143651-72620a577660 - cosmossdk.io/server/v2/stf v0.0.0-00010101000000-000000000000 - cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 ->>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) + cosmossdk.io/runtime/v2 v2.0.0-20241107153845-4e240908dd60 cosmossdk.io/x/accounts v0.0.0-20240913065641-0064ccbce64e cosmossdk.io/x/accounts/defaults/base v0.0.0-00010101000000-000000000000 cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5 @@ -49,40 +42,25 @@ require ( cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000 cosmossdk.io/x/distribution v0.0.0-20240227221813-a248d05f70f4 - cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f - cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f cosmossdk.io/x/gov v0.0.0-20231113122742-912390d5fc4a cosmossdk.io/x/group v0.0.0-00010101000000-000000000000 cosmossdk.io/x/mint v0.0.0-00010101000000-000000000000 - cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f // indirect - cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 cosmossdk.io/x/slashing v0.0.0-00010101000000-000000000000 cosmossdk.io/x/staking v0.0.0-20240226161501-23359a0b6d91 - cosmossdk.io/x/tx v1.0.0-alpha.1 // main - cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f - github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f github.com/cometbft/cometbft/api v1.0.0-rc.1 github.com/cosmos/cosmos-db v1.0.3-0.20240911104526-ddc3f09bfc22 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.5 - // this version is not used as it is always replaced by the latest Cosmos SDK version - github.com/cosmos/cosmos-sdk v0.52.0 - github.com/cosmos/gogoproto v1.7.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.0 github.com/jhump/protoreflect v1.17.0 github.com/rs/zerolog v1.33.0 - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/viper v1.19.0 -<<<<<<< HEAD - github.com/stretchr/testify v1.9.0 - google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.35.1 - gotest.tools/v3 v3.5.1 - pgregory.net/rapid v1.1.0 -======= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b ->>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) +) + +require ( + cosmossdk.io/server/v2/stf v0.0.0-20241107153845-4e240908dd60 + cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 ) require ( @@ -98,7 +76,7 @@ require ( cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 // indirect cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac // indirect - cosmossdk.io/server/v2/appmanager v0.0.0-00010101000000-000000000000 // indirect + cosmossdk.io/server/v2/appmanager v0.0.0-20241107153845-4e240908dd60 // indirect cosmossdk.io/x/circuit v0.0.0-20230613133644-0a778132a60f // indirect cosmossdk.io/x/epochs v0.0.0-20240522060652-a1ae4c3e0337 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -128,7 +106,7 @@ require ( github.com/cosmos/crypto v0.1.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/iavl v1.3.0 // indirect + github.com/cosmos/iavl v1.3.1 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/danieljoos/wincred v1.2.1 // indirect @@ -141,7 +119,7 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/getsentry/sentry-go v0.29.0 // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -207,9 +185,9 @@ require ( github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect @@ -242,7 +220,7 @@ require ( golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect @@ -269,17 +247,10 @@ replace ( // pseudo version lower than the latest tag cosmossdk.io/api => cosmossdk.io/api v0.7.3-0.20240924065902-eb7653cfecdf // main cosmossdk.io/client/v2 => ../client/v2 -<<<<<<< HEAD + cosmossdk.io/core/testing => cosmossdk.io/core/testing v0.0.0-20241107153845-4e240908dd60 // pseudo version lower than the latest tag cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20240913190136-3bc707a5a214 // main -======= - cosmossdk.io/collections => ../collections - cosmossdk.io/runtime/v2 => ../runtime/v2 - cosmossdk.io/server/v2/appmanager => ../server/v2/appmanager - cosmossdk.io/server/v2/stf => ../server/v2/stf - cosmossdk.io/store => ../store - cosmossdk.io/store/v2 => ../store/v2 ->>>>>>> b5993d6b0 (test(integration): port x/bank tests to server/v2 app (#21912)) + cosmossdk.io/store/v2 => cosmossdk.io/store/v2 v2.0.0-20241107153845-4e240908dd60 cosmossdk.io/x/accounts => ../x/accounts cosmossdk.io/x/accounts/defaults/base => ../x/accounts/defaults/base cosmossdk.io/x/accounts/defaults/lockup => ../x/accounts/defaults/lockup diff --git a/tests/go.sum b/tests/go.sum index 0c3171cd3f73..521e3e5f288f 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -198,10 +198,10 @@ cosmossdk.io/collections v0.4.1-0.20241031202146-5b7fc8ae90a7 h1:e5WKeWdFiH5hw1A cosmossdk.io/collections v0.4.1-0.20241031202146-5b7fc8ae90a7/go.mod h1:DcD++Yfcq0OFtM3CJNYLIBjfZ+4DEyeJ/AUk6gkwlOE= cosmossdk.io/core v1.0.0-alpha.5 h1:McjYXAQ6XcT20v2uHyH7PhoWH8V+mebzfVFqT3GinsI= cosmossdk.io/core v1.0.0-alpha.5/go.mod h1:3u9cWq1FAVtiiCrDPpo4LhR+9V6k/ycSG4/Y/tREWCY= -cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29 h1:NxxUo0GMJUbIuVg0R70e3cbn9eFTEuMr7ev1AFvypdY= -cosmossdk.io/core/testing v0.0.0-20240923163230-04da382a9f29/go.mod h1:8s2tPeJtSiQuoyPmr2Ag7meikonISO4Fv4MoO8+ORrs= -cosmossdk.io/depinject v1.0.0 h1:dQaTu6+O6askNXO06+jyeUAnF2/ssKwrrszP9t5q050= -cosmossdk.io/depinject v1.0.0/go.mod h1:zxK/h3HgHoA/eJVtiSsoaRaRA2D5U4cJ5thIG4ssbB8= +cosmossdk.io/core/testing v0.0.0-20241107153845-4e240908dd60 h1:owHSnQ2LZ4Kqgb1qhl8HeOTgebR04FwKHrZk4Q2d50Y= +cosmossdk.io/core/testing v0.0.0-20241107153845-4e240908dd60/go.mod h1:3YvVv9aJayjPhdX0DY1IMrGse4sR63hNBWx2VtDWjGQ= +cosmossdk.io/depinject v1.1.0 h1:wLan7LG35VM7Yo6ov0jId3RHWCGRhe8E8bsuARorl5E= +cosmossdk.io/depinject v1.1.0/go.mod h1:kkI5H9jCGHeKeYWXTqYdruogYrEeWvBQCw1Pj4/eCFI= cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 h1:IQNdY2kB+k+1OM2DvqFG1+UgeU1JzZrWtwuWzI3ZfwA= @@ -210,10 +210,18 @@ cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM= cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= +cosmossdk.io/runtime/v2 v2.0.0-20241107153845-4e240908dd60 h1:305IbyP2jaJeKNRglxBtEAY/JKz8rD3l+081zrAThGY= +cosmossdk.io/runtime/v2 v2.0.0-20241107153845-4e240908dd60/go.mod h1:zXxA8bHeShGxzTLR9m3OdF+aJ/IEmWSnrz343Ri6Me8= cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac h1:3joNZZWZ3k7fMsrBDL1ktuQ2xQwYLZOaDhkruadDFmc= cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ= +cosmossdk.io/server/v2/appmanager v0.0.0-20241107153845-4e240908dd60 h1:M26/YrNRru59kzRkW/Mm7bnpEEsnR9rIFdNFGTXicGQ= +cosmossdk.io/server/v2/appmanager v0.0.0-20241107153845-4e240908dd60/go.mod h1:mONOF8GRbxs5R04zMscuQQI1gx/XHTY7imKjcwLI5uo= +cosmossdk.io/server/v2/stf v0.0.0-20241107153845-4e240908dd60 h1:+PYG1Wjsu8ZX5vvINPu7UjRxzT9m9sJ9SlS8MVApmOU= +cosmossdk.io/server/v2/stf v0.0.0-20241107153845-4e240908dd60/go.mod h1:njNsfl5hKTylxDgSiBTLuOM1tic04aPk0k5Af6ybPN0= cosmossdk.io/store v1.0.0-rc.0.0.20240913190136-3bc707a5a214 h1:UUW0+2UgbDwQ452o2aw4DrVSWmowcad7DB7Vln+N94I= cosmossdk.io/store v1.0.0-rc.0.0.20240913190136-3bc707a5a214/go.mod h1:ct8HATr+s48YYTRXEyP3HF33v9qEVWHMxwOL8P/v4iQ= +cosmossdk.io/store/v2 v2.0.0-20241107153845-4e240908dd60 h1:2DnhJcJEoVDqz1aoqmy4RXiAnpqy9GNW1gzgnkesJmI= +cosmossdk.io/store/v2 v2.0.0-20241107153845-4e240908dd60/go.mod h1:calxpXVRO0AZRYdV+Kup74XP7rhDWAzVHKwHWm2TQZc= cosmossdk.io/x/tx v1.0.0-alpha.1 h1:5w61etWMQbdCSR7uveWXCnGnD5eQ/64B2vzIhqA80yo= cosmossdk.io/x/tx v1.0.0-alpha.1/go.mod h1:xlJjZV1wxZBTCP+ygZx9pNT/XxsfHDPf1H0VhHaUp5w= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -335,8 +343,8 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= -github.com/cosmos/iavl v1.3.0 h1:Ezaxt8aPA3kbkhsfyqwenChGLQwHDAIif3tG9x1FMV8= -github.com/cosmos/iavl v1.3.0/go.mod h1:T6SfBcyhulVIY2G/ZtAtQm/QiJvsuhIos52V4dWYk88= +github.com/cosmos/iavl v1.3.1 h1:+W1G2uSUtJMqMGpwz/fKiwZxY2DDT/9/0hyNLm6Geu0= +github.com/cosmos/iavl v1.3.1/go.mod h1:T6SfBcyhulVIY2G/ZtAtQm/QiJvsuhIos52V4dWYk88= github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo= @@ -394,8 +402,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA= github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -725,8 +733,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -735,8 +743,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -985,8 +993,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From c2e83cb7fe22158c7fcc188586c0798051ebd212 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 7 Nov 2024 13:47:01 -0600 Subject: [PATCH 3/5] rm files --- server/v2/appmanager/appmanager.go | 231 --------------------------- server/v2/stf/core_header_service.go | 58 ------- 2 files changed, 289 deletions(-) delete mode 100644 server/v2/appmanager/appmanager.go delete mode 100644 server/v2/stf/core_header_service.go diff --git a/server/v2/appmanager/appmanager.go b/server/v2/appmanager/appmanager.go deleted file mode 100644 index 597d1c8f4110..000000000000 --- a/server/v2/appmanager/appmanager.go +++ /dev/null @@ -1,231 +0,0 @@ -package appmanager - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - - "cosmossdk.io/core/server" - corestore "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" -) - -// AppManager is a coordinator for all things related to an application -// It is responsible for interacting with stf and store. -// Runtime/v2 is an extension of this interface. -type AppManager[T transaction.Tx] interface { - // InitGenesis initializes the genesis state of the application. - InitGenesis( - ctx context.Context, - blockRequest *server.BlockRequest[T], - initGenesisJSON []byte, - txDecoder transaction.Codec[T], - ) (*server.BlockResponse, corestore.WriterMap, error) - - // ExportGenesis exports the genesis state of the application. - ExportGenesis(ctx context.Context, version uint64) ([]byte, error) - - // DeliverBlock executes a block of transactions. - DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], - ) (*server.BlockResponse, corestore.WriterMap, error) - - // ValidateTx will validate the tx against the latest storage state. This means that - // only the stateful validation will be run, not the execution portion of the tx. - // If full execution is needed, Simulate must be used. - ValidateTx(ctx context.Context, tx T) (server.TxResult, error) - - // Simulate runs validation and execution flow of a Tx. - Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) - - // SimulateWithState runs validation and execution flow of a Tx, - // using the provided state instead of loading the latest state from the underlying database. - SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) - - // Query queries the application at the provided version. - // CONTRACT: Version must always be provided, if 0, get latest - Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) - - // QueryWithState executes a query with the provided state. This allows to process a query - // independently of the db state. For example, it can be used to process a query with temporary - // and uncommitted state - QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) -} - -// Store defines the underlying storage behavior needed by AppManager. -type Store interface { - // StateLatest returns a readonly view over the latest - // committed state of the store. Alongside the version - // associated with it. - StateLatest() (uint64, corestore.ReaderMap, error) - - // StateAt returns a readonly view over the provided - // state. Must error when the version does not exist. - StateAt(version uint64) (corestore.ReaderMap, error) -} - -// appManager is a coordinator for all things related to an application -type appManager[T transaction.Tx] struct { - // Gas limits for validating, querying, and simulating transactions. - config Config - // InitGenesis is a function that initializes the application state from a genesis file. - // It takes a context, a source reader for the genesis file, and a transaction handler function. - initGenesis InitGenesis - // ExportGenesis is a function that exports the application state to a genesis file. - // It takes a context and a version number for the genesis file. - exportGenesis ExportGenesis - // The database for storing application data. - db Store - // The state transition function for processing transactions. - stf StateTransitionFunction[T] -} - -func New[T transaction.Tx]( - config Config, - db Store, - stf StateTransitionFunction[T], - initGenesisImpl InitGenesis, - exportGenesisImpl ExportGenesis, -) AppManager[T] { - return &appManager[T]{ - config: config, - db: db, - stf: stf, - initGenesis: initGenesisImpl, - exportGenesis: exportGenesisImpl, - } -} - -// InitGenesis initializes the genesis state of the application. -func (a appManager[T]) InitGenesis( - ctx context.Context, - blockRequest *server.BlockRequest[T], - initGenesisJSON []byte, - txDecoder transaction.Codec[T], -) (*server.BlockResponse, corestore.WriterMap, error) { - var genTxs []T - genesisState, err := a.initGenesis( - ctx, - bytes.NewBuffer(initGenesisJSON), - func(jsonTx json.RawMessage) error { - genTx, err := txDecoder.DecodeJSON(jsonTx) - if err != nil { - return fmt.Errorf("failed to decode genesis transaction: %w", err) - } - genTxs = append(genTxs, genTx) - return nil - }, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to import genesis state: %w", err) - } - // run block - blockRequest.Txs = genTxs - - blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState) - if err != nil { - return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err) - } - - // after executing block 0, we extract the changes and apply them to the genesis state. - stateChanges, err := blockZeroState.GetStateChanges() - if err != nil { - return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err) - } - - err = genesisState.ApplyStateChanges(stateChanges) - if err != nil { - return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err) - } - - return blockResponse, genesisState, err -} - -// ExportGenesis exports the genesis state of the application. -func (a appManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) { - if a.exportGenesis == nil { - return nil, errors.New("export genesis function not set") - } - - return a.exportGenesis(ctx, version) -} - -// DeliverBlock executes a block of transactions. -func (a appManager[T]) DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], -) (*server.BlockResponse, corestore.WriterMap, error) { - latestVersion, currentState, err := a.db.StateLatest() - if err != nil { - return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err) - } - - if latestVersion+1 != block.Height { - return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height) - } - - blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState) - if err != nil { - return nil, nil, fmt.Errorf("block delivery failed: %w", err) - } - - return blockResponse, newState, nil -} - -// ValidateTx will validate the tx against the latest storage state. This means that -// only the stateful validation will be run, not the execution portion of the tx. -// If full execution is needed, Simulate must be used. -func (a appManager[T]) ValidateTx(ctx context.Context, tx T) (server.TxResult, error) { - _, latestState, err := a.db.StateLatest() - if err != nil { - return server.TxResult{}, err - } - res := a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx) - return res, res.Error -} - -// Simulate runs validation and execution flow of a Tx. -func (a appManager[T]) Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error) { - _, state, err := a.db.StateLatest() - if err != nil { - return server.TxResult{}, nil, err - } - result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler - return result, cs, nil -} - -// SimulateWithState runs validation and execution flow of a Tx, -// using the provided state instead of loading the latest state from the underlying database. -func (a appManager[T]) SimulateWithState(ctx context.Context, state corestore.ReaderMap, tx T) (server.TxResult, corestore.WriterMap, error) { - result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler - return result, cs, nil -} - -// Query queries the application at the provided version. -// CONTRACT: Version must always be provided, if 0, get latest -func (a appManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) { - var ( - queryState corestore.ReaderMap - err error - ) - // if version is provided attempt to do a height query. - if version != 0 { - queryState, err = a.db.StateAt(version) - } else { // otherwise rely on latest available state. - _, queryState, err = a.db.StateLatest() - } - if err != nil { - return nil, err - } - return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request) -} - -// QueryWithState executes a query with the provided state. This allows to process a query -// independently of the db state. For example, it can be used to process a query with temporary -// and uncommitted state -func (a appManager[T]) QueryWithState(ctx context.Context, state corestore.ReaderMap, request transaction.Msg) (transaction.Msg, error) { - return a.stf.Query(ctx, state, a.config.QueryGasLimit, request) -} diff --git a/server/v2/stf/core_header_service.go b/server/v2/stf/core_header_service.go deleted file mode 100644 index bef2ad8894ea..000000000000 --- a/server/v2/stf/core_header_service.go +++ /dev/null @@ -1,58 +0,0 @@ -package stf - -import ( - "context" - - "cosmossdk.io/core/header" - "cosmossdk.io/core/store" -) - -var _ header.Service = (*HeaderService)(nil) - -type HeaderService struct{} - -func (h HeaderService) HeaderInfo(ctx context.Context) header.Info { - exCtx, err := getExecutionCtxFromContext(ctx) - if err != nil { - panic(err) - } - - return exCtx.headerInfo -} - -const headerInfoPrefix = 0x37 - -// setHeaderInfo sets the header info in the state to be used by queries in the future. -func (s STF[T]) setHeaderInfo(state store.WriterMap, headerInfo header.Info) error { - runtimeStore, err := state.GetWriter(Identity) - if err != nil { - return err - } - bz, err := headerInfo.Bytes() - if err != nil { - return err - } - err = runtimeStore.Set([]byte{headerInfoPrefix}, bz) - if err != nil { - return err - } - return nil -} - -// getHeaderInfo gets the header info from the state. It should only be used for queries -func (s STF[T]) getHeaderInfo(state store.WriterMap) (i header.Info, err error) { - runtimeStore, err := state.GetWriter(Identity) - if err != nil { - return header.Info{}, err - } - v, err := runtimeStore.Get([]byte{headerInfoPrefix}) - if err != nil { - return header.Info{}, err - } - if v == nil { - return header.Info{}, nil - } - - err = i.FromBytes(v) - return i, err -} From 332b0d13fa7e07052eebd2144b13c4822375caf8 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 8 Nov 2024 12:39:49 +0100 Subject: [PATCH 4/5] updates --- tests/go.mod | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index 0653a5f0f688..c7a2df19fc13 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -10,29 +10,11 @@ require ( cosmossdk.io/depinject v1.1.0 cosmossdk.io/log v1.4.1 cosmossdk.io/math v1.3.0 + cosmossdk.io/runtime/v2 v2.0.0-20241107153845-4e240908dd60 + cosmossdk.io/server/v2/stf v0.0.0-20241107153845-4e240908dd60 cosmossdk.io/simapp v0.0.0-20230309163709-87da587416ba cosmossdk.io/store v1.1.1 - cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f - cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f - cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f // indirect - cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 - cosmossdk.io/x/tx v1.0.0-alpha.2 - cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f - github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f - github.com/cosmos/cosmos-proto v1.0.0-beta.5 - // this version is not used as it is always replaced by the latest Cosmos SDK version - github.com/cosmos/cosmos-sdk v0.53.0 - github.com/cosmos/gogoproto v1.7.0 - github.com/spf13/cobra v1.8.1 // indirect - github.com/stretchr/testify v1.9.0 - google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.35.1 - gotest.tools/v3 v3.5.1 - pgregory.net/rapid v1.1.0 -) - -require ( - cosmossdk.io/runtime/v2 v2.0.0-20241107153845-4e240908dd60 + cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 cosmossdk.io/x/accounts v0.0.0-20240913065641-0064ccbce64e cosmossdk.io/x/accounts/defaults/base v0.0.0-00010101000000-000000000000 cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5 @@ -41,25 +23,37 @@ require ( cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000 cosmossdk.io/x/distribution v0.0.0-20240227221813-a248d05f70f4 + cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f + cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f cosmossdk.io/x/gov v0.0.0-20231113122742-912390d5fc4a cosmossdk.io/x/group v0.0.0-00010101000000-000000000000 cosmossdk.io/x/mint v0.0.0-00010101000000-000000000000 + cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f // indirect + cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 cosmossdk.io/x/slashing v0.0.0-00010101000000-000000000000 cosmossdk.io/x/staking v0.0.0-20240226161501-23359a0b6d91 + cosmossdk.io/x/tx v1.0.0-alpha.2 + cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f + github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f github.com/cometbft/cometbft/api v1.0.0-rc.1 github.com/cosmos/cosmos-db v1.0.3-0.20240911104526-ddc3f09bfc22 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 + // this version is not used as it is always replaced by the latest Cosmos SDK version + github.com/cosmos/cosmos-sdk v0.52.0 + github.com/cosmos/gogoproto v1.7.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.0 github.com/jhump/protoreflect v1.17.0 github.com/rs/zerolog v1.33.0 + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b -) - -require ( - cosmossdk.io/server/v2/stf v0.0.0-20241107153845-4e240908dd60 - cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + gotest.tools/v3 v3.5.1 + pgregory.net/rapid v1.1.0 ) require ( From dd40a1bc6dd27daee1743b09920d554853186d2c Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 8 Nov 2024 12:43:25 +0100 Subject: [PATCH 5/5] `make lint-fix` --- server/v2/cometbft/mempool/noop.go | 6 ++++-- x/accounts/defaults/base/utils_test.go | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/v2/cometbft/mempool/noop.go b/server/v2/cometbft/mempool/noop.go index 25cffcf69979..3d3e4feab2da 100644 --- a/server/v2/cometbft/mempool/noop.go +++ b/server/v2/cometbft/mempool/noop.go @@ -8,8 +8,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var _ Mempool[sdk.Tx] = (*NoOpMempool[sdk.Tx])(nil) // verify interface at compile time -var _ Mempool[transaction.Tx] = (*NoOpMempool[transaction.Tx])(nil) +var ( + _ Mempool[sdk.Tx] = (*NoOpMempool[sdk.Tx])(nil) // verify interface at compile time + _ Mempool[transaction.Tx] = (*NoOpMempool[transaction.Tx])(nil) +) // NoOpMempool defines a no-op mempool. Transactions are completely discarded and // ignored when BaseApp interacts with the mempool. diff --git a/x/accounts/defaults/base/utils_test.go b/x/accounts/defaults/base/utils_test.go index 5d1a91893064..9ea432674a39 100644 --- a/x/accounts/defaults/base/utils_test.go +++ b/x/accounts/defaults/base/utils_test.go @@ -66,8 +66,7 @@ func newMockContext(t *testing.T) (context.Context, store.KVStoreService) { ) } -type transactionService struct { -} +type transactionService struct{} func (t transactionService) ExecMode(ctx context.Context) transaction.ExecMode { return transaction.ExecModeFinalize