diff --git a/UPGRADING.md b/UPGRADING.md index e46ca93ed826..d5028acfa031 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -78,8 +78,22 @@ need to remove them both from your app.go code, they will yield to unresolvable The Cosmos SDK now supports unordered transactions. This means that transactions can be executed in any order and doesn't require the client to deal with or manage -nonces. This also means the order of execution is not guaranteed. To enable unordered -transactions in your application: +nonces. This also means the order of execution is not guaranteed. + +Unordered transactions are automatically enabled when using `depinject` / app di, simply supply the `servertypes.AppOptions` in `app.go`: + +```diff + depinject.Supply( ++ // supply the application options ++ appOpts, + // supply the logger + logger, + ) +``` + +
+Step-by-step Wiring +If you are still using the legacy wiring, you must enable unordered transactions manually: * Update the `App` constructor to create, load, and save the unordered transaction manager. @@ -143,6 +157,8 @@ transactions in your application: } ``` +
+ To submit an unordered transaction, the client must set the `unordered` flag to `true` and ensure a reasonable `timeout_height` is set. The `timeout_height` is used as a TTL for the transaction and is used to provide replay protection. See diff --git a/runtime/app.go b/runtime/app.go index 2481da380995..dd5da8580779 100644 --- a/runtime/app.go +++ b/runtime/app.go @@ -13,6 +13,7 @@ import ( "cosmossdk.io/core/legacy" "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/auth/ante/unorderedtx" authtx "cosmossdk.io/x/auth/tx" "github.com/cosmos/cosmos-sdk/baseapp" @@ -41,7 +42,9 @@ import ( type App struct { *baseapp.BaseApp - ModuleManager *module.Manager + ModuleManager *module.Manager + UnorderedTxManager *unorderedtx.Manager + configurator module.Configurator // nolint:staticcheck // SA1019: Configurator is deprecated but still used in runtime v1. config *runtimev1alpha1.Module storeKeys []storetypes.StoreKey @@ -154,6 +157,20 @@ func (a *App) Load(loadLatest bool) error { return nil } +// Close closes all necessary application resources. +// It implements servertypes.Application. +func (a *App) Close() error { + // the unordered tx manager could be nil (unlikely but possible) + // if the app has no app options supplied. + if a.UnorderedTxManager != nil { + if err := a.UnorderedTxManager.Close(); err != nil { + return err + } + } + + return a.BaseApp.Close() +} + // PreBlocker application updates every pre block func (a *App) PreBlocker(ctx sdk.Context, _ *abci.FinalizeBlockRequest) error { return a.ModuleManager.PreBlock(ctx) @@ -171,16 +188,14 @@ func (a *App) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { // Precommiter application updates every commit func (a *App) Precommiter(ctx sdk.Context) { - err := a.ModuleManager.Precommit(ctx) - if err != nil { + if err := a.ModuleManager.Precommit(ctx); err != nil { panic(err) } } // PrepareCheckStater application updates every commit func (a *App) PrepareCheckStater(ctx sdk.Context) { - err := a.ModuleManager.PrepareCheckState(ctx) - if err != nil { + if err := a.ModuleManager.PrepareCheckState(ctx); err != nil { panic(err) } } @@ -247,11 +262,6 @@ func (a *App) DefaultGenesis() map[string]json.RawMessage { return a.ModuleManager.DefaultGenesis() } -// GetStoreKeys returns all the stored store keys. -func (a *App) GetStoreKeys() []storetypes.StoreKey { - return a.storeKeys -} - // SetInitChainer sets the init chainer function // It wraps `BaseApp.SetInitChainer` to allow setting a custom init chainer from an app. func (a *App) SetInitChainer(initChainer sdk.InitChainer) { @@ -259,6 +269,23 @@ func (a *App) SetInitChainer(initChainer sdk.InitChainer) { a.BaseApp.SetInitChainer(initChainer) } +// GetStoreKeys returns all the stored store keys. +func (a *App) GetStoreKeys() []storetypes.StoreKey { + return a.storeKeys +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This should only be used in testing. +func (a *App) GetKey(storeKey string) *storetypes.KVStoreKey { + sk := a.UnsafeFindStoreKey(storeKey) + kvStoreKey, ok := sk.(*storetypes.KVStoreKey) + if !ok { + return nil + } + return kvStoreKey +} + // UnsafeFindStoreKey fetches a registered StoreKey from the App in linear time. // // NOTE: This should only be used in testing. diff --git a/runtime/builder.go b/runtime/builder.go index c96af6ecd6ee..73fcb3f73f6b 100644 --- a/runtime/builder.go +++ b/runtime/builder.go @@ -2,11 +2,19 @@ package runtime import ( "encoding/json" + "fmt" "io" + "path/filepath" dbm "github.com/cosmos/cosmos-db" + "github.com/spf13/cast" + + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/auth/ante/unorderedtx" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" ) @@ -16,6 +24,8 @@ import ( // the existing app.go initialization conventions. type AppBuilder struct { app *App + + appOptions servertypes.AppOptions } // DefaultGenesis returns a default genesis from the registered modules. @@ -40,9 +50,63 @@ func (a *AppBuilder) Build(db dbm.DB, traceStore io.Writer, baseAppOptions ...fu a.app.BaseApp = bApp a.app.configurator = module.NewConfigurator(a.app.cdc, a.app.MsgServiceRouter(), a.app.GRPCQueryRouter()) + if a.appOptions != nil { + // register unordered tx manager + if err := a.registerUnorderedTxManager(); err != nil { + panic(err) + } + + // register indexer if enabled + if err := a.registerIndexer(); err != nil { + panic(err) + } + } + + // register services if err := a.app.ModuleManager.RegisterServices(a.app.configurator); err != nil { panic(err) } return a.app } + +// register unordered tx manager +func (a *AppBuilder) registerUnorderedTxManager() error { + // create, start, and load the unordered tx manager + utxDataDir := filepath.Join(cast.ToString(a.appOptions.Get(flags.FlagHome)), "data") + a.app.UnorderedTxManager = unorderedtx.NewManager(utxDataDir) + a.app.UnorderedTxManager.Start() + + if err := a.app.UnorderedTxManager.OnInit(); err != nil { + return fmt.Errorf("failed to initialize unordered tx manager: %w", err) + } + + return nil +} + +// register indexer +func (a *AppBuilder) registerIndexer() error { + // if we have indexer options in app.toml, then enable the built-in indexer framework + if indexerOpts := a.appOptions.Get("indexer"); indexerOpts != nil { + moduleSet := map[string]any{} + for modName, mod := range a.app.ModuleManager.Modules { + moduleSet[modName] = mod + } + + return a.app.EnableIndexer(indexerOpts, a.kvStoreKeys(), moduleSet) + } + + // register legacy streaming services if we don't have the built-in indexer enabled + return a.app.RegisterStreamingServices(a.appOptions, a.kvStoreKeys()) +} + +func (a *AppBuilder) kvStoreKeys() map[string]*storetypes.KVStoreKey { + keys := make(map[string]*storetypes.KVStoreKey) + for _, k := range a.app.GetStoreKeys() { + if kv, ok := k.(*storetypes.KVStoreKey); ok { + keys[kv.Name()] = kv + } + } + + return keys +} diff --git a/runtime/module.go b/runtime/module.go index 99d4ff53dafd..f8883e0e8e27 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -25,6 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/msgservice" @@ -120,7 +121,6 @@ func ProvideApp( appmodule.AppModule, protodesc.Resolver, protoregistry.MessageTypeResolver, - error, ) { protoFiles := proto.HybridResolver protoTypes := protoregistry.GlobalTypes @@ -145,9 +145,9 @@ func ProvideApp( msgServiceRouter: msgServiceRouter, grpcQueryRouter: grpcQueryRouter, } - appBuilder := &AppBuilder{app} + appBuilder := &AppBuilder{app: app} - return appBuilder, msgServiceRouter, grpcQueryRouter, appModule{app}, protoFiles, protoTypes, nil + return appBuilder, msgServiceRouter, grpcQueryRouter, appModule{app}, protoFiles, protoTypes } type AppInputs struct { @@ -160,6 +160,7 @@ type AppInputs struct { BaseAppOptions []BaseAppOption InterfaceRegistry codectypes.InterfaceRegistry LegacyAmino legacy.Amino + AppOptions servertypes.AppOptions `optional:"true"` // can be nil in client wiring } func SetupAppBuilder(inputs AppInputs) { @@ -170,6 +171,10 @@ func SetupAppBuilder(inputs AppInputs) { app.ModuleManager = inputs.ModuleManager app.ModuleManager.RegisterInterfaces(inputs.InterfaceRegistry) app.ModuleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino) + + if inputs.AppOptions != nil { + inputs.AppBuilder.appOptions = inputs.AppOptions + } } func registerStoreKey(wrapper *AppBuilder, key storetypes.StoreKey) { diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go new file mode 100644 index 000000000000..ddb2f9475971 --- /dev/null +++ b/runtime/v2/builder.go @@ -0,0 +1,230 @@ +package runtime + +import ( + "context" + "encoding/json" + "fmt" + "io" + "path/filepath" + + "github.com/spf13/viper" + + "cosmossdk.io/core/appmodule" + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/store" + "cosmossdk.io/core/transaction" + "cosmossdk.io/server/v2/appmanager" + "cosmossdk.io/server/v2/stf" + "cosmossdk.io/server/v2/stf/branch" + "cosmossdk.io/store/v2/db" + rootstore "cosmossdk.io/store/v2/root" +) + +// AppBuilder is a type that is injected into a container by the runtime/v2 module +// (as *AppBuilder) which can be used to create an app which is compatible with +// the existing app.go initialization conventions. +type AppBuilder[T transaction.Tx] struct { + app *App[T] + storeOptions *rootstore.FactoryOptions + viper *viper.Viper + + // the following fields are used to overwrite the default + branch func(state store.ReaderMap) store.WriterMap + txValidator func(ctx context.Context, tx T) error + postTxExec func(ctx context.Context, tx T, success bool) error +} + +// DefaultGenesis returns a default genesis from the registered AppModule's. +func (a *AppBuilder[T]) DefaultGenesis() map[string]json.RawMessage { + return a.app.moduleManager.DefaultGenesis() +} + +// RegisterModules registers the provided modules with the module manager. +// This is the primary hook for integrating with modules which are not registered using the app config. +func (a *AppBuilder[T]) RegisterModules(modules map[string]appmodulev2.AppModule) error { + for name, appModule := range modules { + // if a (legacy) module implements the HasName interface, check that the name matches + if mod, ok := appModule.(interface{ Name() string }); ok { + if name != mod.Name() { + a.app.logger.Warn(fmt.Sprintf("module name %q does not match name returned by HasName: %q", name, mod.Name())) + } + } + + if _, ok := a.app.moduleManager.modules[name]; ok { + return fmt.Errorf("module named %q already exists", name) + } + a.app.moduleManager.modules[name] = appModule + + if mod, ok := appModule.(appmodulev2.HasRegisterInterfaces); ok { + mod.RegisterInterfaces(a.app.interfaceRegistrar) + } + + if mod, ok := appModule.(appmodule.HasAminoCodec); ok { + mod.RegisterLegacyAminoCodec(a.app.amino) + } + } + + return nil +} + +// RegisterStores registers the provided store keys. +// This method should only be used for registering extra stores +// which is necessary for modules that not registered using the app config. +// To be used in combination of RegisterModules. +func (a *AppBuilder[T]) RegisterStores(keys ...string) { + a.app.storeKeys = append(a.app.storeKeys, keys...) + if a.storeOptions != nil { + a.storeOptions.StoreKeys = append(a.storeOptions.StoreKeys, keys...) + } +} + +// Build builds an *App instance. +func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { + for _, opt := range opts { + opt(a) + } + + // default branch + if a.branch == nil { + a.branch = branch.DefaultNewWriterMap + } + + // default tx validator + if a.txValidator == nil { + a.txValidator = a.app.moduleManager.TxValidators() + } + + // default post tx exec + if a.postTxExec == nil { + a.postTxExec = func(ctx context.Context, tx T, success bool) error { + return nil + } + } + + if err := a.app.moduleManager.RegisterServices(a.app); err != nil { + return nil, err + } + + endBlocker, valUpdate := a.app.moduleManager.EndBlock() + + stf, err := stf.NewSTF[T]( + a.app.logger.With("module", "stf"), + a.app.msgRouterBuilder, + a.app.queryRouterBuilder, + a.app.moduleManager.PreBlocker(), + a.app.moduleManager.BeginBlock(), + endBlocker, + a.txValidator, + valUpdate, + a.postTxExec, + a.branch, + ) + if err != nil { + return nil, fmt.Errorf("failed to create STF: %w", err) + } + a.app.stf = stf + + storeOpts := rootstore.DefaultStoreOptions() + if s := a.viper.Sub("store.options"); s != nil { + if err := s.Unmarshal(&storeOpts); err != nil { + return nil, fmt.Errorf("failed to store options: %w", err) + } + } + + home := a.viper.GetString(FlagHome) + scRawDb, err := db.NewDB(db.DBType(a.viper.GetString("store.app-db-backend")), "application", filepath.Join(home, "data"), nil) + if err != nil { + panic(err) + } + + storeOptions := &rootstore.FactoryOptions{ + Logger: a.app.logger, + RootDir: home, + Options: storeOpts, + StoreKeys: append(a.app.storeKeys, "stf"), + SCRawDB: scRawDb, + } + a.storeOptions = storeOptions + + rs, err := rootstore.CreateRootStore(a.storeOptions) + if err != nil { + return nil, fmt.Errorf("failed to create root store: %w", err) + } + a.app.db = rs + + appManagerBuilder := appmanager.Builder[T]{ + STF: a.app.stf, + DB: a.app.db, + ValidateTxGasLimit: a.app.config.GasConfig.ValidateTxGasLimit, + QueryGasLimit: a.app.config.GasConfig.QueryGasLimit, + SimulationGasLimit: a.app.config.GasConfig.SimulationGasLimit, + InitGenesis: func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error { + // this implementation assumes that the state is a JSON object + bz, err := io.ReadAll(src) + if err != nil { + return fmt.Errorf("failed to read import state: %w", err) + } + var genesisState map[string]json.RawMessage + if err = json.Unmarshal(bz, &genesisState); err != nil { + return err + } + if err = a.app.moduleManager.InitGenesisJSON(ctx, genesisState, txHandler); err != nil { + return fmt.Errorf("failed to init genesis: %w", err) + } + return nil + }, + ExportGenesis: func(ctx context.Context, version uint64) ([]byte, error) { + genesisJson, err := a.app.moduleManager.ExportGenesisForModules(ctx) + if err != nil { + return nil, fmt.Errorf("failed to export genesis: %w", err) + } + + bz, err := json.Marshal(genesisJson) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis: %w", err) + } + + return bz, nil + }, + } + + appManager, err := appManagerBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build app manager: %w", err) + } + a.app.AppManager = appManager + + return a.app, nil +} + +// AppBuilderOption is a function that can be passed to AppBuilder.Build to customize the resulting app. +type AppBuilderOption[T transaction.Tx] func(*AppBuilder[T]) + +// AppBuilderWithBranch sets a custom branch implementation for the app. +func AppBuilderWithBranch[T transaction.Tx](branch func(state store.ReaderMap) store.WriterMap) AppBuilderOption[T] { + return func(a *AppBuilder[T]) { + a.branch = branch + } +} + +// AppBuilderWithTxValidator sets the tx validator for the app. +// It overrides all default tx validators defined by modules. +func AppBuilderWithTxValidator[T transaction.Tx](txValidators func(ctx context.Context, tx T) error) AppBuilderOption[T] { + return func(a *AppBuilder[T]) { + a.txValidator = txValidators + } +} + +// AppBuilderWithPostTxExec sets logic that will be executed after each transaction. +// When not provided, a no-op function will be used. +func AppBuilderWithPostTxExec[T transaction.Tx]( + postTxExec func( + ctx context.Context, + tx T, + success bool, + ) error, +) AppBuilderOption[T] { + return func(a *AppBuilder[T]) { + a.postTxExec = postTxExec + } +} diff --git a/runtime/v2/module.go b/runtime/v2/module.go new file mode 100644 index 000000000000..6ae735505d02 --- /dev/null +++ b/runtime/v2/module.go @@ -0,0 +1,247 @@ +package runtime + +import ( + "fmt" + "os" + "slices" + + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoregistry" + + runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" + appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/comet" + "cosmossdk.io/core/legacy" + "cosmossdk.io/core/registry" + "cosmossdk.io/core/server" + "cosmossdk.io/core/store" + "cosmossdk.io/core/transaction" + "cosmossdk.io/depinject" + "cosmossdk.io/depinject/appconfig" + "cosmossdk.io/log" + "cosmossdk.io/runtime/v2/services" + "cosmossdk.io/server/v2/stf" +) + +var ( + _ appmodulev2.AppModule = appModule[transaction.Tx]{} + _ hasServicesV1 = appModule[transaction.Tx]{} +) + +type appModule[T transaction.Tx] struct { + app *App[T] +} + +func (m appModule[T]) IsOnePerModuleType() {} +func (m appModule[T]) IsAppModule() {} + +func (m appModule[T]) RegisterServices(registar grpc.ServiceRegistrar) error { + autoCliQueryService, err := services.NewAutoCLIQueryService(m.app.moduleManager.modules) + if err != nil { + return err + } + + autocliv1.RegisterQueryServer(registar, autoCliQueryService) + + reflectionSvc, err := services.NewReflectionService() + if err != nil { + return err + } + reflectionv1.RegisterReflectionServiceServer(registar, reflectionSvc) + + return nil +} + +func (m appModule[T]) AutoCLIOptions() *autocliv1.ModuleOptions { + return &autocliv1.ModuleOptions{ + Query: &autocliv1.ServiceCommandDescriptor{ + Service: appv1alpha1.Query_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "Config", + Short: "Query the current app config", + }, + }, + SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{ + "autocli": { + Service: autocliv1.Query_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "AppOptions", + Short: "Query the custom autocli options", + }, + }, + }, + "reflection": { + Service: reflectionv1.ReflectionService_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "FileDescriptors", + Short: "Query the app's protobuf file descriptors", + }, + }, + }, + }, + }, + } +} + +func init() { + appconfig.Register(&runtimev2.Module{}, + appconfig.Provide( + ProvideAppBuilder[transaction.Tx], + ProvideEnvironment[transaction.Tx], + ProvideModuleManager[transaction.Tx], + ProvideCometService, + ProvideAppVersionModifier[transaction.Tx], + ), + appconfig.Invoke(SetupAppBuilder), + ) +} + +func ProvideAppBuilder[T transaction.Tx]( + interfaceRegistrar registry.InterfaceRegistrar, + amino legacy.Amino, +) ( + *AppBuilder[T], + *stf.MsgRouterBuilder, + appmodulev2.AppModule, + protodesc.Resolver, + protoregistry.MessageTypeResolver, +) { + protoFiles := proto.HybridResolver + protoTypes := protoregistry.GlobalTypes + + // At startup, check that all proto annotations are correct. + if err := validateProtoAnnotations(protoFiles); err != nil { + // Once we switch to using protoreflect-based ante handlers, we might + // want to panic here instead of logging a warning. + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + } + + msgRouterBuilder := stf.NewMsgRouterBuilder() + app := &App[T]{ + storeKeys: nil, + interfaceRegistrar: interfaceRegistrar, + amino: amino, + msgRouterBuilder: msgRouterBuilder, + queryRouterBuilder: stf.NewMsgRouterBuilder(), // TODO dedicated query router + GRPCMethodsToMessageMap: map[string]func() proto.Message{}, + } + appBuilder := &AppBuilder[T]{app: app} + + return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes +} + +type AppInputs struct { + depinject.In + + Config *runtimev2.Module + AppBuilder *AppBuilder[transaction.Tx] + ModuleManager *MM[transaction.Tx] + InterfaceRegistrar registry.InterfaceRegistrar + LegacyAmino legacy.Amino + Logger log.Logger + Viper *viper.Viper `optional:"true"` // can be nil in client wiring +} + +func SetupAppBuilder(inputs AppInputs) { + app := inputs.AppBuilder.app + app.config = inputs.Config + app.logger = inputs.Logger + app.moduleManager = inputs.ModuleManager + app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar) + app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino) + + if inputs.Viper != nil { + inputs.AppBuilder.viper = inputs.Viper + } +} + +func ProvideModuleManager[T transaction.Tx]( + logger log.Logger, + config *runtimev2.Module, + modules map[string]appmodulev2.AppModule, +) *MM[T] { + return NewModuleManager[T](logger, config, modules) +} + +// ProvideEnvironment provides the environment for keeper modules, while maintaining backward compatibility and provide services directly as well. +func ProvideEnvironment[T transaction.Tx]( + logger log.Logger, + config *runtimev2.Module, + key depinject.ModuleKey, + appBuilder *AppBuilder[T], +) ( + appmodulev2.Environment, + store.KVStoreService, + store.MemoryStoreService, +) { + var ( + kvService store.KVStoreService = failingStoreService{} + memKvService store.MemoryStoreService = failingStoreService{} + ) + + // skips modules that have no store + if !slices.Contains(config.SkipStoreKeys, key.Name()) { + var kvStoreKey string + storeKeyOverride := storeKeyOverride(config, key.Name()) + if storeKeyOverride != nil { + kvStoreKey = storeKeyOverride.KvStoreKey + } else { + kvStoreKey = key.Name() + } + + registerStoreKey(appBuilder, kvStoreKey) + kvService = stf.NewKVStoreService([]byte(kvStoreKey)) + + memStoreKey := fmt.Sprintf("memory:%s", key.Name()) + registerStoreKey(appBuilder, memStoreKey) + memKvService = stf.NewMemoryStoreService([]byte(memStoreKey)) + } + + env := appmodulev2.Environment{ + Logger: logger, + BranchService: stf.BranchService{}, + EventService: stf.NewEventService(), + GasService: stf.NewGasMeterService(), + HeaderService: stf.HeaderService{}, + QueryRouterService: stf.NewQueryRouterService(), + MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())), + TransactionService: services.NewContextAwareTransactionService(), + KVStoreService: kvService, + MemStoreService: memKvService, + } + + return env, kvService, memKvService +} + +func registerStoreKey[T transaction.Tx](wrapper *AppBuilder[T], key string) { + wrapper.app.storeKeys = append(wrapper.app.storeKeys, key) +} + +func storeKeyOverride(config *runtimev2.Module, moduleName string) *runtimev2.StoreKeyConfig { + for _, cfg := range config.OverrideStoreKeys { + if cfg.ModuleName == moduleName { + return cfg + } + } + + return nil +} + +func ProvideCometService() comet.Service { + return &services.ContextAwareCometInfoService{} +} + +// ProvideAppVersionModifier returns nil, `app.VersionModifier` is a feature of BaseApp and neither used nor required for runtime/v2. +// nil is acceptable, see: https://github.com/cosmos/cosmos-sdk/blob/0a6ee406a02477ae8ccbfcbe1b51fc3930087f4c/x/upgrade/keeper/keeper.go#L438 +func ProvideAppVersionModifier[T transaction.Tx](app *AppBuilder[T]) server.VersionModifier { + return nil +} diff --git a/server/util.go b/server/util.go index de4e5c52d108..8b3da36b1829 100644 --- a/server/util.go +++ b/server/util.go @@ -45,8 +45,9 @@ import ( // a command's Context. const ServerContextKey = sdk.ContextKey("server.context") -// Context server context -// Deprecated: Do not use since we use viper to track all config +// Context is the server context. +// Prefer using we use viper a it tracks track all config. +// See core/context/server_context.go. type Context struct { Viper *viper.Viper Config *cmtcfg.Config diff --git a/simapp/CHANGELOG.md b/simapp/CHANGELOG.md index 2daff48a15f2..3c46633c7080 100644 --- a/simapp/CHANGELOG.md +++ b/simapp/CHANGELOG.md @@ -32,10 +32,19 @@ It is an exautive list of changes that completes the SimApp section in the [UPGR Always refer to the [UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/main/UPGRADING.md) to understand the changes. +* Update module path to new vanity url. +* Wire new SDK modules (`epochs`, `accounts`, `protocolpool`). +* Remove the crisis module from simapp. +* Update `export` function to make use of the new module collections API. +* Add example of how to define a custom mint function in `simapp/mint_fn.go`. +* Add address codec in client context and signing context. +* Update testnet command to match new module APIs. * [#20409](https://github.com/cosmos/cosmos-sdk/pull/20409) Add `tx` as `SkipStoreKeys` in `app_config.go`. * [#20485](https://github.com/cosmos/cosmos-sdk/pull/20485) The signature of `x/upgrade/types.UpgradeHandler` has changed to accept `appmodule.VersionMap` from `module.VersionMap`. These types are interchangeable, but usages of `UpradeKeeper.SetUpgradeHandler` may need to adjust their usages to match the new signature. * [#20740](https://github.com/cosmos/cosmos-sdk/pull/20740) Update `genutilcli.Commands` to use the genutil modules from the module manager. * [#20771](https://github.com/cosmos/cosmos-sdk/pull/20771) Use client/v2 `GetNodeHomeDirectory` helper in `app.go` and use the `DefaultNodeHome` constant everywhere in the app. +* [#20490](https://github.com/cosmos/cosmos-sdk/pull/20490) Refactor simulations to make use of `testutil/sims` instead of `runsims`. +* [#19726](https://github.com/cosmos/cosmos-sdk/pull/19726) Update APIs to match CometBFT v1. diff --git a/simapp/ante.go b/simapp/ante.go index 6a12eabecfd5..8fd68c613076 100644 --- a/simapp/ante.go +++ b/simapp/ante.go @@ -14,7 +14,6 @@ import ( type HandlerOptions struct { ante.HandlerOptions CircuitKeeper circuitante.CircuitBreaker - TxManager *unorderedtx.Manager } // NewAnteHandler returns an AnteHandler that checks and increments sequence @@ -39,7 +38,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), ante.NewValidateBasicDecorator(options.Environment), ante.NewTxTimeoutHeightDecorator(options.Environment), - ante.NewUnorderedTxDecorator(unorderedtx.DefaultMaxTimeoutDuration, options.TxManager, options.Environment, ante.DefaultSha256Cost), + ante.NewUnorderedTxDecorator(unorderedtx.DefaultMaxTimeoutDuration, options.UnorderedTxManager, options.Environment, ante.DefaultSha256Cost), ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), diff --git a/simapp/app.go b/simapp/app.go index 20d54913883b..5c0b495eab1e 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -636,9 +636,9 @@ func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { SignModeHandler: txConfig.SignModeHandler(), FeegrantKeeper: app.FeeGrantKeeper, SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + UnorderedTxManager: app.UnorderedTxManager, }, &app.CircuitKeeper, - app.UnorderedTxManager, }, ) if err != nil { @@ -660,9 +660,13 @@ func (app *SimApp) setPostHandler() { app.SetPostHandler(postHandler) } -// Close implements the Application interface and closes all necessary application -// resources. +// Close closes all necessary application resources. +// It implements servertypes.Application. func (app *SimApp) Close() error { + if err := app.BaseApp.Close(); err != nil { + return err + } + return app.UnorderedTxManager.Close() } diff --git a/simapp/app_config.go b/simapp/app_config.go index 8918475184e1..a1d45ff021df 100644 --- a/simapp/app_config.go +++ b/simapp/app_config.go @@ -1,4 +1,4 @@ -//nolint:unused,nolintlint // ignore unused code linting and directive `//nolint:unused // ignore unused code linting` is unused for linter "unused" +//nolint:unused,nolintlint // ignore unused code linting package simapp import ( diff --git a/simapp/app_di.go b/simapp/app_di.go index c2a5696e273e..34335df797aa 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -6,17 +6,14 @@ import ( _ "embed" "fmt" "io" - "path/filepath" dbm "github.com/cosmos/cosmos-db" - "github.com/spf13/cast" clienthelpers "cosmossdk.io/client/v2/helpers" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/legacy" "cosmossdk.io/depinject" "cosmossdk.io/log" - storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/accounts" "cosmossdk.io/x/auth" "cosmossdk.io/x/auth/ante" @@ -44,7 +41,6 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -74,8 +70,6 @@ type SimApp struct { txConfig client.TxConfig interfaceRegistry codectypes.InterfaceRegistry - UnorderedTxManager *unorderedtx.Manager - // keepers AccountsKeeper accounts.Keeper AuthKeeper authkeeper.AccountKeeper @@ -245,23 +239,6 @@ func NewSimApp( app.App = appBuilder.Build(db, traceStore, baseAppOptions...) - if indexerOpts := appOpts.Get("indexer"); indexerOpts != nil { - // if we have indexer options in app.toml, then enable the built-in indexer framework - moduleSet := map[string]any{} - for modName, mod := range appModules { - moduleSet[modName] = mod - } - err := app.EnableIndexer(indexerOpts, app.kvStoreKeys(), moduleSet) - if err != nil { - panic(err) - } - } else { - // register legacy streaming services if we don't have the built-in indexer enabled - if err := app.RegisterStreamingServices(appOpts, app.kvStoreKeys()); err != nil { - panic(err) - } - } - /**** Module Options ****/ // RegisterUpgradeHandlers is used for registering any on-chain upgrades. @@ -287,26 +264,16 @@ func NewSimApp( // However, when registering a module manually (i.e. that does not support app wiring), the module version map // must be set manually as follow. The upgrade module will de-duplicate the module version map. // - // app.SetInitChainer(func(ctx sdk.Context, req *abci.RequestInitChain) (*abci.InitChainResponse, error) { + // app.SetInitChainer(func(ctx sdk.Context, req *abci.InitChainRequest) (*abci.InitChainResponse, error) { // app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) // return app.App.InitChainer(ctx, req) // }) - // create, start, and load the unordered tx manager - utxDataDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data") - app.UnorderedTxManager = unorderedtx.NewManager(utxDataDir) - app.UnorderedTxManager.Start() - - if err := app.UnorderedTxManager.OnInit(); err != nil { - panic(fmt.Errorf("failed to initialize unordered tx manager: %w", err)) - } - // register custom snapshot extensions (if any) if manager := app.SnapshotManager(); manager != nil { - err := manager.RegisterExtensions( + if err := manager.RegisterExtensions( unorderedtx.NewSnapshotter(app.UnorderedTxManager), - ) - if err != nil { + ); err != nil { panic(fmt.Errorf("failed to register snapshot extension: %w", err)) } } @@ -327,15 +294,15 @@ func (app *SimApp) setCustomAnteHandler() { anteHandler, err := NewAnteHandler( HandlerOptions{ ante.HandlerOptions{ - AccountKeeper: app.AuthKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: app.txConfig.SignModeHandler(), - FeegrantKeeper: app.FeeGrantKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - Environment: app.AuthKeeper.Environment, + AccountKeeper: app.AuthKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: app.txConfig.SignModeHandler(), + FeegrantKeeper: app.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + UnorderedTxManager: app.UnorderedTxManager, + Environment: app.AuthKeeper.Environment, }, &app.CircuitBreakerKeeper, - app.UnorderedTxManager, }, ) if err != nil { @@ -346,12 +313,6 @@ func (app *SimApp) setCustomAnteHandler() { app.SetAnteHandler(anteHandler) } -// Close implements the Application interface and closes all necessary application -// resources. -func (app *SimApp) Close() error { - return app.UnorderedTxManager.Close() -} - // LegacyAmino returns SimApp's amino codec. // // NOTE: This is solely to be used for testing purposes as it may be desirable @@ -383,29 +344,6 @@ func (app *SimApp) TxConfig() client.TxConfig { return app.txConfig } -// GetKey returns the KVStoreKey for the provided store key. -// -// NOTE: This is solely to be used for testing purposes. -func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { - sk := app.UnsafeFindStoreKey(storeKey) - kvStoreKey, ok := sk.(*storetypes.KVStoreKey) - if !ok { - return nil - } - return kvStoreKey -} - -func (app *SimApp) kvStoreKeys() map[string]*storetypes.KVStoreKey { - keys := make(map[string]*storetypes.KVStoreKey) - for _, k := range app.GetStoreKeys() { - if kv, ok := k.(*storetypes.KVStoreKey); ok { - keys[kv.Name()] = kv - } - } - - return keys -} - // SimulationManager implements the SimulationApp interface func (app *SimApp) SimulationManager() *module.SimulationManager { return app.sm diff --git a/simapp/simd/cmd/config.go b/simapp/simd/cmd/config.go index ce6698e255b1..b74d0c56985e 100644 --- a/simapp/simd/cmd/config.go +++ b/simapp/simd/cmd/config.go @@ -103,7 +103,6 @@ func initAppConfig() (string, interface{}) { // // In simapp, we set the min gas prices to 0. srvCfg.MinGasPrices = "0stake" - // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default // Now we set the custom config default values. customAppConfig := CustomAppConfig{ diff --git a/simapp/v2/app_config.go b/simapp/v2/app_config.go index ab54100a3f09..4930d0cd2135 100644 --- a/simapp/v2/app_config.go +++ b/simapp/v2/app_config.go @@ -1,4 +1,4 @@ -//nolint:unused,nolintlint // ignore unused code linting and directive `//nolint:unused // ignore unused code linting` is unused for linter "unused" +//nolint:unused,nolintlint // ignore unused code linting package simapp import ( diff --git a/testutil/network/network.go b/testutil/network/network.go index e34afc44b4f1..9729ebe0b4ba 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -49,6 +49,7 @@ import ( srvconfig "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/testutil" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" @@ -210,7 +211,10 @@ func DefaultConfigWithAppConfig(appConfig depinject.Config, baseappOpts ...func( if err := depinject.Inject( depinject.Configs( appConfig, - depinject.Supply(val.GetLogger()), + depinject.Supply( + val.GetLogger(), + simtestutil.NewAppOptionsWithFlagHome(val.GetViper().GetString(flags.FlagHome)), + ), ), &appBuilder); err != nil { panic(err) diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go index c81070242ed5..b052f943b624 100644 --- a/x/auth/ante/ante.go +++ b/x/auth/ante/ante.go @@ -4,6 +4,7 @@ import ( "cosmossdk.io/core/appmodule" "cosmossdk.io/core/gas" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/x/auth/ante/unorderedtx" "cosmossdk.io/x/auth/types" txsigning "cosmossdk.io/x/tx/signing" @@ -23,6 +24,7 @@ type HandlerOptions struct { SignModeHandler *txsigning.HandlerMap SigGasConsumer func(meter gas.Meter, sig signing.SignatureV2, params types.Params) error TxFeeChecker TxFeeChecker + UnorderedTxManager *unorderedtx.Manager } // NewAnteHandler returns an AnteHandler that checks and increments sequence @@ -53,5 +55,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler, options.SigGasConsumer, options.AccountAbstractionKeeper), } + if options.UnorderedTxManager != nil { + anteDecorators = append(anteDecorators, NewUnorderedTxDecorator(unorderedtx.DefaultMaxTimeoutDuration, options.UnorderedTxManager, options.Environment, DefaultSha256Cost)) + } + return sdk.ChainAnteDecorators(anteDecorators...), nil } diff --git a/x/auth/tx/config/depinject.go b/x/auth/tx/config/depinject.go index bdf3879efbc0..09c49fd4e937 100644 --- a/x/auth/tx/config/depinject.go +++ b/x/auth/tx/config/depinject.go @@ -20,6 +20,7 @@ import ( "cosmossdk.io/depinject" "cosmossdk.io/depinject/appconfig" "cosmossdk.io/x/auth/ante" + "cosmossdk.io/x/auth/ante/unorderedtx" "cosmossdk.io/x/auth/posthandler" "cosmossdk.io/x/auth/tx" authtypes "cosmossdk.io/x/auth/types" @@ -58,6 +59,7 @@ type ModuleInputs struct { AccountAbstractionKeeper ante.AccountAbstractionKeeper `optional:"true"` CustomSignModeHandlers func() []txsigning.SignModeHandler `optional:"true"` CustomGetSigners []txsigning.CustomGetSigner `optional:"true"` + UnorderedTxManager *unorderedtx.Manager `optional:"true"` } type ModuleOutputs struct { @@ -162,12 +164,13 @@ func newAnteHandler(txConfig client.TxConfig, in ModuleInputs) (sdk.AnteHandler, anteHandler, err := ante.NewAnteHandler( ante.HandlerOptions{ - AccountKeeper: in.AccountKeeper, - BankKeeper: in.BankKeeper, - SignModeHandler: txConfig.SignModeHandler(), - FeegrantKeeper: in.FeeGrantKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - Environment: in.Environment, + Environment: in.Environment, + AccountKeeper: in.AccountKeeper, + BankKeeper: in.BankKeeper, + SignModeHandler: txConfig.SignModeHandler(), + FeegrantKeeper: in.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + UnorderedTxManager: in.UnorderedTxManager, }, ) if err != nil {