refactor(server/v2): eager config loading (#22267)

This commit is contained in:
Matt Kocubinski
2024-10-29 06:09:16 +00:00
committed by GitHub
parent 7b9a89b568
commit 31f97e934e
39 changed files with 1051 additions and 682 deletions
+1 -1
View File
@@ -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
+46
View File
@@ -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()]
}
+2
View File
@@ -103,6 +103,8 @@ func init() {
ProvideTransientStoreService,
ProvideModuleManager,
ProvideCometService,
ProvideModuleConfigMaps,
ProvideModuleScopedConfigMap,
),
appconfig.Invoke(SetupAppBuilder),
)
+1 -1
View File
@@ -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
}
+6 -4
View File
@@ -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
}
+67
View File
@@ -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()]
}
+5 -1
View File
@@ -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]
+32 -21
View File
@@ -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
+23 -18
View File
@@ -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()))
+32 -19
View File
@@ -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,9 +26,37 @@ 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(),
}
srv.router.Handle("/", NewDefaultHandler(appManager))
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
return srv, nil
}
// 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,
}
}
@@ -34,23 +64,6 @@ 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())
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.router = http.NewServeMux()
s.router.Handle("/", NewDefaultHandler(appI))
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()))
+2 -2
View File
@@ -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)
+18 -22
View File
@@ -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()))
+59 -40
View File
@@ -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
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 := rs.GetStateStorage().(snapshots.StorageSnapshotter)
sc := rs.GetStateCommitment().(snapshots.CommitSnapshotter)
ss := store.GetStateStorage().(snapshots.StorageSnapshotter)
sc := store.GetStateCommitment().(snapshots.CommitSnapshotter)
snapshotStore, err := GetSnapshotStore(s.config.ConfigTomlConfig.RootDir)
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 {
+185
View File
@@ -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 '*:<level>,<key>:<level>')
// --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 '*:<level>,<key>:<level>')")
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
}
+54 -95
View File
@@ -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 '*:<level>,<key>:<level>')")
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")
return nil, 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)
}
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()
}
}
+28 -9
View File
@@ -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
}
+5 -25
View File
@@ -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 {
-8
View File
@@ -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())
+6 -8
View File
@@ -61,20 +61,18 @@ func TestServer(t *testing.T) {
logger := log.NewLogger(os.Stdout)
ctx, err := serverv2.SetServerContext(context.Background(), v, logger)
ctx := serverv2.SetServerContext(context.Background(), v, logger)
app := &mockApp[transaction.Tx]{}
grpcServer, err := grpc.New[transaction.Tx](logger, app.InterfaceRegistry(), app.QueryHandlers(), app, cfg)
require.NoError(t, err)
grpcServer := grpc.New[transaction.Tx]()
err = grpcServer.Init(&mockApp[transaction.Tx]{}, cfg, logger)
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,
+11 -10
View File
@@ -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(),
},
}
}
+5 -1
View File
@@ -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 <height> <format>",
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
+4 -8
View File
@@ -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.
+4 -2
View File
@@ -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
),
)
}
+2 -2
View File
@@ -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,
+81 -99
View File
@@ -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
// 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 -> }
//
// 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 -> }
//
// 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
//
//
// MINT
//
// For providing a custom inflation function for x/mint add here your
// custom function that implements the minttypes.InflationCalculationFn
// interface.
// 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)
}
app.store, err = storeBuilder.Build(logger, storeConfig)
if err != nil {
panic(err)
if err := depinject.Inject(appConfig, outputs...); err != nil {
return nil, err
}
var err error
app.App, err = appBuilder.Build()
if err != nil {
panic(err)
return nil, 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)
}
+7 -1
View File
@@ -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()
+115 -70
View File
@@ -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(),
)
// wire server commands
if err := serverv2.AddCommands(
rootCmd,
newApp,
initServerConfig(),
consensusComponent,
grpc.New[T](),
serverstore.New[T](),
telemetry.New[T](),
rest.New[T](),
); err != nil {
panic(err)
// 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
return serverv2.AddCommands[T](
rootCmd,
logger,
deps.GlobalConfig,
initServerConfig(),
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)
var simApp *simapp.SimApp[T]
if height != -1 {
simApp = simapp.NewSimApp[T](logger, viper)
if err := simApp.LoadHeight(uint64(height)); err != nil {
return genutilv2.ExportedApp{}, err
clientCtx = clientCtx.WithCmdContext(cmd.Context())
clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
} else {
simApp = simapp.NewSimApp[T](logger, viper)
}
return simApp.ExportAppStateAndValidators(jailAllowedAddrs)
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
}
}
var _ transaction.Codec[transaction.Tx] = &genericTxDecoder[transaction.Tx]{}
+69
View File
@@ -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
}
+70 -133
View File
@@ -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
)
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)
}
rootCmd := &cobra.Command{
Use: "simd",
Short: "simulation app",
func NewRootCmd[T transaction.Tx](
args ...string,
) (*cobra.Command, error) {
rootCommand := &cobra.Command{
Use: "simdv2",
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
},
}
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 != nil {
return nil, err
}
consensusComponent := makeConsensusComponent(clientCtx)
initRootCmd(rootCmd, moduleManager, consensusComponent)
subCommand, configMap, logger, err := factory.ParseCommand(rootCommand, args)
if err != nil {
return nil, 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
}
}
commandDeps := CommandDependencies[T]{
GlobalConfig: configMap,
TxConfig: clientCtx.TxConfig,
ModuleManager: moduleManager,
SimApp: simApp,
}
rootCommand = &cobra.Command{
Use: "simdv2",
Short: "simulation app",
SilenceErrors: true,
PersistentPreRunE: RootCommandPersistentPreRun(clientCtx),
}
factory.EnhanceRootCommand(rootCommand)
_, err = InitRootCmd(rootCommand, logger, commandDeps)
if err != nil {
return nil, err
}
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)
if err := autoCliOpts.EnhanceRootCommand(rootCommand); 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")
}
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)
}
// 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
return rootCommand, nil
}
+12 -12
View File
@@ -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)
+4 -10
View File
@@ -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)
+6 -7
View File
@@ -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())
}
+15 -6
View File
@@ -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)
}
}
+5 -2
View File
@@ -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")
}
+16 -7
View File
@@ -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
+8 -5
View File
@@ -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)
}
-9
View File
@@ -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 {
+20 -10
View File
@@ -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)
+25 -14
View File
@@ -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)
}