diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 308c61887003..e360907353d9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,7 +25,7 @@ /runtime/v2/ @julienrbrt @hieuvubk @cosmos/sdk-core-dev /schema/ @aaronc @testinginprod @cosmos/sdk-core-dev /server/ @cosmos/sdk-core-dev -/server/v2/ @julienrbrt @hieuvubk @cosmos/sdk-core-dev +/server/v2/ @julienrbrt @hieuvubk @kocubinski @cosmos/sdk-core-dev /server/v2/stf/ @testinginprod @kocubinski @cosmos/sdk-core-dev /server/v2/appmanager/ @testinginprod @facundomedica @cosmos/sdk-core-dev /server/v2/cometbft/ @facundomedica @sontrinh16 @cosmos/sdk-core-dev diff --git a/runtime/config.go b/runtime/config.go new file mode 100644 index 000000000000..6474ebafbe02 --- /dev/null +++ b/runtime/config.go @@ -0,0 +1,46 @@ +package runtime + +import ( + "cosmossdk.io/core/server" + "cosmossdk.io/depinject" +) + +// ModuleConfigMaps is a map module scoped ConfigMaps +type ModuleConfigMaps map[string]server.ConfigMap + +type ModuleConfigMapsInput struct { + depinject.In + + ModuleConfigs []server.ModuleConfigMap + DynamicConfig server.DynamicConfig `optional:"true"` +} + +// ProvideModuleConfigMaps returns a map of module name to module config map. +// The module config map is a map of flag to value. +func ProvideModuleConfigMaps(in ModuleConfigMapsInput) ModuleConfigMaps { + moduleConfigMaps := make(ModuleConfigMaps) + if in.DynamicConfig == nil { + return moduleConfigMaps + } + for _, moduleConfig := range in.ModuleConfigs { + cfg := moduleConfig.Config + name := moduleConfig.Module + moduleConfigMaps[name] = make(server.ConfigMap) + for flag, df := range cfg { + val := in.DynamicConfig.Get(flag) + if val != nil { + moduleConfigMaps[name][flag] = val + } else { + moduleConfigMaps[name][flag] = df + } + } + } + return moduleConfigMaps +} + +func ProvideModuleScopedConfigMap( + key depinject.ModuleKey, + moduleConfigs ModuleConfigMaps, +) server.ConfigMap { + return moduleConfigs[key.Name()] +} diff --git a/runtime/module.go b/runtime/module.go index 773efe10a3e2..3cf7be226421 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -103,6 +103,8 @@ func init() { ProvideTransientStoreService, ProvideModuleManager, ProvideCometService, + ProvideModuleConfigMaps, + ProvideModuleScopedConfigMap, ), appconfig.Invoke(SetupAppBuilder), ) diff --git a/runtime/v2/app.go b/runtime/v2/app.go index b7887ab77f54..0c017fdcbcd9 100644 --- a/runtime/v2/app.go +++ b/runtime/v2/app.go @@ -83,7 +83,7 @@ func (a *App[T]) LoadLatestHeight() (uint64, error) { return a.db.GetLatestVersion() } -// GetQueryHandlers returns the query handlers. +// QueryHandlers returns the query handlers. func (a *App[T]) QueryHandlers() map[string]appmodulev2.Handler { return a.queryHandlers } diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index 8556e35745a8..e6e8cb7c4ea5 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -24,6 +24,7 @@ import ( type AppBuilder[T transaction.Tx] struct { app *App[T] storeBuilder root.Builder + storeConfig *root.Config // the following fields are used to overwrite the default branch func(state store.ReaderMap) store.WriterMap @@ -82,12 +83,13 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { } } - a.app.db = a.storeBuilder.Get() - if a.app.db == nil { - return nil, fmt.Errorf("storeBuilder did not return a db") + var err error + a.app.db, err = a.storeBuilder.Build(a.app.logger, a.storeConfig) + if err != nil { + return nil, err } - if err := a.app.moduleManager.RegisterServices(a.app); err != nil { + if err = a.app.moduleManager.RegisterServices(a.app); err != nil { return nil, err } diff --git a/runtime/v2/config.go b/runtime/v2/config.go new file mode 100644 index 000000000000..4cddadd7be20 --- /dev/null +++ b/runtime/v2/config.go @@ -0,0 +1,67 @@ +package runtime + +import ( + "strings" + + "cosmossdk.io/core/server" + "cosmossdk.io/depinject" +) + +// GlobalConfig is a recursive configuration map containing configuration +// key-value pairs parsed from the configuration file, flags, or other +// input sources. +// +// It is aliased to server.ConfigMap so that DI can distinguish between +// module-scoped and global configuration maps. In the DI container `server.ConfigMap` +// objects are module-scoped and `GlobalConfig` is global-scoped. +type GlobalConfig server.ConfigMap + +// ModuleConfigMaps is a map module scoped ConfigMaps +type ModuleConfigMaps map[string]server.ConfigMap + +// ProvideModuleConfigMaps returns a map of module name to module config map. +// The module config map is a map of flag to value. +func ProvideModuleConfigMaps( + moduleConfigs []server.ModuleConfigMap, + globalConfig GlobalConfig, +) ModuleConfigMaps { + moduleConfigMaps := make(ModuleConfigMaps) + for _, moduleConfig := range moduleConfigs { + cfg := moduleConfig.Config + name := moduleConfig.Module + moduleConfigMaps[name] = make(server.ConfigMap) + for flag, df := range cfg { + m := globalConfig + fetchFlag := flag + // splitting on "." is required to handle nested flags which are defined + // in other modules that are not the current module + // for example: "server.minimum-gas-prices" is defined in the server module + // but required by x/validate + for _, part := range strings.Split(flag, ".") { + if maybeMap, ok := m[part]; ok { + innerMap, ok := maybeMap.(map[string]any) + if !ok { + fetchFlag = part + break + } + m = innerMap + } else { + break + } + } + if val, ok := m[fetchFlag]; ok { + moduleConfigMaps[name][flag] = val + } else { + moduleConfigMaps[name][flag] = df + } + } + } + return moduleConfigMaps +} + +func ProvideModuleScopedConfigMap( + key depinject.ModuleKey, + moduleConfigs ModuleConfigMaps, +) server.ConfigMap { + return moduleConfigs[key.Name()] +} diff --git a/runtime/v2/module.go b/runtime/v2/module.go index 54d77dc2742f..a2cca4a96337 100644 --- a/runtime/v2/module.go +++ b/runtime/v2/module.go @@ -99,6 +99,8 @@ func init() { ProvideModuleManager[transaction.Tx], ProvideEnvironment, ProvideKVService, + ProvideModuleConfigMaps, + ProvideModuleScopedConfigMap, ), appconfig.Invoke(SetupAppBuilder), ) @@ -108,6 +110,7 @@ func ProvideAppBuilder[T transaction.Tx]( interfaceRegistrar registry.InterfaceRegistrar, amino registry.AminoRegistrar, storeBuilder root.Builder, + storeConfig *root.Config, ) ( *AppBuilder[T], *stf.MsgRouterBuilder, @@ -134,7 +137,7 @@ func ProvideAppBuilder[T transaction.Tx]( queryHandlers: map[string]appmodulev2.Handler{}, storeLoader: DefaultStoreLoader, } - appBuilder := &AppBuilder[T]{app: app, storeBuilder: storeBuilder} + appBuilder := &AppBuilder[T]{app: app, storeBuilder: storeBuilder, storeConfig: storeConfig} return appBuilder, msgRouterBuilder, appModule[T]{app}, protoFiles, protoTypes } @@ -142,6 +145,7 @@ func ProvideAppBuilder[T transaction.Tx]( type AppInputs struct { depinject.In + StoreConfig *root.Config Config *runtimev2.Module AppBuilder *AppBuilder[transaction.Tx] ModuleManager *MM[transaction.Tx] diff --git a/server/v2/api/grpc/server.go b/server/v2/api/grpc/server.go index 10e22a514a1e..4768b74ca1c8 100644 --- a/server/v2/api/grpc/server.go +++ b/server/v2/api/grpc/server.go @@ -21,6 +21,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -42,40 +43,50 @@ type Server[T transaction.Tx] struct { } // New creates a new grpc server. -func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] { - return &Server[T]{ +func New[T transaction.Tx]( + logger log.Logger, + interfaceRegistry server.InterfaceRegistry, + queryHandlers map[string]appmodulev2.Handler, + queryable interface { + Query(ctx context.Context, version uint64, msg transaction.Msg) (transaction.Msg, error) + }, + cfg server.ConfigMap, + cfgOptions ...CfgOption, +) (*Server[T], error) { + srv := &Server[T]{ cfgOptions: cfgOptions, } -} - -// Init returns a correctly configured and initialized gRPC server. -// Note, the caller is responsible for starting the server. -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - serverCfg := s.Config().(*Config) + serverCfg := srv.Config().(*Config) if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &serverCfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) } } - methodsMap := appI.QueryHandlers() grpcSrv := grpc.NewServer( - grpc.ForceServerCodec(newProtoCodec(appI.InterfaceRegistry()).GRPCCodec()), + grpc.ForceServerCodec(newProtoCodec(interfaceRegistry).GRPCCodec()), grpc.MaxSendMsgSize(serverCfg.MaxSendMsgSize), grpc.MaxRecvMsgSize(serverCfg.MaxRecvMsgSize), - grpc.UnknownServiceHandler( - makeUnknownServiceHandler(methodsMap, appI), - ), + grpc.UnknownServiceHandler(makeUnknownServiceHandler(queryHandlers, queryable)), ) // Reflection allows external clients to see what services and methods the gRPC server exposes. - gogoreflection.Register(grpcSrv, slices.Collect(maps.Keys(methodsMap)), logger.With("sub-module", "grpc-reflection")) + gogoreflection.Register(grpcSrv, slices.Collect(maps.Keys(queryHandlers)), logger.With("sub-module", "grpc-reflection")) - s.grpcSrv = grpcSrv - s.config = serverCfg - s.logger = logger.With(log.ModuleKey, s.Name()) + srv.grpcSrv = grpcSrv + srv.config = serverCfg + srv.logger = logger.With(log.ModuleKey, srv.Name()) - return nil + return srv, nil +} + +// NewWithConfigOptions creates a new GRPC server with the provided config options. +// It is *not* a fully functional server (since it has been created without dependencies) +// The returned server should only be used to get and set configuration. +func NewWithConfigOptions[T transaction.Tx](opts ...CfgOption) *Server[T] { + return &Server[T]{ + cfgOptions: opts, + } } func (s *Server[T]) StartCmdFlags() *pflag.FlagSet { @@ -186,7 +197,7 @@ func (s *Server[T]) Config() any { return s.config } -func (s *Server[T]) Start(ctx context.Context) error { +func (s *Server[T]) Start(context.Context) error { if !s.config.Enable { s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) return nil diff --git a/server/v2/api/grpcgateway/server.go b/server/v2/api/grpcgateway/server.go index 412c4e4ad81d..7fba8ce1be20 100644 --- a/server/v2/api/grpcgateway/server.go +++ b/server/v2/api/grpcgateway/server.go @@ -11,6 +11,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -34,7 +35,13 @@ type Server[T transaction.Tx] struct { } // New creates a new gRPC-gateway server. -func New[T transaction.Tx](grpcSrv *grpc.Server, ir jsonpb.AnyResolver, cfgOptions ...CfgOption) *Server[T] { +func New[T transaction.Tx]( + logger log.Logger, + config server.ConfigMap, + grpcSrv *grpc.Server, + ir jsonpb.AnyResolver, + cfgOptions ...CfgOption, +) (*Server[T], error) { // The default JSON marshaller used by the gRPC-Gateway is unable to marshal non-nullable non-scalar fields. // Using the gogo/gateway package with the gRPC-Gateway WithMarshaler option fixes the scalar field marshaling issue. marshalerOption := &gateway.JSONPb{ @@ -44,7 +51,7 @@ func New[T transaction.Tx](grpcSrv *grpc.Server, ir jsonpb.AnyResolver, cfgOptio AnyResolver: ir, } - return &Server[T]{ + s := &Server[T]{ gRPCSrv: grpcSrv, gRPCGatewayRouter: runtime.NewServeMux( // Custom marshaler option is required for gogo proto @@ -60,6 +67,20 @@ func New[T transaction.Tx](grpcSrv *grpc.Server, ir jsonpb.AnyResolver, cfgOptio ), cfgOptions: cfgOptions, } + + serverCfg := s.Config().(*Config) + if len(config) > 0 { + if err := serverv2.UnmarshalSubConfig(config, s.Name(), &serverCfg); err != nil { + return s, fmt.Errorf("failed to unmarshal config: %w", err) + } + } + + // TODO: register the gRPC-Gateway routes + + s.logger = logger.With(log.ModuleKey, s.Name()) + s.config = serverCfg + + return s, nil } func (s *Server[T]) Name() string { @@ -80,22 +101,6 @@ func (s *Server[T]) Config() any { return s.config } -func (s *Server[T]) Init(appI serverv2.AppI[transaction.Tx], cfg map[string]any, logger log.Logger) error { - serverCfg := s.Config().(*Config) - if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - } - - // TODO: register the gRPC-Gateway routes - - s.logger = logger.With(log.ModuleKey, s.Name()) - s.config = serverCfg - - return nil -} - func (s *Server[T]) Start(ctx context.Context) error { if !s.config.Enable { s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) diff --git a/server/v2/api/rest/server.go b/server/v2/api/rest/server.go index 0f2b1777973a..327ec9d0d69b 100644 --- a/server/v2/api/rest/server.go +++ b/server/v2/api/rest/server.go @@ -6,9 +6,11 @@ import ( "fmt" "net/http" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" + "cosmossdk.io/server/v2/appmanager" ) const ( @@ -24,31 +26,42 @@ type Server[T transaction.Tx] struct { cfgOptions []CfgOption } -func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] { - return &Server[T]{ +func New[T transaction.Tx]( + appManager appmanager.AppManager[T], + logger log.Logger, + cfg server.ConfigMap, + cfgOptions ...CfgOption, +) (*Server[T], error) { + srv := &Server[T]{ cfgOptions: cfgOptions, + logger: logger.With(log.ModuleKey, ServerName), + router: http.NewServeMux(), } -} -func (s *Server[T]) Name() string { - return ServerName -} - -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - s.logger = logger.With(log.ModuleKey, s.Name()) + srv.router.Handle("/", NewDefaultHandler(appManager)) - serverCfg := s.Config().(*Config) + serverCfg := srv.Config().(*Config) if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &serverCfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) } } + srv.config = serverCfg + + return srv, nil +} - s.router = http.NewServeMux() - s.router.Handle("/", NewDefaultHandler(appI)) - s.config = serverCfg +// NewWithConfigOptions creates a new REST server with the provided config options. +// It is *not* a fully functional server (since it has been created without dependencies) +// The returned server should only be used to get and set configuration. +func NewWithConfigOptions[T transaction.Tx](opts ...CfgOption) *Server[T] { + return &Server[T]{ + cfgOptions: opts, + } +} - return nil +func (s *Server[T]) Name() string { + return ServerName } func (s *Server[T]) Start(ctx context.Context) error { diff --git a/server/v2/api/rest/server_test.go b/server/v2/api/rest/server_test.go index cec027f5d24b..3fb798979a4b 100644 --- a/server/v2/api/rest/server_test.go +++ b/server/v2/api/rest/server_test.go @@ -17,7 +17,7 @@ func TestServerConfig(t *testing.T) { { name: "Default configuration, no custom configuration", setupFunc: func() *Config { - s := New[transaction.Tx]() + s := &Server[transaction.Tx]{} return s.Config().(*Config) }, expectedConfig: DefaultConfig(), @@ -25,7 +25,7 @@ func TestServerConfig(t *testing.T) { { name: "Custom configuration", setupFunc: func() *Config { - s := New[transaction.Tx](func(config *Config) { + s := NewWithConfigOptions[transaction.Tx](func(config *Config) { config.Enable = false }) return s.Config().(*Config) diff --git a/server/v2/api/telemetry/server.go b/server/v2/api/telemetry/server.go index 411b1bda797d..42c501fe2534 100644 --- a/server/v2/api/telemetry/server.go +++ b/server/v2/api/telemetry/server.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" @@ -27,8 +28,23 @@ type Server[T transaction.Tx] struct { } // New creates a new telemetry server. -func New[T transaction.Tx]() *Server[T] { - return &Server[T]{} +func New[T transaction.Tx](cfg server.ConfigMap, logger log.Logger) (*Server[T], error) { + srv := &Server[T]{} + serverCfg := srv.Config().(*Config) + if len(cfg) > 0 { + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &serverCfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + } + srv.config = serverCfg + srv.logger = logger.With(log.ModuleKey, srv.Name()) + + metrics, err := NewMetrics(srv.config) + if err != nil { + return nil, fmt.Errorf("failed to initialize metrics: %w", err) + } + srv.metrics = metrics + return srv, nil } // Name returns the server name. @@ -44,26 +60,6 @@ func (s *Server[T]) Config() any { return s.config } -// Init implements serverv2.ServerComponent. -func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { - serverCfg := s.Config().(*Config) - if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - } - s.config = serverCfg - s.logger = logger.With(log.ModuleKey, s.Name()) - - metrics, err := NewMetrics(s.config) - if err != nil { - return fmt.Errorf("failed to initialize metrics: %w", err) - } - s.metrics = metrics - - return nil -} - func (s *Server[T]) Start(ctx context.Context) error { if !s.config.Enable { s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) diff --git a/server/v2/cometbft/server.go b/server/v2/cometbft/server.go index 1ad285bb99e7..33f7a31f1ca5 100644 --- a/server/v2/cometbft/server.go +++ b/server/v2/cometbft/server.go @@ -18,12 +18,17 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" + "cosmossdk.io/schema/decoding" "cosmossdk.io/schema/indexer" serverv2 "cosmossdk.io/server/v2" + "cosmossdk.io/server/v2/appmanager" cometlog "cosmossdk.io/server/v2/cometbft/log" "cosmossdk.io/server/v2/cometbft/mempool" + "cosmossdk.io/server/v2/cometbft/types" "cosmossdk.io/store/v2/snapshots" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" @@ -49,34 +54,39 @@ type CometBFTServer[T transaction.Tx] struct { } func New[T transaction.Tx]( + logger log.Logger, + appName string, + store types.Store, + appManager appmanager.AppManager[T], + queryHandlers map[string]appmodulev2.Handler, + decoderResolver decoding.DecoderResolver, txCodec transaction.Codec[T], + cfg server.ConfigMap, serverOptions ServerOptions[T], cfgOptions ...CfgOption, -) *CometBFTServer[T] { - return &CometBFTServer[T]{ +) (*CometBFTServer[T], error) { + srv := &CometBFTServer[T]{ initTxCodec: txCodec, serverOptions: serverOptions, cfgOptions: cfgOptions, } -} -func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error { home, _ := cfg[serverv2.FlagHome].(string) // get configs (app.toml + config.toml) from viper - appTomlConfig := s.Config().(*AppTomlConfig) + appTomlConfig := srv.Config().(*AppTomlConfig) configTomlConfig := cmtcfg.DefaultConfig().SetRoot(home) if len(cfg) > 0 { - if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &appTomlConfig); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &appTomlConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) } if err := serverv2.UnmarshalSubConfig(cfg, "", &configTomlConfig); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) + return nil, fmt.Errorf("failed to unmarshal config: %w", err) } } - s.config = Config{ + srv.config = Config{ ConfigTomlConfig: configTomlConfig, AppTomlConfig: appTomlConfig, } @@ -96,59 +106,68 @@ func (s *CometBFTServer[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logg } } - indexEvents := make(map[string]struct{}, len(s.config.AppTomlConfig.IndexEvents)) - for _, e := range s.config.AppTomlConfig.IndexEvents { + indexEvents := make(map[string]struct{}, len(srv.config.AppTomlConfig.IndexEvents)) + for _, e := range srv.config.AppTomlConfig.IndexEvents { indexEvents[e] = struct{}{} } - s.logger = logger.With(log.ModuleKey, s.Name()) - rs := appI.Store() + srv.logger = logger.With(log.ModuleKey, srv.Name()) consensus := NewConsensus( - s.logger, - appI.Name(), - appI, - appI.Close, - s.serverOptions.Mempool(cfg), + logger, + appName, + appManager, + nil, + srv.serverOptions.Mempool(cfg), indexEvents, - appI.QueryHandlers(), - rs, - s.config, - s.initTxCodec, + queryHandlers, + store, + srv.config, + srv.initTxCodec, chainID, ) - consensus.prepareProposalHandler = s.serverOptions.PrepareProposalHandler - consensus.processProposalHandler = s.serverOptions.ProcessProposalHandler - consensus.checkTxHandler = s.serverOptions.CheckTxHandler - consensus.verifyVoteExt = s.serverOptions.VerifyVoteExtensionHandler - consensus.extendVote = s.serverOptions.ExtendVoteHandler - consensus.addrPeerFilter = s.serverOptions.AddrPeerFilter - consensus.idPeerFilter = s.serverOptions.IdPeerFilter - - ss := rs.GetStateStorage().(snapshots.StorageSnapshotter) - sc := rs.GetStateCommitment().(snapshots.CommitSnapshotter) - - snapshotStore, err := GetSnapshotStore(s.config.ConfigTomlConfig.RootDir) + consensus.prepareProposalHandler = srv.serverOptions.PrepareProposalHandler + consensus.processProposalHandler = srv.serverOptions.ProcessProposalHandler + consensus.checkTxHandler = srv.serverOptions.CheckTxHandler + consensus.verifyVoteExt = srv.serverOptions.VerifyVoteExtensionHandler + consensus.extendVote = srv.serverOptions.ExtendVoteHandler + consensus.addrPeerFilter = srv.serverOptions.AddrPeerFilter + consensus.idPeerFilter = srv.serverOptions.IdPeerFilter + + ss := store.GetStateStorage().(snapshots.StorageSnapshotter) + sc := store.GetStateCommitment().(snapshots.CommitSnapshotter) + + snapshotStore, err := GetSnapshotStore(srv.config.ConfigTomlConfig.RootDir) if err != nil { - return err + return nil, err } - consensus.snapshotManager = snapshots.NewManager(snapshotStore, s.serverOptions.SnapshotOptions(cfg), sc, ss, nil, s.logger) + consensus.snapshotManager = snapshots.NewManager( + snapshotStore, srv.serverOptions.SnapshotOptions(cfg), sc, ss, nil, logger) + + srv.Consensus = consensus // initialize the indexer - if indexerCfg := s.config.AppTomlConfig.Indexer; len(indexerCfg.Target) > 0 { + if indexerCfg := srv.config.AppTomlConfig.Indexer; len(indexerCfg.Target) > 0 { listener, err := indexer.StartIndexing(indexer.IndexingOptions{ Config: indexerCfg, - Resolver: appI.SchemaDecoderResolver(), - Logger: s.logger.With(log.ModuleKey, "indexer"), + Resolver: decoderResolver, + Logger: logger.With(log.ModuleKey, "indexer"), }) if err != nil { - return fmt.Errorf("failed to start indexing: %w", err) + return nil, fmt.Errorf("failed to start indexing: %w", err) } consensus.listener = &listener.Listener } - s.Consensus = consensus + return srv, nil +} - return nil +// NewWithConfigOptions creates a new CometBFT server with the provided config options. +// It is *not* a fully functional server (since it has been created without dependencies) +// The returned server should only be used to get and set configuration. +func NewWithConfigOptions[T transaction.Tx](opts ...CfgOption) *CometBFTServer[T] { + return &CometBFTServer[T]{ + cfgOptions: opts, + } } func (s *CometBFTServer[T]) Name() string { diff --git a/server/v2/command_factory.go b/server/v2/command_factory.go new file mode 100644 index 000000000000..a9f544a2f3b8 --- /dev/null +++ b/server/v2/command_factory.go @@ -0,0 +1,185 @@ +package serverv2 + +import ( + "errors" + "io" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "cosmossdk.io/core/server" + "cosmossdk.io/log" +) + +// CommandFactory is a factory help create server/v2 root commands. +// For example usage see simapp/v2/cmd/root_di.go +type CommandFactory struct { + defaultHomeDir string + envPrefix string + configWriter ConfigWriter + loggerFactory func(server.ConfigMap, io.Writer) (log.Logger, error) + + logger log.Logger + // TODO remove this field + // this viper handle is kept because certain commands in server/v2 fetch a viper instance + // from the command context in order to read the config. + // After merging #22267 this is no longer required, and server.ConfigMap can be used instead. + // See issue #22388 + vipr *viper.Viper +} + +type CommandFactoryOption func(*CommandFactory) error + +// NewCommandFactory creates a new CommandFactory with the given options. +func NewCommandFactory(opts ...CommandFactoryOption) (*CommandFactory, error) { + f := &CommandFactory{} + for _, opt := range opts { + err := opt(f) + if err != nil { + return nil, err + } + } + return f, nil +} + +// WithEnvPrefix sets the environment variable prefix for the command factory. +func WithEnvPrefix(envPrefix string) CommandFactoryOption { + return func(f *CommandFactory) error { + f.envPrefix = envPrefix + return nil + } +} + +// WithStdDefaultHomeDir sets the server's default home directory `user home directory`/`defaultHomeBasename`. +func WithStdDefaultHomeDir(defaultHomeBasename string) CommandFactoryOption { + return func(f *CommandFactory) error { + // get the home directory from the environment variable + // to not clash with the $HOME system variable, when no prefix is set + // we check the NODE_HOME environment variable + homeDir, envHome := "", "HOME" + if len(f.envPrefix) > 0 { + homeDir = os.Getenv(f.envPrefix + "_" + envHome) + } else { + homeDir = os.Getenv("NODE_" + envHome) + } + if homeDir != "" { + f.defaultHomeDir = filepath.Clean(homeDir) + return nil + } + + // get user home directory + userHomeDir, err := os.UserHomeDir() + if err != nil { + return err + } + + f.defaultHomeDir = filepath.Join(userHomeDir, defaultHomeBasename) + return nil + } +} + +// WithDefaultHomeDir sets the server's default home directory. +func WithDefaultHomeDir(homedir string) CommandFactoryOption { + return func(f *CommandFactory) error { + f.defaultHomeDir = homedir + return nil + } +} + +// WithConfigWriter sets the config writer for the command factory. +// If set the config writer will be used to write TOML config files during ParseCommand invocations. +func WithConfigWriter(configWriter ConfigWriter) CommandFactoryOption { + return func(f *CommandFactory) error { + f.configWriter = configWriter + return nil + } +} + +// WithLoggerFactory sets the logger factory for the command factory. +func WithLoggerFactory(loggerFactory func(server.ConfigMap, io.Writer) (log.Logger, error)) CommandFactoryOption { + return func(f *CommandFactory) error { + f.loggerFactory = loggerFactory + return nil + } +} + +// enhanceCommand adds the following flags to the command: +// +// --log-level: The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:,:') +// --log-format: The logging format (json|plain) +// --log-no-color: Disable colored logs +// --home: directory for config and data +// +// It also sets the environment variable prefix for the viper instance. +func (f *CommandFactory) enhanceCommand(cmd *cobra.Command) { + pflags := cmd.PersistentFlags() + pflags.String(FlagLogLevel, "info", "The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:,:')") + pflags.String(FlagLogFormat, "plain", "The logging format (json|plain)") + pflags.Bool(FlagLogNoColor, false, "Disable colored logs") + pflags.StringP(FlagHome, "", f.defaultHomeDir, "directory for config and data") + viper.SetEnvPrefix(f.envPrefix) + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) + viper.AutomaticEnv() +} + +// EnhanceRootCommand sets the viper and logger in the command context. +func (f *CommandFactory) EnhanceRootCommand(cmd *cobra.Command) { + f.enhanceCommand(cmd) + SetCmdServerContext(cmd, f.vipr, f.logger) +} + +// ParseCommand parses args against the input rootCmd CLI skeleton then returns the target subcommand, +// a fully realized config map, and a properly configured logger. +// If `WithConfigWriter` was set in the factory options, the config writer will be used to write the app.toml file. +// Internally a viper instance is created and used to bind the flags to the config map. +// Future invocations of EnhanceCommandContext will set the viper instance and logger in the command context. +func (f *CommandFactory) ParseCommand( + rootCmd *cobra.Command, + args []string, +) (*cobra.Command, server.ConfigMap, log.Logger, error) { + f.enhanceCommand(rootCmd) + cmd, _, err := rootCmd.Traverse(args) + if err != nil { + return nil, nil, nil, err + } + if err = cmd.ParseFlags(args); err != nil { + // help requested, return the command early + if errors.Is(err, pflag.ErrHelp) { + return cmd, nil, nil, nil + } + return nil, nil, nil, err + } + home, err := cmd.Flags().GetString(FlagHome) + if err != nil { + return nil, nil, nil, err + } + configDir := filepath.Join(home, "config") + if f.configWriter != nil { + // create app.toml if it does not already exist + if _, err = os.Stat(filepath.Join(configDir, "app.toml")); os.IsNotExist(err) { + if err = f.configWriter.WriteConfig(configDir); err != nil { + return nil, nil, nil, err + } + } + } + f.vipr, err = ReadConfig(configDir) + if err != nil { + return nil, nil, nil, err + } + if err = f.vipr.BindPFlags(cmd.Flags()); err != nil { + return nil, nil, nil, err + } + + if f.loggerFactory != nil { + f.logger, err = f.loggerFactory(f.vipr.AllSettings(), cmd.OutOrStdout()) + if err != nil { + return nil, nil, nil, err + } + } + + return cmd, f.vipr.AllSettings(), f.logger, nil +} diff --git a/server/v2/commands.go b/server/v2/commands.go index c64fbc1f0de4..aab0e8eb3667 100644 --- a/server/v2/commands.go +++ b/server/v2/commands.go @@ -5,68 +5,33 @@ import ( "errors" "os" "os/signal" - "path/filepath" "runtime/pprof" + "slices" "strings" "syscall" "github.com/spf13/cobra" - "github.com/spf13/viper" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" ) -// Execute executes the root command of an application. -// It handles adding core CLI flags, specifically the logging flags. -func Execute(rootCmd *cobra.Command, envPrefix, defaultHome string) error { - rootCmd.PersistentFlags().String(FlagLogLevel, "info", "The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:,:')") - rootCmd.PersistentFlags().String(FlagLogFormat, "plain", "The logging format (json|plain)") - rootCmd.PersistentFlags().Bool(FlagLogNoColor, false, "Disable colored logs") - rootCmd.PersistentFlags().StringP(FlagHome, "", defaultHome, "directory for config and data") - - // update the global viper with the root command's configuration - viper.SetEnvPrefix(envPrefix) - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) - viper.AutomaticEnv() - - return rootCmd.Execute() -} - // AddCommands add the server commands to the root command // It configures the config handling and the logger handling func AddCommands[T transaction.Tx]( rootCmd *cobra.Command, - newApp AppCreator[T], - globalServerCfg ServerConfig, + logger log.Logger, + globalAppConfig server.ConfigMap, + globalServerConfig ServerConfig, components ...ServerComponent[T], -) error { +) (ConfigWriter, error) { if len(components) == 0 { - return errors.New("no components provided") - } - - server := NewServer(globalServerCfg, components...) - originalPersistentPreRunE := rootCmd.PersistentPreRunE - rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { - // set the default command outputs - cmd.SetOut(cmd.OutOrStdout()) - cmd.SetErr(cmd.ErrOrStderr()) - - if err := configHandle(server, cmd); err != nil { - return err - } - - // call the original PersistentPreRun(E) if it exists - if rootCmd.PersistentPreRun != nil { - rootCmd.PersistentPreRun(cmd, args) - return nil - } - - return originalPersistentPreRunE(cmd, args) + return nil, errors.New("no components provided") } - - cmds := server.CLICommands() - startCmd := createStartCommand(server, newApp) + srv := NewServer(globalServerConfig, components...) + cmds := srv.CLICommands() + startCmd := createStartCommand(srv, globalAppConfig, logger) startCmd.SetContext(rootCmd.Context()) cmds.Commands = append(cmds.Commands, startCmd) rootCmd.AddCommand(cmds.Commands...) @@ -92,13 +57,14 @@ func AddCommands[T transaction.Tx]( } } - return nil + return srv, nil } // createStartCommand creates the start command for the application. func createStartCommand[T transaction.Tx]( server *Server[T], - newApp AppCreator[T], + config server.ConfigMap, + logger log.Logger, ) *cobra.Command { flags := server.StartFlags() @@ -106,16 +72,6 @@ func createStartCommand[T transaction.Tx]( Use: "start", Short: "Run the application", RunE: func(cmd *cobra.Command, args []string) error { - v := GetViperFromCmd(cmd) - l := GetLoggerFromCmd(cmd) - if err := v.BindPFlags(cmd.Flags()); err != nil { - return err - } - - if err := server.Init(newApp(l, v), v.AllSettings(), l); err != nil { - return err - } - ctx, cancelFn := context.WithCancel(cmd.Context()) go func() { sigCh := make(chan os.Signal, 1) @@ -135,7 +91,7 @@ func createStartCommand[T transaction.Tx]( } }() - return wrapCPUProfile(l, v, func() error { + return wrapCPUProfile(logger, config, func() error { return server.Start(ctx) }) }, @@ -151,14 +107,14 @@ func createStartCommand[T transaction.Tx]( // wrapCPUProfile starts CPU profiling, if enabled, and executes the provided // callbackFn, then waits for it to return. -func wrapCPUProfile(logger log.Logger, v *viper.Viper, callbackFn func() error) error { - cpuProfileFile := v.GetString(FlagCPUProfiling) - if len(cpuProfileFile) == 0 { +func wrapCPUProfile(logger log.Logger, cfg server.ConfigMap, callbackFn func() error) error { + cpuProfileFile, ok := cfg[FlagCPUProfiling] + if !ok { // if cpu profiling is not enabled, just run the callback return callbackFn() } - f, err := os.Create(cpuProfileFile) + f, err := os.Create(cpuProfileFile.(string)) if err != nil { return err } @@ -180,39 +136,6 @@ func wrapCPUProfile(logger log.Logger, v *viper.Viper, callbackFn func() error) return callbackFn() } -// configHandle writes the default config to the home directory if it does not exist and sets the server context -func configHandle[T transaction.Tx](s *Server[T], cmd *cobra.Command) error { - home, err := cmd.Flags().GetString(FlagHome) - if err != nil { - return err - } - - configDir := filepath.Join(home, "config") - - // we need to check app.toml as the config folder can already exist for the client.toml - if _, err := os.Stat(filepath.Join(configDir, "app.toml")); os.IsNotExist(err) { - if err = s.WriteConfig(configDir); err != nil { - return err - } - } - - v, err := ReadConfig(configDir) - if err != nil { - return err - } - - if err := v.BindPFlags(cmd.Flags()); err != nil { - return err - } - - logger, err := NewLogger(v, cmd.OutOrStdout()) - if err != nil { - return err - } - - return SetCmdServerContext(cmd, v, logger) -} - // findSubCommand finds a sub-command of the provided command whose Use // string is or begins with the provided subCmdName. // It verifies the command's aliases as well. @@ -246,3 +169,39 @@ func topLevelCmd(ctx context.Context, use, short string) *cobra.Command { return cmd } + +// appBuildingCommands are the commands which need a full application to be built +var appBuildingCommands = [][]string{ + {"start"}, + {"genesis", "export"}, +} + +// IsAppRequired determines if a command requires a full application to be built by +// recursively checking the command hierarchy against known command paths. +// +// The function works by: +// 1. Combining default appBuildingCommands with additional required commands +// 2. Building command paths by traversing up the command tree +// 3. Checking if any known command path matches the current command path +// +// Time Complexity: O(d * p) where d is command depth and p is number of paths +// Space Complexity: O(p) where p is total number of command paths +func IsAppRequired(cmd *cobra.Command, required ...[]string) bool { + m := make(map[string]bool) + cmds := append(appBuildingCommands, required...) + for _, c := range cmds { + slices.Reverse(c) + m[strings.Join(c, "")] = true + } + cmdPath := make([]string, 0, 5) // Pre-allocate with reasonable capacity + for { + cmdPath = append(cmdPath, cmd.Use) + if _, ok := m[strings.Join(cmdPath, "")]; ok { + return true + } + if cmd.Parent() == nil { + return false + } + cmd = cmd.Parent() + } +} diff --git a/server/v2/logger.go b/server/v2/logger.go index 8ca80e4cce45..33ef557ca540 100644 --- a/server/v2/logger.go +++ b/server/v2/logger.go @@ -4,33 +4,52 @@ import ( "io" "github.com/rs/zerolog" - "github.com/spf13/viper" + "cosmossdk.io/core/server" "cosmossdk.io/log" ) // NewLogger creates the default SDK logger. // It reads the log level and format from the server context. -func NewLogger(v *viper.Viper, out io.Writer) (log.Logger, error) { +func NewLogger(cfg server.ConfigMap, out io.Writer) (log.Logger, error) { var opts []log.Option - if v.GetString(FlagLogFormat) == OutputFormatJSON { + var ( + format string + noColor bool + trace bool + level string + ) + if v, ok := cfg[FlagLogFormat]; ok { + format = v.(string) + } + if v, ok := cfg[FlagLogNoColor]; ok { + noColor = v.(bool) + } + if v, ok := cfg[FlagTrace]; ok { + trace = v.(bool) + } + if v, ok := cfg[FlagLogLevel]; ok { + level = v.(string) + } + + if format == OutputFormatJSON { opts = append(opts, log.OutputJSONOption()) } opts = append(opts, - log.ColorOption(!v.GetBool(FlagLogNoColor)), - log.TraceOption(v.GetBool(FlagTrace))) + log.ColorOption(!noColor), + log.TraceOption(trace), + ) // check and set filter level or keys for the logger if any - logLvlStr := v.GetString(FlagLogLevel) - if logLvlStr == "" { + if level == "" { return log.NewLogger(out, opts...), nil } - logLvl, err := zerolog.ParseLevel(logLvlStr) + logLvl, err := zerolog.ParseLevel(level) switch { case err != nil: // If the log level is not a valid zerolog level, then we try to parse it as a key filter. - filterFunc, err := log.ParseLogLevel(logLvlStr) + filterFunc, err := log.ParseLogLevel(level) if err != nil { return nil, err } diff --git a/server/v2/server.go b/server/v2/server.go index 980cb3e5fed2..063be7d1df24 100644 --- a/server/v2/server.go +++ b/server/v2/server.go @@ -22,7 +22,6 @@ type ServerComponent[T transaction.Tx] interface { Start(context.Context) error Stop(context.Context) error - Init(AppI[T], map[string]any, log.Logger) error } // HasStartFlags is a server module that has start flags. @@ -38,6 +37,11 @@ type HasConfig interface { Config() any } +// ConfigWriter is a server module that can write its config to a file. +type ConfigWriter interface { + WriteConfig(path string) error +} + // HasCLICommands is a server module that has CLI commands. type HasCLICommands interface { CLICommands() CLIConfig @@ -186,30 +190,6 @@ func (s *Server[T]) StartCmdFlags() *pflag.FlagSet { return flags } -// Init initializes all server components with the provided application, configuration, and logger. -// It returns an error if any component fails to initialize. -func (s *Server[T]) Init(appI AppI[T], cfg map[string]any, logger log.Logger) error { - serverCfg := s.config - if len(cfg) > 0 { - if err := UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - } - - var components []ServerComponent[T] - for _, mod := range s.components { - if err := mod.Init(appI, cfg, logger); err != nil { - return err - } - - components = append(components, mod) - } - - s.config = serverCfg - s.components = components - return nil -} - // WriteConfig writes the config to the given path. // Note: it does not use viper.WriteConfigAs because we do not want to store flag values in the config. func (s *Server[T]) WriteConfig(configPath string) error { diff --git a/server/v2/server_mock_test.go b/server/v2/server_mock_test.go index 8b590ba2d32f..2158549eed0c 100644 --- a/server/v2/server_mock_test.go +++ b/server/v2/server_mock_test.go @@ -4,10 +4,6 @@ import ( "context" "fmt" "math/rand" - - "cosmossdk.io/core/transaction" - "cosmossdk.io/log" - serverv2 "cosmossdk.io/server/v2" ) type mockServerConfig struct { @@ -31,10 +27,6 @@ func (s *mockServer) Name() string { return s.name } -func (s *mockServer) Init(appI serverv2.AppI[transaction.Tx], cfg map[string]any, logger log.Logger) error { - return nil -} - func (s *mockServer) Start(ctx context.Context) error { for ctx.Err() == nil { s.ch <- fmt.Sprintf("%s mock server: %d", s.name, rand.Int()) diff --git a/server/v2/server_test.go b/server/v2/server_test.go index a53b71fc35b9..d71416dfb3fd 100644 --- a/server/v2/server_test.go +++ b/server/v2/server_test.go @@ -61,20 +61,18 @@ func TestServer(t *testing.T) { logger := log.NewLogger(os.Stdout) - ctx, err := serverv2.SetServerContext(context.Background(), v, logger) - require.NoError(t, err) + ctx := serverv2.SetServerContext(context.Background(), v, logger) + app := &mockApp[transaction.Tx]{} - grpcServer := grpc.New[transaction.Tx]() - err = grpcServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) + grpcServer, err := grpc.New[transaction.Tx](logger, app.InterfaceRegistry(), app.QueryHandlers(), app, cfg) require.NoError(t, err) - storeServer := store.New[transaction.Tx]() - err = storeServer.Init(&mockApp[transaction.Tx]{}, cfg, logger) + storeServer, err := store.New[transaction.Tx](app.Store(), cfg) require.NoError(t, err) mockServer := &mockServer{name: "mock-server-1", ch: make(chan string, 100)} - server := serverv2.NewServer( + server := serverv2.NewServer[transaction.Tx]( serverv2.DefaultServerConfig(), grpcServer, storeServer, diff --git a/server/v2/store/server.go b/server/v2/store/server.go index 1fafe4e25d53..897773abf28e 100644 --- a/server/v2/store/server.go +++ b/server/v2/store/server.go @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" + "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" - "cosmossdk.io/log" serverv2 "cosmossdk.io/server/v2" storev2 "cosmossdk.io/store/v2" "cosmossdk.io/store/v2/root" @@ -27,14 +27,15 @@ type Server[T transaction.Tx] struct { backend storev2.Backend } -func New[T transaction.Tx]() *Server[T] { - return &Server[T]{} -} - -func (s *Server[T]) Init(app serverv2.AppI[T], v map[string]any, _ log.Logger) (err error) { - s.backend = app.Store() - s.config, err = UnmarshalConfig(v) - return err +func New[T transaction.Tx](store storev2.Backend, cfg server.ConfigMap) (*Server[T], error) { + config, err := UnmarshalConfig(cfg) + if err != nil { + return nil, err + } + return &Server[T]{ + backend: store, + config: config, + }, nil } func (s *Server[T]) Name() string { @@ -58,7 +59,7 @@ func (s *Server[T]) CLICommands() serverv2.CLIConfig { s.ListSnapshotsCmd(), s.DumpArchiveCmd(), s.LoadArchiveCmd(), - s.RestoreSnapshotCmd(s.backend), + s.RestoreSnapshotCmd(), }, } } diff --git a/server/v2/store/snapshot.go b/server/v2/store/snapshot.go index cbde7cd9a7d9..c858d47757a9 100644 --- a/server/v2/store/snapshot.go +++ b/server/v2/store/snapshot.go @@ -75,7 +75,7 @@ func (s *Server[T]) ExportSnapshotCmd() *cobra.Command { } // RestoreSnapshotCmd returns a command to restore a snapshot -func (s *Server[T]) RestoreSnapshotCmd(rootStore storev2.Backend) *cobra.Command { +func (s *Server[T]) RestoreSnapshotCmd() *cobra.Command { cmd := &cobra.Command{ Use: "restore ", Short: "Restore app state from local snapshot", @@ -95,6 +95,10 @@ func (s *Server[T]) RestoreSnapshotCmd(rootStore storev2.Backend) *cobra.Command logger := log.NewLogger(cmd.OutOrStdout()) + rootStore, _, err := createRootStore(v, logger) + if err != nil { + return fmt.Errorf("failed to create root store: %w", err) + } sm, err := createSnapshotsManager(cmd, v, logger, rootStore) if err != nil { return err diff --git a/server/v2/util.go b/server/v2/util.go index d02ea30125e5..f17faf031595 100644 --- a/server/v2/util.go +++ b/server/v2/util.go @@ -15,26 +15,22 @@ import ( // SetServerContext sets the logger and viper in the context. // The server manager expects the logger and viper to be set in the context. -func SetServerContext(ctx context.Context, viper *viper.Viper, logger log.Logger) (context.Context, error) { +func SetServerContext(ctx context.Context, viper *viper.Viper, logger log.Logger) context.Context { if ctx == nil { ctx = context.Background() } ctx = context.WithValue(ctx, corectx.LoggerContextKey, logger) ctx = context.WithValue(ctx, corectx.ViperContextKey, viper) - return ctx, nil + return ctx } // SetCmdServerContext sets a command's Context value to the provided argument. // The server manager expects the logger and viper to be set in the context. // If the context has not been set, set the given context as the default. -func SetCmdServerContext(cmd *cobra.Command, viper *viper.Viper, logger log.Logger) error { - ctx, err := SetServerContext(cmd.Context(), viper, logger) - if err != nil { - return err - } +func SetCmdServerContext(cmd *cobra.Command, viper *viper.Viper, logger log.Logger) { + ctx := SetServerContext(cmd.Context(), viper, logger) cmd.SetContext(ctx) - return nil } // GetViperFromContext returns the viper instance from the context. diff --git a/simapp/app_di.go b/simapp/app_di.go index b26220892e0d..0704cbdc38ca 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -93,8 +93,10 @@ func init() { // AppConfig returns the default app config. func AppConfig() depinject.Config { return depinject.Configs( - appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) - depinject.Provide(ProvideExampleMintFn), // optional: override the mint module's mint function with epoched minting + appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) + depinject.Provide( + ProvideExampleMintFn, // optional: override the mint module's mint function with epoched minting + ), ) } diff --git a/simapp/v2/app_config.go b/simapp/v2/app_config.go index 2f1337aec398..5ade503e6403 100644 --- a/simapp/v2/app_config.go +++ b/simapp/v2/app_config.go @@ -106,8 +106,8 @@ var ( // pooltypes.ModuleName } - // application configuration (used by depinject) - appConfig = appconfig.Compose(&appv1alpha1.Config{ + // ModuleConfig is the application module configuration used by depinject + ModuleConfig = appconfig.Compose(&appv1alpha1.Config{ Modules: []*appv1alpha1.ModuleConfig{ { Name: runtime.ModuleName, diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index 593c8d0c275f..5fa97c224161 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -2,10 +2,8 @@ package simapp import ( _ "embed" + "fmt" - "github.com/spf13/viper" - - clienthelpers "cosmossdk.io/client/v2/helpers" "cosmossdk.io/core/registry" "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" @@ -28,9 +26,6 @@ import ( _ "github.com/cosmos/cosmos-sdk/x/genutil" ) -// DefaultNodeHome default home directories for the application daemon -var DefaultNodeHome string - // SimApp extends an ABCI application, but with most of its parameters exported. // They are exported for convenience in creating helper functions, as object // capabilities aren't needed for testing. @@ -48,24 +43,24 @@ type SimApp[T transaction.Tx] struct { StakingKeeper *stakingkeeper.Keeper } -func init() { - var err error - DefaultNodeHome, err = clienthelpers.GetNodeHomeDirectory(".simappv2") - if err != nil { - panic(err) - } -} - // AppConfig returns the default app config. func AppConfig() depinject.Config { return depinject.Configs( - appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) + ModuleConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) runtime.DefaultServiceBindings(), depinject.Provide( codec.ProvideInterfaceRegistry, codec.ProvideAddressCodec, codec.ProvideProtoCodec, codec.ProvideLegacyAmino, + ProvideRootStoreConfig, + // inject desired account types: + multisigdepinject.ProvideAccount, + basedepinject.ProvideAccount, + lockupdepinject.ProvideAllLockupAccounts, + + // provide base account options + basedepinject.ProvideSecp256K1PubKey, ), depinject.Invoke( std.RegisterInterfaces, @@ -74,89 +69,80 @@ func AppConfig() depinject.Config { ) } -// NewSimApp returns a reference to an initialized SimApp. func NewSimApp[T transaction.Tx]( - logger log.Logger, - viper *viper.Viper, -) *SimApp[T] { + config depinject.Config, + outputs ...any, +) (*SimApp[T], error) { var ( app = &SimApp[T]{} appBuilder *runtime.AppBuilder[T] - err error storeBuilder root.Builder + logger log.Logger // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( AppConfig(), + config, depinject.Supply( - logger, - viper, - - // ADVANCED CONFIGURATION - - // - // AUTH - // - // For providing a custom function required in auth to generate custom account types - // add it below. By default the auth module uses simulation.RandomGenesisAccounts. - // - // authtypes.RandomGenesisAccountsFn(simulation.RandomGenesisAccounts), - // - // For providing a custom a base account type add it below. - // By default the auth module uses authtypes.ProtoBaseAccount(). - // - // func() sdk.AccountI { return authtypes.ProtoBaseAccount() }, - // - // For providing a different address codec, add it below. - // By default the auth module uses a Bech32 address codec, - // with the prefix defined in the auth module configuration. - // - // func() address.Codec { return <- custom address codec type -> } - - // - // STAKING - // - // For provinding a different validator and consensus address codec, add it below. - // By default the staking module uses the bech32 prefix provided in the auth config, - // and appends "valoper" and "valcons" for validator and consensus addresses respectively. - // When providing a custom address codec in auth, custom address codecs must be provided here as well. - // - // func() runtime.ValidatorAddressCodec { return <- custom validator address codec type -> } - // func() runtime.ConsensusAddressCodec { return <- custom consensus address codec type -> } - - // - // MINT - // - - // For providing a custom inflation function for x/mint add here your - // custom function that implements the minttypes.InflationCalculationFn - // interface. + // ADVANCED CONFIGURATION + + // + // AUTH + // + // For providing a custom function required in auth to generate custom account types + // add it below. By default the auth module uses simulation.RandomGenesisAccounts. + // + // authtypes.RandomGenesisAccountsFn(simulation.RandomGenesisAccounts), + // + // For providing a custom a base account type add it below. + // By default the auth module uses authtypes.ProtoBaseAccount(). + // + // func() sdk.AccountI { return authtypes.ProtoBaseAccount() }, + // + // For providing a different address codec, add it below. + // By default the auth module uses a Bech32 address codec, + // with the prefix defined in the auth module configuration. + // + // func() address.Codec { return <- custom address codec type -> } + + // + // STAKING + // + // For provinding a different validator and consensus address codec, add it below. + // By default the staking module uses the bech32 prefix provided in the auth config, + // and appends "valoper" and "valcons" for validator and consensus addresses respectively. + // When providing a custom address codec in auth, custom address codecs must be provided here as well. + // + // func() runtime.ValidatorAddressCodec { return <- custom validator address codec type -> } + // func() runtime.ConsensusAddressCodec { return <- custom consensus address codec type -> } + + // + // MINT + // + + // For providing a custom inflation function for x/mint add here your + // custom function that implements the minttypes.InflationCalculationFn + // interface. ), depinject.Provide( - // inject desired account types: - multisigdepinject.ProvideAccount, - basedepinject.ProvideAccount, - lockupdepinject.ProvideAllLockupAccounts, - - // provide base account options - basedepinject.ProvideSecp256K1PubKey, - // if you want to provide a custom public key you - // can do it from here. - // Example: - // basedepinject.ProvideCustomPubkey[Ed25519PublicKey]() - // - // You can also provide a custom public key with a custom validation function: - // - // basedepinject.ProvideCustomPubKeyAndValidationFunc(func(pub Ed25519PublicKey) error { - // if len(pub.Key) != 64 { - // return fmt.Errorf("invalid pub key size") - // } - // }) + // if you want to provide a custom public key you + // can do it from here. + // Example: + // basedepinject.ProvideCustomPubkey[Ed25519PublicKey]() + // + // You can also provide a custom public key with a custom validation function: + // + // basedepinject.ProvideCustomPubKeyAndValidationFunc(func(pub Ed25519PublicKey) error { + // if len(pub.Key) != 64 { + // return fmt.Errorf("invalid pub key size") + // } + // }) ), ) ) - if err := depinject.Inject(appConfig, + outputs = append(outputs, + &logger, &storeBuilder, &appBuilder, &app.appCodec, @@ -164,25 +150,21 @@ func NewSimApp[T transaction.Tx]( &app.txConfig, &app.interfaceRegistry, &app.UpgradeKeeper, - &app.StakingKeeper, - ); err != nil { - panic(err) - } + &app.StakingKeeper) - // store/v2 follows a slightly more eager config life cycle than server components - storeConfig, err := serverstore.UnmarshalConfig(viper.AllSettings()) - if err != nil { - panic(err) + if err := depinject.Inject(appConfig, outputs...); err != nil { + return nil, err } - app.store, err = storeBuilder.Build(logger, storeConfig) + var err error + app.App, err = appBuilder.Build() if err != nil { - panic(err) + return nil, err } - app.App, err = appBuilder.Build() - if err != nil { - panic(err) + app.store = storeBuilder.Get() + if app.store == nil { + return nil, fmt.Errorf("store builder did not return a db") } /**** Module Options ****/ @@ -190,14 +172,10 @@ func NewSimApp[T transaction.Tx]( // RegisterUpgradeHandlers is used for registering any on-chain upgrades. app.RegisterUpgradeHandlers() - // TODO (here or in runtime/v2) - // wire simulation manager - // wire unordered tx manager - - if err := app.LoadLatest(); err != nil { - panic(err) + if err = app.LoadLatest(); err != nil { + return nil, err } - return app + return app, nil } // AppCodec returns SimApp's app codec. @@ -231,3 +209,7 @@ func (app *SimApp[T]) Close() error { return app.App.Close() } + +func ProvideRootStoreConfig(config runtime.GlobalConfig) (*root.Config, error) { + return serverstore.UnmarshalConfig(config) +} diff --git a/simapp/v2/app_test.go b/simapp/v2/app_test.go index 61ace5e4ef29..c3c51489a7e5 100644 --- a/simapp/v2/app_test.go +++ b/simapp/v2/app_test.go @@ -16,8 +16,10 @@ import ( "cosmossdk.io/core/server" "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" + "cosmossdk.io/depinject" "cosmossdk.io/log" sdkmath "cosmossdk.io/math" + "cosmossdk.io/runtime/v2" serverv2 "cosmossdk.io/server/v2" serverv2store "cosmossdk.io/server/v2/store" "cosmossdk.io/store/v2/db" @@ -39,7 +41,11 @@ func NewTestApp(t *testing.T) (*SimApp[transaction.Tx], context.Context) { vp.Set(serverv2store.FlagAppDBBackend, string(db.DBTypeGoLevelDB)) vp.Set(serverv2.FlagHome, t.TempDir()) - app := NewSimApp[transaction.Tx](logger, vp) + app, err := NewSimApp[transaction.Tx](depinject.Configs( + depinject.Supply(logger, runtime.GlobalConfig(vp.AllSettings()))), + ) + require.NoError(t, err) + genesis := app.ModuleManager().DefaultGenesis() privVal := mock.NewPV() diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index cf940ac67e96..c001db4dff92 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -1,15 +1,12 @@ package cmd import ( - "context" "errors" - "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" "cosmossdk.io/client/v2/offchain" - corectx "cosmossdk.io/core/context" + coreserver "cosmossdk.io/core/server" "cosmossdk.io/core/transaction" "cosmossdk.io/log" runtimev2 "cosmossdk.io/runtime/v2" @@ -17,82 +14,140 @@ import ( "cosmossdk.io/server/v2/api/grpc" "cosmossdk.io/server/v2/api/rest" "cosmossdk.io/server/v2/api/telemetry" + "cosmossdk.io/server/v2/cometbft" serverstore "cosmossdk.io/server/v2/store" "cosmossdk.io/simapp/v2" confixcmd "cosmossdk.io/tools/confix/cmd" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/rpc" - "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/genutil" genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - genutilv2 "github.com/cosmos/cosmos-sdk/x/genutil/v2" v2 "github.com/cosmos/cosmos-sdk/x/genutil/v2/cli" ) -func newApp[T transaction.Tx](logger log.Logger, viper *viper.Viper) serverv2.AppI[T] { - viper.SetDefault(serverv2.FlagHome, simapp.DefaultNodeHome) - return serverv2.AppI[T](simapp.NewSimApp[T](logger, viper)) +// CommandDependencies is a struct that contains all the dependencies needed to initialize the root command. +// an alternative design could fetch these even later from the command context +type CommandDependencies[T transaction.Tx] struct { + GlobalConfig coreserver.ConfigMap + TxConfig client.TxConfig + ModuleManager *runtimev2.MM[T] + SimApp *simapp.SimApp[T] + Consensus serverv2.ServerComponent[T] } -func initRootCmd[T transaction.Tx]( +func InitRootCmd[T transaction.Tx]( rootCmd *cobra.Command, - moduleManager *runtimev2.MM[T], - consensusComponent serverv2.ServerComponent[T], -) { + logger log.Logger, + deps CommandDependencies[T], +) (serverv2.ConfigWriter, error) { cfg := sdk.GetConfig() cfg.Seal() rootCmd.AddCommand( - genutilcli.InitCmd(moduleManager), + genutilcli.InitCmd(deps.ModuleManager), + genesisCommand(deps.ModuleManager, deps.SimApp), + NewTestnetCmd(deps.ModuleManager), debug.Cmd(), confixcmd.ConfigCommand(), - NewTestnetCmd(moduleManager), - ) - - // add keybase, auxiliary RPC, query, genesis, and tx child commands - rootCmd.AddCommand( - genesisCommand(moduleManager), + // add keybase, auxiliary RPC, query, genesis, and tx child commands queryCommand(), txCommand(), keys.Commands(), offchain.OffChain(), ) + // build CLI skeleton for initial config parsing or a client application invocation + if deps.SimApp == nil { + if deps.Consensus == nil { + deps.Consensus = cometbft.NewWithConfigOptions[T](initCometConfig()) + } + return serverv2.AddCommands[T]( + rootCmd, + logger, + deps.GlobalConfig, + initServerConfig(), + deps.Consensus, + &grpc.Server[T]{}, + &serverstore.Server[T]{}, + &telemetry.Server[T]{}, + &rest.Server[T]{}, + ) + } + + // build full app! + simApp := deps.SimApp + grpcServer, err := grpc.New[T](logger, simApp.InterfaceRegistry(), simApp.QueryHandlers(), simApp, deps.GlobalConfig) + if err != nil { + return nil, err + } + // store component (not a server) + storeComponent, err := serverstore.New[T](simApp.Store(), deps.GlobalConfig) + if err != nil { + return nil, err + } + restServer, err := rest.New[T](simApp.App.AppManager, logger, deps.GlobalConfig) + if err != nil { + return nil, err + } + + // consensus component + if deps.Consensus == nil { + deps.Consensus, err = cometbft.New( + logger, + simApp.Name(), + simApp.Store(), + simApp.App.AppManager, + simApp.App.QueryHandlers(), + simApp.App.SchemaDecoderResolver(), + &genericTxDecoder[T]{deps.TxConfig}, + deps.GlobalConfig, + initCometOptions[T](), + ) + if err != nil { + return nil, err + } + } + telemetryServer, err := telemetry.New[T](deps.GlobalConfig, logger) + if err != nil { + return nil, err + } + // wire server commands - if err := serverv2.AddCommands( + return serverv2.AddCommands[T]( rootCmd, - newApp, + logger, + deps.GlobalConfig, initServerConfig(), - consensusComponent, - grpc.New[T](), - serverstore.New[T](), - telemetry.New[T](), - rest.New[T](), - ); err != nil { - panic(err) - } + deps.Consensus, + grpcServer, + storeComponent, + telemetryServer, + restServer, + ) } // genesisCommand builds genesis-related `simd genesis` command. func genesisCommand[T transaction.Tx]( moduleManager *runtimev2.MM[T], - cmds ...*cobra.Command, + app *simapp.SimApp[T], ) *cobra.Command { + var genTxValidator func([]transaction.Msg) error + if moduleManager != nil { + genTxValidator = moduleManager.Modules()[genutiltypes.ModuleName].(genutil.AppModule).GenTxValidator() + } cmd := v2.Commands( - moduleManager.Modules()[genutiltypes.ModuleName].(genutil.AppModule), + genTxValidator, moduleManager, - appExport[T], + app, ) - for _, subCmd := range cmds { - cmd.AddCommand(subCmd) - } return cmd } @@ -139,41 +194,31 @@ func txCommand() *cobra.Command { return cmd } -// appExport creates a new simapp (optionally at a given height) and exports state. -func appExport[T transaction.Tx]( - ctx context.Context, - height int64, - jailAllowedAddrs []string, -) (genutilv2.ExportedApp, error) { - value := ctx.Value(corectx.ViperContextKey) - viper, ok := value.(*viper.Viper) - if !ok { - return genutilv2.ExportedApp{}, - fmt.Errorf("incorrect viper type %T: expected *viper.Viper in context", value) - } - value = ctx.Value(corectx.LoggerContextKey) - logger, ok := value.(log.Logger) - if !ok { - return genutilv2.ExportedApp{}, - fmt.Errorf("incorrect logger type %T: expected log.Logger in context", value) - } +func RootCommandPersistentPreRun(clientCtx client.Context) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) - // overwrite the FlagInvCheckPeriod - viper.Set(server.FlagInvCheckPeriod, 1) - viper.SetDefault(serverv2.FlagHome, simapp.DefaultNodeHome) + clientCtx = clientCtx.WithCmdContext(cmd.Context()) + clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } - var simApp *simapp.SimApp[T] - if height != -1 { - simApp = simapp.NewSimApp[T](logger, viper) + customClientTemplate, customClientConfig := initClientConfig() + clientCtx, err = config.CreateClientConfig( + clientCtx, customClientTemplate, customClientConfig) + if err != nil { + return err + } - if err := simApp.LoadHeight(uint64(height)); err != nil { - return genutilv2.ExportedApp{}, err + if err = client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { + return err } - } else { - simApp = simapp.NewSimApp[T](logger, viper) - } - return simApp.ExportAppStateAndValidators(jailAllowedAddrs) + return nil + } } var _ transaction.Codec[transaction.Tx] = &genericTxDecoder[transaction.Tx]{} diff --git a/simapp/v2/simdv2/cmd/depinject.go b/simapp/v2/simdv2/cmd/depinject.go new file mode 100644 index 000000000000..f59140f29399 --- /dev/null +++ b/simapp/v2/simdv2/cmd/depinject.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "os" + + "cosmossdk.io/core/address" + "cosmossdk.io/core/registry" + "cosmossdk.io/runtime/v2" + serverv2 "cosmossdk.io/server/v2" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtxconfig "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// ProvideClientContext is a depinject Provider function which assembles and returns a client.Context. +func ProvideClientContext( + configMap runtime.GlobalConfig, + appCodec codec.Codec, + interfaceRegistry codectypes.InterfaceRegistry, + txConfigOpts tx.ConfigOptions, + legacyAmino registry.AminoRegistrar, + addressCodec address.Codec, + validatorAddressCodec address.ValidatorAddressCodec, + consensusAddressCodec address.ConsensusAddressCodec, +) client.Context { + var err error + amino, ok := legacyAmino.(*codec.LegacyAmino) + if !ok { + panic("registry.AminoRegistrar must be an *codec.LegacyAmino instance for legacy ClientContext") + } + homeDir, ok := configMap[serverv2.FlagHome].(string) + if !ok { + panic("server.ConfigMap must contain a string value for serverv2.FlagHome") + } + + clientCtx := client.Context{}. + WithCodec(appCodec). + WithInterfaceRegistry(interfaceRegistry). + WithLegacyAmino(amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithAddressCodec(addressCodec). + WithValidatorAddressCodec(validatorAddressCodec). + WithConsensusAddressCodec(consensusAddressCodec). + WithHomeDir(homeDir). + WithViper("") // uses by default the binary name as prefix + + // Read the config to overwrite the default values with the values from the config file + customClientTemplate, customClientConfig := initClientConfig() + clientCtx, err = config.CreateClientConfig(clientCtx, customClientTemplate, customClientConfig) + if err != nil { + panic(err) + } + + // textual is enabled by default, we need to re-create the tx config grpc instead of bank keeper. + txConfigOpts.TextualCoinMetadataQueryFn = authtxconfig.NewGRPCCoinMetadataQueryFn(clientCtx) + txConfig, err := tx.NewTxConfigWithOptions(clientCtx.Codec, txConfigOpts) + if err != nil { + panic(err) + } + clientCtx = clientCtx.WithTxConfig(txConfig) + + return clientCtx +} diff --git a/simapp/v2/simdv2/cmd/root_di.go b/simapp/v2/simdv2/cmd/root_di.go index da1bcf77061a..0bbae702becb 100644 --- a/simapp/v2/simdv2/cmd/root_di.go +++ b/simapp/v2/simdv2/cmd/root_di.go @@ -1,161 +1,98 @@ package cmd import ( - "os" - "github.com/spf13/cobra" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" "cosmossdk.io/client/v2/autocli" - "cosmossdk.io/core/address" - "cosmossdk.io/core/registry" "cosmossdk.io/core/transaction" "cosmossdk.io/depinject" "cosmossdk.io/log" "cosmossdk.io/runtime/v2" serverv2 "cosmossdk.io/server/v2" - "cosmossdk.io/server/v2/cometbft" "cosmossdk.io/simapp/v2" - basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" - lockupdepinject "cosmossdk.io/x/accounts/defaults/lockup/depinject" - multisigdepinject "cosmossdk.io/x/accounts/defaults/multisig/depinject" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/config" nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/x/auth/tx" - authtxconfig "github.com/cosmos/cosmos-sdk/x/auth/tx/config" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// NewCometBFTRootCmd creates a new root command for simd, -// using the CometBFT server component for consensus. -// It is called once in the main function. -func NewCometBFTRootCmd[T transaction.Tx]() *cobra.Command { - return NewRootCmdWithConsensusComponent(func(cc client.Context) serverv2.ServerComponent[T] { - return cometbft.New[T]( - &genericTxDecoder[T]{cc.TxConfig}, - initCometOptions[T](), - initCometConfig(), - ) - }) -} - -// NewRootCmdWithConsensusComponent returns a new root command, -// using the provided callback to instantiate the server component for the consensus layer. -// Callers who want to use CometBFT should call [NewCometBFTRootCmd] directly. -func NewRootCmdWithConsensusComponent[T transaction.Tx]( - makeConsensusComponent func(cc client.Context) serverv2.ServerComponent[T], -) *cobra.Command { - var ( - autoCliOpts autocli.AppOptions - moduleManager *runtime.MM[T] - clientCtx client.Context +func NewRootCmd[T transaction.Tx]( + args ...string, +) (*cobra.Command, error) { + rootCommand := &cobra.Command{ + Use: "simdv2", + SilenceErrors: true, + } + configWriter, err := InitRootCmd(rootCommand, log.NewNopLogger(), CommandDependencies[T]{}) + if err != nil { + return nil, err + } + factory, err := serverv2.NewCommandFactory( + serverv2.WithConfigWriter(configWriter), + serverv2.WithStdDefaultHomeDir(".simappv2"), + serverv2.WithLoggerFactory(serverv2.NewLogger), ) - - if err := depinject.Inject( - depinject.Configs( - simapp.AppConfig(), - depinject.Provide( - ProvideClientContext, - // inject desired account types: - multisigdepinject.ProvideAccount, - basedepinject.ProvideAccount, - lockupdepinject.ProvideAllLockupAccounts, - - // provide base account options - basedepinject.ProvideSecp256K1PubKey), - depinject.Supply(log.NewNopLogger()), - ), - &autoCliOpts, - &moduleManager, - &clientCtx, - ); err != nil { - panic(err) + if err != nil { + return nil, err } - rootCmd := &cobra.Command{ - Use: "simd", - Short: "simulation app", - SilenceErrors: true, - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - clientCtx = clientCtx.WithCmdContext(cmd.Context()) - clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags()) - if err != nil { - return err - } - - customClientTemplate, customClientConfig := initClientConfig() - clientCtx, err = config.CreateClientConfig(clientCtx, customClientTemplate, customClientConfig) - if err != nil { - return err - } - - if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { - return err - } - - return nil - }, + subCommand, configMap, logger, err := factory.ParseCommand(rootCommand, args) + if err != nil { + return nil, err } - consensusComponent := makeConsensusComponent(clientCtx) - initRootCmd(rootCmd, moduleManager, consensusComponent) - - nodeCmds := nodeservice.NewNodeCommands() - autoCliOpts.ModuleOptions = make(map[string]*autocliv1.ModuleOptions) - autoCliOpts.ModuleOptions[nodeCmds.Name()] = nodeCmds.AutoCLIOptions() - if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { - panic(err) + var ( + autoCliOpts autocli.AppOptions + moduleManager *runtime.MM[T] + clientCtx client.Context + simApp *simapp.SimApp[T] + depinjectConfig = depinject.Configs( + depinject.Supply(logger, runtime.GlobalConfig(configMap)), + depinject.Provide(ProvideClientContext), + ) + ) + if serverv2.IsAppRequired(subCommand) { + // server construction + simApp, err = simapp.NewSimApp[T](depinjectConfig, &autoCliOpts, &moduleManager, &clientCtx) + if err != nil { + return nil, err + } + } else { + // client construction + if err = depinject.Inject( + depinject.Configs( + simapp.AppConfig(), + depinjectConfig, + ), + &autoCliOpts, &moduleManager, &clientCtx, + ); err != nil { + return nil, err + } } - return rootCmd -} - -func ProvideClientContext( - appCodec codec.Codec, - interfaceRegistry codectypes.InterfaceRegistry, - txConfigOpts tx.ConfigOptions, - legacyAmino registry.AminoRegistrar, - addressCodec address.Codec, - validatorAddressCodec address.ValidatorAddressCodec, - consensusAddressCodec address.ConsensusAddressCodec, -) client.Context { - var err error - - amino, ok := legacyAmino.(*codec.LegacyAmino) - if !ok { - panic("registry.AminoRegistrar must be an *codec.LegacyAmino instance for legacy ClientContext") + commandDeps := CommandDependencies[T]{ + GlobalConfig: configMap, + TxConfig: clientCtx.TxConfig, + ModuleManager: moduleManager, + SimApp: simApp, } - - clientCtx := client.Context{}. - WithCodec(appCodec). - WithInterfaceRegistry(interfaceRegistry). - WithLegacyAmino(amino). - WithInput(os.Stdin). - WithAccountRetriever(types.AccountRetriever{}). - WithAddressCodec(addressCodec). - WithValidatorAddressCodec(validatorAddressCodec). - WithConsensusAddressCodec(consensusAddressCodec). - WithHomeDir(simapp.DefaultNodeHome). - WithViper("") // uses by default the binary name as prefix - - // Read the config to overwrite the default values with the values from the config file - customClientTemplate, customClientConfig := initClientConfig() - clientCtx, err = config.CreateClientConfig(clientCtx, customClientTemplate, customClientConfig) - if err != nil { - panic(err) + rootCommand = &cobra.Command{ + Use: "simdv2", + Short: "simulation app", + SilenceErrors: true, + PersistentPreRunE: RootCommandPersistentPreRun(clientCtx), } - - // textual is enabled by default, we need to re-create the tx config grpc instead of bank keeper. - txConfigOpts.TextualCoinMetadataQueryFn = authtxconfig.NewGRPCCoinMetadataQueryFn(clientCtx) - txConfig, err := tx.NewTxConfigWithOptions(clientCtx.Codec, txConfigOpts) + factory.EnhanceRootCommand(rootCommand) + _, err = InitRootCmd(rootCommand, logger, commandDeps) if err != nil { - panic(err) + return nil, err + } + nodeCmds := nodeservice.NewNodeCommands() + autoCliOpts.ModuleOptions = make(map[string]*autocliv1.ModuleOptions) + autoCliOpts.ModuleOptions[nodeCmds.Name()] = nodeCmds.AutoCLIOptions() + if err := autoCliOpts.EnhanceRootCommand(rootCommand); err != nil { + return nil, err } - clientCtx = clientCtx.WithTxConfig(txConfig) - return clientCtx + return rootCommand, nil } diff --git a/simapp/v2/simdv2/cmd/root_test.go b/simapp/v2/simdv2/cmd/root_test.go index 87678bc4b909..7f7d6a07d95a 100644 --- a/simapp/v2/simdv2/cmd/root_test.go +++ b/simapp/v2/simdv2/cmd/root_test.go @@ -7,8 +7,6 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/core/transaction" - svrcmd "cosmossdk.io/server/v2" - "cosmossdk.io/simapp/v2" "cosmossdk.io/simapp/v2/simdv2/cmd" "github.com/cosmos/cosmos-sdk/client/flags" @@ -16,27 +14,29 @@ import ( ) func TestInitCmd(t *testing.T) { - rootCmd := cmd.NewCometBFTRootCmd[transaction.Tx]() - rootCmd.SetArgs([]string{ + args := []string{ "init", // Test the init cmd "simapp-test", // Moniker fmt.Sprintf("--%s=%s", cli.FlagOverwrite, "true"), // Overwrite genesis.json, in case it already exists - }) - - require.NoError(t, svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome)) + } + rootCmd, err := cmd.NewRootCmd[transaction.Tx](args...) + require.NoError(t, err) + rootCmd.SetArgs(args) + require.NoError(t, rootCmd.Execute()) } func TestHomeFlagRegistration(t *testing.T) { homeDir := "/tmp/foo" - - rootCmd := cmd.NewCometBFTRootCmd[transaction.Tx]() - rootCmd.SetArgs([]string{ + args := []string{ "query", fmt.Sprintf("--%s", flags.FlagHome), homeDir, - }) + } - require.NoError(t, svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome)) + rootCmd, err := cmd.NewRootCmd[transaction.Tx](args...) + require.NoError(t, err) + rootCmd.SetArgs(args) + require.NoError(t, rootCmd.Execute()) result, err := rootCmd.Flags().GetString(flags.FlagHome) require.NoError(t, err) diff --git a/simapp/v2/simdv2/cmd/testnet.go b/simapp/v2/simdv2/cmd/testnet.go index 7b71e98b588d..46a19c039c86 100644 --- a/simapp/v2/simdv2/cmd/testnet.go +++ b/simapp/v2/simdv2/cmd/testnet.go @@ -335,15 +335,10 @@ func initTestnetFiles[T transaction.Tx]( serverCfg := serverv2.DefaultServerConfig() serverCfg.MinGasPrices = args.minGasPrices - // Write server config - cometServer := cometbft.New[T]( - &genericTxDecoder[T]{clientCtx.TxConfig}, - cometbft.ServerOptions[T]{}, - cometbft.OverwriteDefaultConfigTomlConfig(nodeConfig), - ) - storeServer := store.New[T]() - grpcServer := grpc.New[T](grpc.OverwriteDefaultConfig(grpcConfig)) - server := serverv2.NewServer[T](serverCfg, cometServer, grpcServer, storeServer) + cometServer := cometbft.NewWithConfigOptions[T](cometbft.OverwriteDefaultConfigTomlConfig(nodeConfig)) + storeServer := &store.Server[T]{} + grpcServer := grpc.NewWithConfigOptions[T](grpc.OverwriteDefaultConfig(grpcConfig)) + server := serverv2.NewServer[T](serverCfg, cometServer, storeServer, grpcServer) err = server.WriteConfig(filepath.Join(nodeDir, "config")) if err != nil { return err @@ -363,7 +358,6 @@ func initTestnetFiles[T transaction.Tx]( return err } - // Update viper root since root dir become rootdir/node/simd serverv2.GetViperFromCmd(cmd).Set(flags.FlagHome, nodeConfig.RootDir) cmd.PrintErrf("Successfully initialized %d node directories\n", args.numValidators) diff --git a/simapp/v2/simdv2/cmd/testnet_test.go b/simapp/v2/simdv2/cmd/testnet_test.go index 3c7769d912e8..acf57dd37b80 100644 --- a/simapp/v2/simdv2/cmd/testnet_test.go +++ b/simapp/v2/simdv2/cmd/testnet_test.go @@ -7,8 +7,6 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/core/transaction" - svrcmd "cosmossdk.io/server/v2" - "cosmossdk.io/simapp/v2" "cosmossdk.io/simapp/v2/simdv2/cmd" "github.com/cosmos/cosmos-sdk/client/flags" @@ -16,12 +14,13 @@ import ( ) func TestInitTestFilesCmd(t *testing.T) { - rootCmd := cmd.NewCometBFTRootCmd[transaction.Tx]() - rootCmd.SetArgs([]string{ + args := []string{ "testnet", // Test the testnet init-files command "init-files", fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest), // Set keyring-backend to test - }) - - require.NoError(t, svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome)) + } + rootCmd, err := cmd.NewRootCmd[transaction.Tx](args...) + require.NoError(t, err) + rootCmd.SetArgs(args) + require.NoError(t, rootCmd.Execute()) } diff --git a/simapp/v2/simdv2/main.go b/simapp/v2/simdv2/main.go index 84248ceed0a6..d01a45030cb9 100644 --- a/simapp/v2/simdv2/main.go +++ b/simapp/v2/simdv2/main.go @@ -1,20 +1,29 @@ package main import ( + "errors" "fmt" "os" - clientv2helpers "cosmossdk.io/client/v2/helpers" "cosmossdk.io/core/transaction" - serverv2 "cosmossdk.io/server/v2" - "cosmossdk.io/simapp/v2" "cosmossdk.io/simapp/v2/simdv2/cmd" ) func main() { - rootCmd := cmd.NewCometBFTRootCmd[transaction.Tx]() - if err := serverv2.Execute(rootCmd, clientv2helpers.EnvPrefix, simapp.DefaultNodeHome); err != nil { - fmt.Fprintln(rootCmd.OutOrStderr(), err) + // reproduce default cobra behavior so that eager parsing of flags is possible. + // see: https://github.com/spf13/cobra/blob/e94f6d0dd9a5e5738dca6bce03c4b1207ffbc0ec/command.go#L1082 + args := os.Args[1:] + rootCmd, err := cmd.NewRootCmd[transaction.Tx](args...) + if err != nil { + if _, pErr := fmt.Fprintln(os.Stderr, err); pErr != nil { + panic(errors.Join(err, pErr)) + } + os.Exit(1) + } + if err = rootCmd.Execute(); err != nil { + if _, pErr := fmt.Fprintln(rootCmd.OutOrStderr(), err); pErr != nil { + panic(errors.Join(err, pErr)) + } os.Exit(1) } } diff --git a/x/genutil/client/cli/collect.go b/x/genutil/client/cli/collect.go index ff3da30d5bdb..10d9db0cdc38 100644 --- a/x/genutil/client/cli/collect.go +++ b/x/genutil/client/cli/collect.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" + "cosmossdk.io/core/transaction" "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/client" @@ -16,7 +17,7 @@ import ( const flagGenTxDir = "gentx-dir" // CollectGenTxsCmd - return the cobra command to collect genesis transactions -func CollectGenTxsCmd(validator types.MessageValidator) *cobra.Command { +func CollectGenTxsCmd(validator func([]transaction.Msg) error) *cobra.Command { cmd := &cobra.Command{ Use: "collect-gentxs", Short: "Collect genesis txs and output a genesis.json file", @@ -50,7 +51,9 @@ func CollectGenTxsCmd(validator types.MessageValidator) *cobra.Command { toPrint := newPrintInfo(config.Moniker, appGenesis.ChainID, nodeID, genTxsDir, json.RawMessage("")) initCfg := types.NewInitConfig(appGenesis.ChainID, genTxsDir, nodeID, valPubKey) - appMessage, err := genutil.GenAppStateFromConfig(cdc, clientCtx.TxConfig, config, initCfg, appGenesis, validator, clientCtx.ValidatorAddressCodec, clientCtx.AddressCodec) + appMessage, err := genutil.GenAppStateFromConfig( + cdc, clientCtx.TxConfig, config, initCfg, appGenesis, + validator, clientCtx.ValidatorAddressCodec, clientCtx.AddressCodec) if err != nil { return errors.Wrap(err, "failed to get genesis app state from config") } diff --git a/x/genutil/v2/cli/commands.go b/x/genutil/v2/cli/commands.go index 7b871ec074a4..6812345d23ba 100644 --- a/x/genutil/v2/cli/commands.go +++ b/x/genutil/v2/cli/commands.go @@ -5,10 +5,10 @@ import ( "github.com/spf13/cobra" + "cosmossdk.io/core/transaction" banktypes "cosmossdk.io/x/bank/types" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" v2 "github.com/cosmos/cosmos-sdk/x/genutil/v2" @@ -19,17 +19,26 @@ type genesisMM interface { ValidateGenesis(genesisData map[string]json.RawMessage) error } +type ExportableApp interface { + ExportAppStateAndValidators([]string) (v2.ExportedApp, error) + LoadHeight(uint64) error +} + // Commands adds core sdk's sub-commands into genesis command. -func Commands(genutilModule genutil.AppModule, genMM genesisMM, appExport v2.AppExporter) *cobra.Command { - return CommandsWithCustomMigrationMap(genutilModule, genMM, appExport, cli.MigrationMap) +func Commands( + genTxValidator func([]transaction.Msg) error, + genMM genesisMM, + exportable ExportableApp, +) *cobra.Command { + return CommandsWithCustomMigrationMap(genTxValidator, genMM, exportable, cli.MigrationMap) } // CommandsWithCustomMigrationMap adds core sdk's sub-commands into genesis command with custom migration map. // This custom migration map can be used by the application to add its own migration map. func CommandsWithCustomMigrationMap( - genutilModule genutil.AppModule, + genTxValidator func([]transaction.Msg) error, genMM genesisMM, - appExport v2.AppExporter, + exportable ExportableApp, migrationMap genutiltypes.MigrationMap, ) *cobra.Command { cmd := &cobra.Command{ @@ -42,10 +51,10 @@ func CommandsWithCustomMigrationMap( cmd.AddCommand( cli.GenTxCmd(genMM, banktypes.GenesisBalancesIterator{}), cli.MigrateGenesisCmd(migrationMap), - cli.CollectGenTxsCmd(genutilModule.GenTxValidator()), + cli.CollectGenTxsCmd(genTxValidator), cli.ValidateGenesisCmd(genMM), cli.AddGenesisAccountCmd(), - ExportCmd(appExport), + ExportCmd(exportable), ) return cmd diff --git a/x/genutil/v2/cli/export.go b/x/genutil/v2/cli/export.go index 9b0d992bad0a..c53236d49329 100644 --- a/x/genutil/v2/cli/export.go +++ b/x/genutil/v2/cli/export.go @@ -13,7 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/version" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - v2 "github.com/cosmos/cosmos-sdk/x/genutil/v2" ) const ( @@ -22,7 +21,7 @@ const ( ) // ExportCmd dumps app state to JSON. -func ExportCmd(appExporter v2.AppExporter) *cobra.Command { +func ExportCmd(app ExportableApp) *cobra.Command { cmd := &cobra.Command{ Use: "export", Short: "Export state to JSON", @@ -34,7 +33,7 @@ func ExportCmd(appExporter v2.AppExporter) *cobra.Command { return err } - if appExporter == nil { + if app == nil { if _, err := fmt.Fprintln(cmd.ErrOrStderr(), "WARNING: App exporter not defined. Returning genesis file."); err != nil { return err } @@ -59,8 +58,12 @@ func ExportCmd(appExporter v2.AppExporter) *cobra.Command { height, _ := cmd.Flags().GetInt64(flagHeight) jailAllowedAddrs, _ := cmd.Flags().GetStringSlice(flagJailAllowedAddrs) outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument) - - exported, err := appExporter(cmd.Context(), height, jailAllowedAddrs) + if height != -1 { + if err := app.LoadHeight(uint64(height)); err != nil { + return err + } + } + exported, err := app.ExportAppStateAndValidators(jailAllowedAddrs) if err != nil { return fmt.Errorf("error exporting state: %w", err) } diff --git a/x/genutil/v2/types.go b/x/genutil/v2/types.go index fbf288365a57..1509f09f59b8 100644 --- a/x/genutil/v2/types.go +++ b/x/genutil/v2/types.go @@ -1,20 +1,11 @@ package v2 import ( - "context" "encoding/json" sdk "github.com/cosmos/cosmos-sdk/types" ) -// AppExporter is a function that dumps all app state to -// JSON-serializable structure and returns the current validator set. -type AppExporter func( - ctx context.Context, - height int64, - jailAllowedAddrs []string, -) (ExportedApp, error) - // ExportedApp represents an exported app state, along with // validators, consensus params and latest app height. type ExportedApp struct { diff --git a/x/upgrade/depinject.go b/x/upgrade/depinject.go index d0ad08a1c34e..01b93356fdf0 100644 --- a/x/upgrade/depinject.go +++ b/x/upgrade/depinject.go @@ -26,22 +26,31 @@ func (am AppModule) IsOnePerModuleType() {} func init() { appconfig.RegisterModule(&modulev1.Module{}, - appconfig.Provide(ProvideModule), + appconfig.Provide(ProvideModule, ProvideConfig), appconfig.Invoke(PopulateVersionMap), ) } +func ProvideConfig(key depinject.OwnModuleKey) coreserver.ModuleConfigMap { + return coreserver.ModuleConfigMap{ + Module: depinject.ModuleKey(key).Name(), + Config: coreserver.ConfigMap{ + server.FlagUnsafeSkipUpgrades: []int{}, + flags.FlagHome: "", + }, + } +} + type ModuleInputs struct { depinject.In Config *modulev1.Module + ConfigMap coreserver.ConfigMap Environment appmodule.Environment Cdc codec.Codec AddressCodec address.Codec AppVersionModifier coreserver.VersionModifier ConsensusKeeper types.ConsensusKeeper - - DynamicConfig coreserver.DynamicConfig `optional:"true"` } type ModuleOutputs struct { @@ -57,14 +66,15 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { skipUpgradeHeights = make(map[int64]bool) ) - if in.DynamicConfig != nil { - skipUpgrades := cast.ToIntSlice(in.DynamicConfig.Get(server.FlagUnsafeSkipUpgrades)) - for _, h := range skipUpgrades { - skipUpgradeHeights[int64(h)] = true - } - - homePath = in.DynamicConfig.GetString(flags.FlagHome) + skipUpgrades, ok := in.ConfigMap[server.FlagUnsafeSkipUpgrades] + if !ok || skipUpgrades == nil { + skipUpgrades = []int{} + } + heights := cast.ToIntSlice(skipUpgrades) // safe to use cast here as we've handled nil case + for _, h := range heights { + skipUpgradeHeights[int64(h)] = true } + homePath = cast.ToString(in.ConfigMap[flags.FlagHome]) // default to governance authority if not provided authority := authtypes.NewModuleAddress(types.GovModuleName) diff --git a/x/validate/depinject.go b/x/validate/depinject.go index 9f508717f04f..0c383f457abe 100644 --- a/x/validate/depinject.go +++ b/x/validate/depinject.go @@ -3,6 +3,8 @@ package validate import ( "fmt" + "github.com/spf13/cast" + modulev1 "cosmossdk.io/api/cosmos/validate/module/v1" appmodulev2 "cosmossdk.io/core/appmodule/v2" "cosmossdk.io/core/server" @@ -25,17 +27,28 @@ const flagMinGasPricesV2 = "server.minimum-gas-prices" func init() { appconfig.RegisterModule(&modulev1.Module{}, - appconfig.Provide(ProvideModule), + appconfig.Provide(ProvideModule, ProvideConfig), ) } +// ProvideConfig specifies the configuration key for the minimum gas prices. +// During dependency injection, a configuration map is provided with the key set. +func ProvideConfig(key depinject.OwnModuleKey) server.ModuleConfigMap { + return server.ModuleConfigMap{ + Module: depinject.ModuleKey(key).Name(), + Config: server.ConfigMap{ + flagMinGasPricesV2: "", + }, + } +} + type ModuleInputs struct { depinject.In - ModuleConfig *modulev1.Module - Environment appmodulev2.Environment - TxConfig client.TxConfig - DynamicConfig server.DynamicConfig `optional:"true"` + ModuleConfig *modulev1.Module + Environment appmodulev2.Environment + TxConfig client.TxConfig + ConfigMap server.ConfigMap AccountKeeper ante.AccountKeeper BankKeeper authtypes.BankKeeper @@ -69,17 +82,15 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { unorderedTxValidator *ante.UnorderedTxDecorator ) - if in.DynamicConfig != nil { - minGasPricesStr := in.DynamicConfig.GetString(flagMinGasPricesV2) - minGasPrices, err = sdk.ParseDecCoins(minGasPricesStr) - if err != nil { - panic(fmt.Sprintf("invalid minimum gas prices: %v", err)) - } - - feeTxValidator = ante.NewDeductFeeDecorator(in.AccountKeeper, in.BankKeeper, in.FeeGrantKeeper, in.TxFeeChecker) - feeTxValidator.SetMinGasPrices(minGasPrices) // set min gas price in deduct fee decorator + minGasPricesStr := cast.ToString(in.ConfigMap[flagMinGasPricesV2]) + minGasPrices, err = sdk.ParseDecCoins(minGasPricesStr) + if err != nil { + panic(fmt.Sprintf("invalid minimum gas prices: %v", err)) } + feeTxValidator = ante.NewDeductFeeDecorator(in.AccountKeeper, in.BankKeeper, in.FeeGrantKeeper, in.TxFeeChecker) + feeTxValidator.SetMinGasPrices(minGasPrices) // set min gas price in deduct fee decorator + if in.UnorderedTxManager != nil { unorderedTxValidator = ante.NewUnorderedTxDecorator(unorderedtx.DefaultMaxTimeoutDuration, in.UnorderedTxManager, in.Environment, ante.DefaultSha256Cost) }