diff --git a/changelog.md b/changelog.md index 95e3165366..6a33895cf9 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ - [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands - [#3985](https://github.com/ignite/cli/pull/3985) Make some `cmd` pkg functions public - [#3967](https://github.com/ignite/cli/issues/3967) Add HD wallet parameters `address index` and `account number` to the chain account config +- [#3660](https://github.com/ignite/cli/pull/3660) Add ability to scaffold ICS consumer chain - [#4004](https://github.com/ignite/cli/pull/4004) Remove all import placeholders using the `xast` pkg ### Changes diff --git a/docs/docs/08-references/02-config.md b/docs/docs/08-references/02-config.md index 1eb4aa7f8f..34347b9565 100644 --- a/docs/docs/08-references/02-config.md +++ b/docs/docs/08-references/02-config.md @@ -12,6 +12,26 @@ to describe the development environment for your blockchain. Only a default set of parameters is provided. If more nuanced configuration is required, you can add these parameters to the `config.yml` file. +## Validation + +Ignite uses the `validation` field to determine the kind of validation +of your blockchain. There are currently two supported kinds of validation: + +- `sovereign` which is the standard kind of validation where your blockchain + has its own validator set. This is the default value when this field is not + in the config file. +- `consumer` indicates your blockchain is a consumer chain, in the sense of + Replicated Security. That means it doesn't have a validator set, but + inherits the one of a provider chain. + +While the `sovereign` chain is the default validation when you run the `ignite scaffold +chain`, to scaffold a consumer chain, you have to run `ignite scaffold chain +--consumer`. + +This field is, at this time of writing, only used by Ignite at the genesis +generation step, because the genesis of a sovereign chain and a consumer chain +are different. + ## Accounts A list of user accounts created during genesis of the blockchain. diff --git a/ignite/cmd/scaffold_chain.go b/ignite/cmd/scaffold_chain.go index 1c44c3d0ae..9539e0474a 100644 --- a/ignite/cmd/scaffold_chain.go +++ b/ignite/cmd/scaffold_chain.go @@ -14,6 +14,7 @@ const ( flagMinimal = "minimal" flagNoDefaultModule = "no-module" flagSkipGit = "skip-git" + flagIsConsumer = "consumer" tplScaffoldChainSuccess = ` ⭐️ Successfully created a new blockchain '%[1]v'. @@ -84,6 +85,9 @@ about Cosmos SDK on https://docs.cosmos.network c.Flags().StringSlice(flagModuleConfigs, []string{}, "add module configs") c.Flags().Bool(flagSkipGit, false, "skip Git repository initialization") c.Flags().Bool(flagMinimal, false, "create a minimal blockchain (with the minimum required Cosmos SDK modules)") + c.Flags().Bool(flagIsConsumer, false, "scafffold an ICS consumer chain") + // Cannot have both minimal and consumer flag + c.MarkFlagsMutuallyExclusive(flagIsConsumer, flagMinimal) return c } @@ -99,6 +103,7 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { noDefaultModule, _ = cmd.Flags().GetBool(flagNoDefaultModule) skipGit, _ = cmd.Flags().GetBool(flagSkipGit) minimal, _ = cmd.Flags().GetBool(flagMinimal) + isConsumer, _ = cmd.Flags().GetBool(flagIsConsumer) params, _ = cmd.Flags().GetStringSlice(flagParams) moduleConfigs, _ = cmd.Flags().GetStringSlice(flagModuleConfigs) ) @@ -116,7 +121,7 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { return err } - appdir, err := scaffolder.Init( + appDir, err := scaffolder.Init( cmd.Context(), cacheStorage, placeholder.New(), @@ -126,6 +131,7 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { noDefaultModule, skipGit, minimal, + isConsumer, params, moduleConfigs, ) @@ -133,7 +139,7 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { return err } - path, err := xfilepath.RelativePath(appdir) + path, err := xfilepath.RelativePath(appDir) if err != nil { return err } diff --git a/ignite/config/chain/base/config.go b/ignite/config/chain/base/config.go index 60699bca12..033886a163 100644 --- a/ignite/config/chain/base/config.go +++ b/ignite/config/chain/base/config.go @@ -153,15 +153,29 @@ type Host struct { API string `yaml:"api"` } +// Validation describes the kind of validation the chain has. +type Validation string + +const ( + // ValidationSovereign is when the chain has his own validator set. + // Note that an empty string is also considered as a sovereign validation, + // because this is the default value. + ValidationSovereign = "sovereign" + // ValidationConsumer is when the chain is validated by a provider chain. + // Such chain is called a consumer chain. + ValidationConsumer = "consumer" +) + // Config defines a struct with the fields that are common to all config versions. type Config struct { - Version version.Version `yaml:"version"` - Build Build `yaml:"build,omitempty"` - Accounts []Account `yaml:"accounts"` - Faucet Faucet `yaml:"faucet,omitempty"` - Client Client `yaml:"client,omitempty"` - Genesis xyaml.Map `yaml:"genesis,omitempty"` - Minimal bool `yaml:"minimal,omitempty"` + Validation Validation `yaml:"validation,omitempty"` + Version version.Version `yaml:"version"` + Build Build `yaml:"build,omitempty"` + Accounts []Account `yaml:"accounts"` + Faucet Faucet `yaml:"faucet,omitempty"` + Client Client `yaml:"client,omitempty"` + Genesis xyaml.Map `yaml:"genesis,omitempty"` + Minimal bool `yaml:"minimal,omitempty"` } // GetVersion returns the config version. @@ -174,6 +188,14 @@ func (c Config) IsChainMinimal() bool { return c.Minimal } +func (c Config) IsSovereignChain() bool { + return c.Validation == "" || c.Validation == ValidationSovereign +} + +func (c Config) IsConsumerChain() bool { + return c.Validation == ValidationConsumer +} + // SetDefaults assigns default values to empty config fields. func (c *Config) SetDefaults() error { return mergo.Merge(c, DefaultConfig()) diff --git a/ignite/internal/plugin/consumer.go b/ignite/internal/plugin/consumer.go new file mode 100644 index 0000000000..03f2b5ef70 --- /dev/null +++ b/ignite/internal/plugin/consumer.go @@ -0,0 +1,39 @@ +package plugininternal + +import ( + "context" + "strconv" + + "github.com/ignite/cli/v28/ignite/pkg/errors" + "github.com/ignite/cli/v28/ignite/services/plugin" +) + +// TODO use released version of app-consumer. +const consumerPlugin = "github.com/ignite/apps/consumer" + +// ConsumerWriteGenesis writes validators in the consumer module genesis. +// NOTE(tb): Using a plugin for this task avoids having the interchain-security +// dependency in Ignite. +func ConsumerWriteGenesis(ctx context.Context, c plugin.Chainer) error { + _, err := Execute(ctx, consumerPlugin, []string{"writeGenesis"}, plugin.WithChain(c)) + if err != nil { + return errors.Errorf("execute consumer plugin 'writeGenesis': %w", err) + } + return nil +} + +// ConsumerIsInitialized returns true if the consumer chain's genesis c has +// a consumer module entry with an initial validator set. +// NOTE(tb): Using a plugin for this task avoids having the interchain-security +// dependency in Ignite. +func ConsumerIsInitialized(ctx context.Context, c plugin.Chainer) (bool, error) { + out, err := Execute(ctx, consumerPlugin, []string{"isInitialized"}, plugin.WithChain(c)) + if err != nil { + return false, errors.Errorf("execute consumer plugin 'isInitialized': %w", err) + } + b, err := strconv.ParseBool(out) + if err != nil { + return false, errors.Errorf("invalid consumer plugin 'isInitialized' output, got '%s': %w", out, err) + } + return b, nil +} diff --git a/ignite/internal/plugin/consumer_test.go b/ignite/internal/plugin/consumer_test.go new file mode 100644 index 0000000000..1bb5954f3d --- /dev/null +++ b/ignite/internal/plugin/consumer_test.go @@ -0,0 +1,134 @@ +package plugininternal + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v28/ignite/services/plugin" + "github.com/ignite/cli/v28/ignite/services/plugin/mocks" +) + +func TestConsumerPlugin(t *testing.T) { + tests := []struct { + name string + args []string + setup func(*testing.T, string) + expectedOutput string + expectedError string + }{ + { + name: "fail: missing arg", + expectedError: "missing argument", + }, + { + name: "fail: invalid arg", + args: []string{"xxx"}, + expectedError: "invalid argument \"xxx\"", + }, + { + name: "fail: writeGenesis w/o priv_validator_key.json", + args: []string{"writeGenesis"}, + expectedError: "open .*/config/priv_validator_key.json: no such file or directory", + }, + { + name: "fail: writeFenesis w/o genesis.json", + args: []string{"writeGenesis"}, + setup: func(t *testing.T, path string) { + // Add priv_validator_key.json to path + bz, err := os.ReadFile("testdata/consumer/config/priv_validator_key.json") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(path, "config", "priv_validator_key.json"), bz, 0o777) + require.NoError(t, err) + }, + expectedError: ".*/config/genesis.json does not exist, run `init` first", + }, + + { + name: "ok: writeGenesis", + args: []string{"writeGenesis"}, + setup: func(t *testing.T, path string) { + // Add priv_validator_key.json to path + bz, err := os.ReadFile("testdata/consumer/config/priv_validator_key.json") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(path, "config", "priv_validator_key.json"), bz, 0o777) + require.NoError(t, err) + + // Add genesis.json to path + bz, err = os.ReadFile("testdata/consumer/config/genesis.json") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(path, "config", "genesis.json"), bz, 0o777) + require.NoError(t, err) + }, + }, + { + name: "ok: isInitialized returns false", + args: []string{"isInitialized"}, + expectedOutput: "false", + }, + { + name: "ok: isInitialized returns true", + args: []string{"isInitialized"}, + setup: func(t *testing.T, path string) { + // isInitialized returns true if there's a consumer genesis with an + // InitialValSet length != 0 + // Add priv_validator_key.json to path + bz, err := os.ReadFile("testdata/consumer/config/priv_validator_key.json") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(path, "config", "priv_validator_key.json"), bz, 0o777) + require.NoError(t, err) + + // Add genesis.json to path + bz, err = os.ReadFile("testdata/consumer/config/genesis.json") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(path, "config", "genesis.json"), bz, 0o777) + require.NoError(t, err) + + // Call writeGenesis to create the genesis + chainer := mocks.NewChainerInterface(t) + chainer.EXPECT().ID().Return("id", nil).Maybe() + chainer.EXPECT().AppPath().Return("apppath").Maybe() + chainer.EXPECT().ConfigPath().Return("configpath").Maybe() + chainer.EXPECT().Home().Return(path, nil).Maybe() + chainer.EXPECT().RPCPublicAddress().Return("rpcPublicAddress", nil).Maybe() + _, err = Execute(context.Background(), consumerPlugin, []string{"writeGenesis"}, plugin.WithChain(chainer)) + require.NoError(t, err) + }, + expectedOutput: "true", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + homePath := t.TempDir() + err := os.MkdirAll(filepath.Join(homePath, "config"), 0o777) + require.NoError(t, err) + chainer := mocks.NewChainerInterface(t) + chainer.EXPECT().ID().Return("id", nil).Maybe() + chainer.EXPECT().AppPath().Return("apppath").Maybe() + chainer.EXPECT().ConfigPath().Return("configpath").Maybe() + chainer.EXPECT().Home().Return(homePath, nil).Maybe() + chainer.EXPECT().RPCPublicAddress().Return("rpcPublicAddress", nil).Maybe() + if tt.setup != nil { + tt.setup(t, homePath) + } + + out, err := Execute( + context.Background(), + consumerPlugin, + tt.args, + plugin.WithChain(chainer), + ) + + if tt.expectedError != "" { + require.Error(t, err) + require.Regexp(t, tt.expectedError, err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.expectedOutput, out) + }) + } +} diff --git a/ignite/internal/plugin/execute_test.go b/ignite/internal/plugin/execute_test.go index 199950dcaf..5e5bf360c9 100644 --- a/ignite/internal/plugin/execute_test.go +++ b/ignite/internal/plugin/execute_test.go @@ -15,10 +15,10 @@ import ( func TestPluginExecute(t *testing.T) { tests := []struct { - name string - pluginPath string - expectedOut string - expectedError string + name string + pluginPath string + expectedOutput string + expectedError string }{ { name: "fail: plugin doesnt exist", @@ -26,12 +26,12 @@ func TestPluginExecute(t *testing.T) { expectedError: "local app path \"/not/exists\" not found: stat /not/exists: no such file or directory", }, { - name: "ok: plugin execute ok ", - pluginPath: "testdata/execute_ok", - expectedOut: "ok args=[arg1 arg2] chainid=id appPath=apppath configPath=configpath home=home rpcAddress=rpcPublicAddress\n", + name: "ok: plugin execute ok", + pluginPath: "testdata/execute_ok", + expectedOutput: "ok args=[arg1 arg2] chainid=id appPath=apppath configPath=configpath home=home rpcAddress=rpcPublicAddress\n", }, { - name: "ok: plugin execute fail ", + name: "ok: plugin execute fail", pluginPath: "testdata/execute_fail", expectedError: "fail", }, @@ -64,7 +64,7 @@ func TestPluginExecute(t *testing.T) { return } require.NoError(t, err) - require.Equal(t, tt.expectedOut, out) + require.Equal(t, tt.expectedOutput, out) }) } } diff --git a/ignite/internal/plugin/testdata/consumer/config/genesis.json b/ignite/internal/plugin/testdata/consumer/config/genesis.json new file mode 100644 index 0000000000..49a3950dda --- /dev/null +++ b/ignite/internal/plugin/testdata/consumer/config/genesis.json @@ -0,0 +1,9 @@ +{ + "app_name": "test", + "app_version": "", + "genesis_time": "2024-01-19T10:27:44.742750573Z", + "chain_id": "test", + "initial_height": 1, + "app_hash": null, + "app_state": {} +} diff --git a/ignite/internal/plugin/testdata/consumer/config/priv_validator_key.json b/ignite/internal/plugin/testdata/consumer/config/priv_validator_key.json new file mode 100644 index 0000000000..ec5122805f --- /dev/null +++ b/ignite/internal/plugin/testdata/consumer/config/priv_validator_key.json @@ -0,0 +1,11 @@ +{ + "address": "2D3C15095E5EAA318CAEDE1C2D02C77581584751", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "uBOT+dDuUvXjJrkfwMNrS4bRT4/O+fBnpwfYpR6n1Wk=" + }, + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "HovIzTJTGMrQx5oBikjfypyMZYF9QP5MxS+S+S/3QYq4E5P50O5S9eMmuR/Aw2tLhtFPj8758GenB9ilHqfVaQ==" + } +} \ No newline at end of file diff --git a/ignite/services/chain/build.go b/ignite/services/chain/build.go index 2bb2753288..33ba7b9697 100644 --- a/ignite/services/chain/build.go +++ b/ignite/services/chain/build.go @@ -29,6 +29,7 @@ const ( releaseChecksumKey = "release_checksum" modChecksumKey = "go_mod_checksum" buildDirchangeCacheNamespace = "build.dirchange" + consumerDevel = "consumer_devel" ) // Build builds and installs app binaries. @@ -72,6 +73,17 @@ func (c *Chain) build( } } + cfg, err := c.Config() + if err != nil { + return err + } + if cfg.IsConsumerChain() { + // When building a non-release consumer chain (which is the case for this + // build() method), enable consumerDevel (see templates consumer_devel and + // consumer_final for more info). + buildTags = append(buildTags, consumerDevel) + } + buildFlags, err := c.preBuild(ctx, cacheStorage, buildTags...) if err != nil { return err diff --git a/ignite/services/chain/init.go b/ignite/services/chain/init.go index 533bcb7e82..101d75ea96 100644 --- a/ignite/services/chain/init.go +++ b/ignite/services/chain/init.go @@ -9,6 +9,7 @@ import ( "github.com/imdario/mergo" chainconfig "github.com/ignite/cli/v28/ignite/config/chain" + "github.com/ignite/cli/v28/ignite/internal/plugin" chaincmdrunner "github.com/ignite/cli/v28/ignite/pkg/chaincmd/runner" "github.com/ignite/cli/v28/ignite/pkg/cliui/view/accountview" "github.com/ignite/cli/v28/ignite/pkg/confile" @@ -171,11 +172,22 @@ func (c *Chain) InitAccounts(ctx context.Context, cfg *chainconfig.Config) error c.ev.SendView(accounts, events.ProgressFinish()) // 0 length validator set when using network config - if len(cfg.Validators) != 0 { - _, err = c.IssueGentx(ctx, createValidatorFromConfig(cfg)) + if len(cfg.Validators) == 0 { + return nil } - - return err + if cfg.IsConsumerChain() { + err := plugininternal.ConsumerWriteGenesis(ctx, c) + if err != nil { + return err + } + } else { + // Sovereign chain writes validators in gentxs. + _, err := c.IssueGentx(ctx, createValidatorFromConfig(cfg)) + if err != nil { + return err + } + } + return nil } // IssueGentx generates a gentx from the validator information in chain config and imports it in the chain genesis. @@ -196,12 +208,22 @@ func (c Chain) IssueGentx(ctx context.Context, v Validator) (string, error) { } // IsInitialized checks if the chain is initialized. -// The check is performed by checking if the gentx dir exists in the config. +// The check is performed by checking if the gentx dir exists in the config, +// unless c is a consumer chain, in which case the check relies on checking if +// the consumer genesis module is filled with validators. func (c *Chain) IsInitialized() (bool, error) { home, err := c.Home() if err != nil { return false, err } + cfg, err := c.Config() + if err != nil { + return false, err + } + if cfg.IsConsumerChain() { + return plugininternal.ConsumerIsInitialized(context.Background(), c) + } + gentxDir := filepath.Join(home, "config", "gentx") if _, err := os.Stat(gentxDir); os.IsNotExist(err) { diff --git a/ignite/services/scaffolder/init.go b/ignite/services/scaffolder/init.go index 4262c2bb2a..d6f91d8e61 100644 --- a/ignite/services/scaffolder/init.go +++ b/ignite/services/scaffolder/init.go @@ -25,7 +25,7 @@ func Init( cacheStorage cache.Storage, tracer *placeholder.Tracer, root, name, addressPrefix string, - noDefaultModule, skipGit, minimal bool, + noDefaultModule, skipGit, minimal, isConsumerChain bool, params, moduleConfigs []string, ) (path string, err error) { pathInfo, err := gomodulepath.Parse(name) @@ -46,8 +46,18 @@ func Init( path = filepath.Join(root, appFolder) // create the project - err = generate(ctx, tracer, pathInfo, addressPrefix, path, noDefaultModule, minimal, params, moduleConfigs) - if err != nil { + if err := generate( + ctx, + tracer, + pathInfo, + addressPrefix, + path, + noDefaultModule, + minimal, + isConsumerChain, + params, + moduleConfigs, + ); err != nil { return "", err } @@ -73,7 +83,7 @@ func generate( pathInfo gomodulepath.Path, addressPrefix, absRoot string, - noDefaultModule, minimal bool, + noDefaultModule, minimal, isConsumerChain bool, params, moduleConfigs []string, ) error { // Parse params with the associated type @@ -103,6 +113,7 @@ func generate( BinaryNamePrefix: pathInfo.Root, AddressPrefix: addressPrefix, IsChainMinimal: minimal, + IsConsumerChain: isConsumerChain, }) if err != nil { return err diff --git a/ignite/services/scaffolder/module.go b/ignite/services/scaffolder/module.go index c25941daf6..64b531b0d7 100644 --- a/ignite/services/scaffolder/module.go +++ b/ignite/services/scaffolder/module.go @@ -56,6 +56,7 @@ var ( "account": {}, "block": {}, "broadcast": {}, + "consumer": {}, // ICS consumer module "encode": {}, "multisign": {}, "sign": {}, diff --git a/ignite/templates/app/app.go b/ignite/templates/app/app.go index 573a05d896..8b50f00481 100644 --- a/ignite/templates/app/app.go +++ b/ignite/templates/app/app.go @@ -19,6 +19,9 @@ var ( //go:embed files-minimal/* files-minimal/**/* filesMinimal embed.FS + + //go:embed files-consumer/* files-consumer/**/* + filesConsumer embed.FS ) const ( @@ -32,27 +35,35 @@ func NewGenerator(opts *Options) (*genny.Generator, error) { if err != nil { return nil, errors.Errorf("generator sub: %w", err) } - g := genny.New() - var excludePrefix []string + var ( + includePrefix = opts.IncludePrefixes + excludePrefix []string + overridesFS = make(map[string]embed.FS) + ) if opts.IsChainMinimal { // minimal chain does not have ibc excludePrefix = append(excludePrefix, ibcConfig) + overridesFS["files-minimal"] = filesMinimal + } + if opts.IsConsumerChain { + overridesFS["files-consumer"] = filesConsumer } - if err := g.SelectiveFS(subfs, opts.IncludePrefixes, nil, excludePrefix, nil); err != nil { + g := genny.New() + if err := g.SelectiveFS(subfs, includePrefix, nil, excludePrefix, nil); err != nil { return g, errors.Errorf("generator fs: %w", err) } - if opts.IsChainMinimal { - // Remove "files-minimal/" prefix - subfs, err := fs.Sub(filesMinimal, "files-minimal") + for prefix, embed := range overridesFS { + // Remove prefix + subfs, err := fs.Sub(embed, prefix) if err != nil { - return nil, errors.Errorf("generator sub minimal: %w", err) + return g, errors.Errorf("generator sub %s: %w", prefix, err) } - // Override files from "files" with the ones from "files-minimal" + // Override files from "files" with the ones from embed if err := g.FS(subfs); err != nil { - return g, errors.Errorf("generator fs minimal: %w", err) + return g, errors.Errorf("generator fs %s: %w", prefix, err) } } @@ -62,6 +73,7 @@ func NewGenerator(opts *Options) (*genny.Generator, error) { ctx.Set("GitHubPath", opts.GitHubPath) ctx.Set("BinaryNamePrefix", opts.BinaryNamePrefix) ctx.Set("AddressPrefix", opts.AddressPrefix) + ctx.Set("IsConsumerChain", opts.IsConsumerChain) ctx.Set("DepTools", cosmosgen.DepTools()) ctx.Set("IsChainMinimal", opts.IsChainMinimal) diff --git a/ignite/templates/app/files-consumer/app/ante_handler.go.plush b/ignite/templates/app/files-consumer/app/ante_handler.go.plush new file mode 100644 index 0000000000..de1c4ecca8 --- /dev/null +++ b/ignite/templates/app/files-consumer/app/ante_handler.go.plush @@ -0,0 +1,73 @@ +package app + +import ( + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + circuitante "cosmossdk.io/x/circuit/ante" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + consumerante "github.com/cosmos/interchain-security/v3/app/consumer/ante" + ibcconsumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC channel keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCKeeper *ibckeeper.Keeper + ConsumerKeeper ibcconsumerkeeper.Keeper + CircuitKeeper circuitante.CircuitBreaker +} + +func NewAnteHandler(options HandlerOptions, logger log.Logger) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + sigGasConsumer := options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = ante.DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), + circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper), + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + consumerante.NewDisabledModulesDecorator("/cosmos.evidence", "/cosmos.slashing"), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + // This constant depends on build tag. + // When true the consumer ante decorator is not added. This decorator rejects + // all non-IBC messages until the CCV channel is established with the + // provider chain. While this is useful for production, for development it's + // more convenient to avoid it so the consumer chain can be tested without + // a provider chain and a relayer running on top. + if !ConsumerSkipMsgFilter { + anteDecorators = append(anteDecorators, consumerante.NewMsgFilterDecorator(options.ConsumerKeeper)) + } else { + logger.Error("WARNING: BUILT WITH skip_ccv_msg_filter. THIS IS NOT A PRODUCTION BUILD") + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/ignite/templates/app/files-consumer/app/app.go.plush b/ignite/templates/app/files-consumer/app/app.go.plush new file mode 100644 index 0000000000..fac3700686 --- /dev/null +++ b/ignite/templates/app/files-consumer/app/app.go.plush @@ -0,0 +1,463 @@ +package app + +import ( + "io" + "os" + "path/filepath" + "fmt" + + "cosmossdk.io/depinject" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + circuitkeeper "cosmossdk.io/x/circuit/keeper" + evidencekeeper "cosmossdk.io/x/evidence/keeper" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + _ "cosmossdk.io/api/cosmos/tx/config/v1" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/auth" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/bank" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/consensus" // import for side-effects + _ "cosmossdk.io/x/circuit" // import for side-effects + _ "cosmossdk.io/x/evidence" // import for side-effects + _ "cosmossdk.io/x/feegrant/module" // import for side-effects + _ "cosmossdk.io/x/nft/module" // import for side-effects + nftkeeper "cosmossdk.io/x/nft/keeper" + _ "cosmossdk.io/x/upgrade" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/auth/vesting" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/authz/module" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/crisis" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/group/module" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/params" // import for side-effects + _ "github.com/cosmos/cosmos-sdk/x/slashing" // import for side-effects + _ "github.com/cosmos/ibc-go/modules/capability" // import for side-effects + _ "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts" // import for side-effects + _ "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" // import for side-effects + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + icacontrollerkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/keeper" + icahostkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/keeper" + ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + ibcconsumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + ibcconsumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + // this line is used by starport scaffolding # stargate/app/moduleImport + + "<%= ModulePath %>/docs" +) + +const ( + AccountAddressPrefix = "<%= AddressPrefix %>" + Name = "<%= BinaryNamePrefix %>" +) + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string +) + +var ( + _ runtime.AppI = (*App)(nil) + _ servertypes.Application = (*App)(nil) +) + +// App 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. +type App struct { + *runtime.App + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry codectypes.InterfaceRegistry + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + ConsensusParamsKeeper consensuskeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + ConsumerKeeper ibcconsumerkeeper.Keeper + CrisisKeeper *crisiskeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + EvidenceKeeper evidencekeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + GroupKeeper groupkeeper.Keeper + NFTKeeper nftkeeper.Keeper + CircuitBreakerKeeper circuitkeeper.Keeper + + // IBC + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + CapabilityKeeper *capabilitykeeper.Keeper + IBCFeeKeeper ibcfeekeeper.Keeper + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + + // Scoped IBC + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedIBCTransferKeeper capabilitykeeper.ScopedKeeper + ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper + ScopedICAHostKeeper capabilitykeeper.ScopedKeeper + + // this line is used by starport scaffolding # stargate/app/keeperDeclaration + + // simulation manager + sm *module.SimulationManager +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, "."+Name) +} + +// AppConfig returns the default app config. +func AppConfig() depinject.Config { + return depinject.Configs( + appConfig, + // Loads the app config from a YAML file. + // appconfig.LoadYAML(AppConfigYAML), + depinject.Supply( + // supply custom module basics + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + // this line is used by starport scaffolding # stargate/appConfig/moduleBasic + }, + ), + ) +} + +// New returns a reference to an initialized App. +func New( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) (*App, error) { + var ( + app = &App{} + appBuilder *runtime.AppBuilder + + // merge the AppConfig and other configuration in one config + appConfig = depinject.Configs( + AppConfig(), + depinject.Supply( + // Supply the application options + appOpts, + // Supply with IBC keeper getter for the IBC modules with App Wiring. + // The IBC Keeper cannot be passed because it has not been initiated yet. + // Passing the getter, the app IBC Keeper will always be accessible. + // This needs to be removed after IBC supports App Wiring. + app.GetIBCKeeper, + app.GetCapabilityScopedKeeper, + // Supply the consumer keeper for the consumer module + &app.ConsumerKeeper, + // Supply the logger + logger, + + // 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. + ), + ) + ) + + if err := depinject.Inject(appConfig, + &appBuilder, + &app.appCodec, + &app.legacyAmino, + &app.txConfig, + &app.interfaceRegistry, + &app.AccountKeeper, + &app.BankKeeper, + &app.ConsensusParamsKeeper, + &app.SlashingKeeper, + &app.CrisisKeeper, + &app.UpgradeKeeper, + &app.ParamsKeeper, + &app.AuthzKeeper, + &app.EvidenceKeeper, + &app.FeeGrantKeeper, + &app.NFTKeeper, + &app.GroupKeeper, + &app.CircuitBreakerKeeper, + // this line is used by starport scaffolding # stargate/app/keeperDefinition + ); err != nil { + panic(err) + } + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // app.App = appBuilder.Build(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, app.App.BaseApp) + // + // app.App.BaseApp.SetMempool(nonceMempool) + // app.App.BaseApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // app.App.BaseApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to the appBuilder. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + // + // create and set vote extension handler + // voteExtOp := func(bApp *baseapp.BaseApp) { + // voteExtHandler := NewVoteExtensionHandler() + // voteExtHandler.SetHandlers(bApp) + // } + + app.App = appBuilder.Build(db, traceStore, baseAppOptions...) + + // Register legacy modules + if err := app.registerIBCModules(appOpts); err != nil { + return nil, err + } + + // register streaming services + if err := app.RegisterStreamingServices(appOpts, app.kvStoreKeys()); err != nil { + return nil, err + } + + /**** Module Options ****/ + + app.ModuleManager.RegisterInvariants(app.CrisisKeeper) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.sm.RegisterStoreDecoders() + + // TODO theres probably a better way to SetAnteHandler with app wiring + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: app.txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + ConsumerKeeper: app.ConsumerKeeper, + CircuitKeeper: &app.CircuitBreakerKeeper, + }, + logger, + ) + if err != nil { + panic(fmt.Errorf("failed to create AnteHandler: %w", err)) + } + app.SetAnteHandler(anteHandler) + + // A custom InitChainer can be set if extra pre-init-genesis logic is required. + // By default, when using app wiring enabled module, this is not required. + // For instance, the upgrade module will set automatically the module version map in its init genesis thanks to app wiring. + // However, when registering a module manually (i.e. that does not support app wiring), the module version map + // must be set manually as follow. The upgrade module will de-duplicate the module version map. + // + // app.SetInitChainer(func(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + // app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) + // return app.App.InitChainer(ctx, req) + // }) + + if err := app.Load(loadLatest); err != nil { + return nil, err + } + + return app, nil +} + +// LegacyAmino returns App's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns App's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) AppCodec() codec.Codec { + return app.appCodec +} + +// GetKey returns the KVStoreKey for the provided store key. +func (app *App) GetKey(storeKey string) *storetypes.KVStoreKey { + kvStoreKey, ok := app.UnsafeFindStoreKey(storeKey).(*storetypes.KVStoreKey) + if !ok { + return nil + } + return kvStoreKey +} + +// GetMemKey returns the MemoryStoreKey for the provided store key. +func (app *App) GetMemKey(storeKey string) *storetypes.MemoryStoreKey { + key, ok := app.UnsafeFindStoreKey(storeKey).(*storetypes.MemoryStoreKey) + if !ok { + return nil + } + + return key +} + +// kvStoreKeys returns all the kv store keys registered inside App. +func (app *App) kvStoreKeys() map[string]*storetypes.KVStoreKey { + keys := make(map[string]*storetypes.KVStoreKey) + for _, k := range app.GetStoreKeys() { + if kv, ok := k.(*storetypes.KVStoreKey); ok { + keys[kv.Name()] = kv + } + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +func (app *App) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// GetIBCKeeper returns the IBC keeper. +func (app *App) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetCapabilityScopedKeeper returns the capability scoped keeper. +func (app *App) GetCapabilityScopedKeeper(moduleName string) capabilitykeeper.ScopedKeeper { + return app.CapabilityKeeper.ScopeToModule(moduleName) +} + +// SimulationManager implements the SimulationApp interface. +func (app *App) SimulationManager() *module.SimulationManager { + return app.sm +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *App) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + app.App.RegisterAPIRoutes(apiSvr, apiConfig) + // register swagger API in app.go so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } + + // register app's OpenAPI routes. + docs.RegisterOpenAPIService(Name, apiSvr.Router) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dup := make(map[string][]string) + for _, perms := range moduleAccPerms { + dup[perms.Account] = perms.Permissions + } + return dup +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + result := make(map[string]bool) + if len(blockAccAddrs) > 0 { + for _, addr := range blockAccAddrs { + result[addr] = true + } + } else { + for addr := range GetMaccPerms() { + result[addr] = true + } + } + // Remove the fee-pool from the group of blocked recipient addresses in bank + // this is required for the consumer chain to be able to send tokens to + // the provider chain + delete(result, authtypes.NewModuleAddress(ibcconsumertypes.ConsumerToSendToProviderName).String()) + return result +} diff --git a/ignite/templates/app/files-consumer/app/app_config.go.plush b/ignite/templates/app/files-consumer/app/app_config.go.plush new file mode 100644 index 0000000000..669b0fc6cc --- /dev/null +++ b/ignite/templates/app/files-consumer/app/app_config.go.plush @@ -0,0 +1,252 @@ +package app + +import ( + "time" + + runtimev1alpha1 "cosmossdk.io/api/cosmos/app/runtime/v1alpha1" + appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" + authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1" + authzmodulev1 "cosmossdk.io/api/cosmos/authz/module/v1" + bankmodulev1 "cosmossdk.io/api/cosmos/bank/module/v1" + circuitmodulev1 "cosmossdk.io/api/cosmos/circuit/module/v1" + consensusmodulev1 "cosmossdk.io/api/cosmos/consensus/module/v1" + crisismodulev1 "cosmossdk.io/api/cosmos/crisis/module/v1" + evidencemodulev1 "cosmossdk.io/api/cosmos/evidence/module/v1" + feegrantmodulev1 "cosmossdk.io/api/cosmos/feegrant/module/v1" + genutilmodulev1 "cosmossdk.io/api/cosmos/genutil/module/v1" + groupmodulev1 "cosmossdk.io/api/cosmos/group/module/v1" + nftmodulev1 "cosmossdk.io/api/cosmos/nft/module/v1" + paramsmodulev1 "cosmossdk.io/api/cosmos/params/module/v1" + slashingmodulev1 "cosmossdk.io/api/cosmos/slashing/module/v1" + txconfigv1 "cosmossdk.io/api/cosmos/tx/config/v1" + upgrademodulev1 "cosmossdk.io/api/cosmos/upgrade/module/v1" + vestingmodulev1 "cosmossdk.io/api/cosmos/vesting/module/v1" + "cosmossdk.io/core/appconfig" + circuittypes "cosmossdk.io/x/circuit/types" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + "cosmossdk.io/x/nft" + upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/group" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" + ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + "google.golang.org/protobuf/types/known/durationpb" + ibcconsumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + // this line is used by starport scaffolding # stargate/app/moduleImport +) + +var ( + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + genesisModuleOrder = []string{ + // cosmos-sdk/ibc modules + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + slashingtypes.ModuleName, + crisistypes.ModuleName, + ibcexported.ModuleName, + genutiltypes.ModuleName, + evidencetypes.ModuleName, + authz.ModuleName, + ibctransfertypes.ModuleName, + icatypes.ModuleName, + ibcfeetypes.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + circuittypes.ModuleName, + nft.ModuleName, + group.ModuleName, + consensustypes.ModuleName, + circuittypes.ModuleName, + ibcconsumertypes.ModuleName, + // chain modules + // this line is used by starport scaffolding # stargate/app/initGenesis + } + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + // NOTE: staking module is required if HistoricalEntries param > 0 + // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) + beginBlockers = []string{ + // cosmos sdk modules + slashingtypes.ModuleName, + evidencetypes.ModuleName, + ibcconsumertypes.ModuleName, + authz.ModuleName, + genutiltypes.ModuleName, + // ibc modules + capabilitytypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + icatypes.ModuleName, + ibcfeetypes.ModuleName, + // chain modules + // this line is used by starport scaffolding # stargate/app/beginBlockers + } + + endBlockers = []string{ + // cosmos sdk modules + crisistypes.ModuleName, + ibcconsumertypes.ModuleName, + feegrant.ModuleName, + group.ModuleName, + genutiltypes.ModuleName, + // ibc modules + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + capabilitytypes.ModuleName, + icatypes.ModuleName, + ibcfeetypes.ModuleName, + // chain modules + // this line is used by starport scaffolding # stargate/app/endBlockers + } + + preBlockers = []string{ + upgradetypes.ModuleName, + // this line is used by starport scaffolding # stargate/app/preBlockers + } + + // module account permissions + moduleAccPerms = []*authmodulev1.ModuleAccountPermission{ + {Account: authtypes.FeeCollectorName}, + {Account: icatypes.ModuleName}, + {Account: ibcconsumertypes.ConsumerRedistributeName}, + {Account: ibcconsumertypes.ConsumerToSendToProviderName}, + {Account: nft.ModuleName}, + {Account: ibctransfertypes.ModuleName, Permissions: []string{authtypes.Minter, authtypes.Burner}}, + {Account: ibcfeetypes.ModuleName}, + {Account: icatypes.ModuleName}, + // this line is used by starport scaffolding # stargate/app/maccPerms + } + + // blocked account addresses + blockAccAddrs = []string{ + authtypes.FeeCollectorName, + // We allow the following module accounts to receive funds: + // govtypes.ModuleName + } + + // appConfig application configuration (used by depinject) + appConfig = appconfig.Compose(&appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + { + Name: runtime.ModuleName, + Config: appconfig.WrapAny(&runtimev1alpha1.Module{ + AppName: Name, + PreBlockers: preBlockers, + BeginBlockers: beginBlockers, + EndBlockers: endBlockers, + InitGenesis: genesisModuleOrder, + OverrideStoreKeys: []*runtimev1alpha1.StoreKeyConfig{ + { + ModuleName: authtypes.ModuleName, + KvStoreKey: "acc", + }, + }, + // When ExportGenesis is not specified, the export genesis module order + // is equal to the init genesis order + // ExportGenesis: genesisModuleOrder, + // Uncomment if you want to set a custom migration order here. + // OrderMigrations: nil, + }), + }, + { + Name: authtypes.ModuleName, + Config: appconfig.WrapAny(&authmodulev1.Module{ + Bech32Prefix: AccountAddressPrefix, + ModuleAccountPermissions: moduleAccPerms, + // By default modules authority is the governance module. This is configurable with the following: + // Authority: "group", // A custom module authority can be set using a module name + // Authority: "cosmos1cwwv22j5ca08ggdv9c2uky355k908694z577tv", // or a specific address + }), + }, + { + Name: nft.ModuleName, + Config: appconfig.WrapAny(&nftmodulev1.Module{}), + }, + { + Name: vestingtypes.ModuleName, + Config: appconfig.WrapAny(&vestingmodulev1.Module{}), + }, + { + Name: banktypes.ModuleName, + Config: appconfig.WrapAny(&bankmodulev1.Module{ + BlockedModuleAccountsOverride: blockAccAddrs, + }), + }, + { + Name: slashingtypes.ModuleName, + Config: appconfig.WrapAny(&slashingmodulev1.Module{}), + }, + { + Name: paramstypes.ModuleName, + Config: appconfig.WrapAny(¶msmodulev1.Module{}), + }, + { + Name: "tx", + Config: appconfig.WrapAny(&txconfigv1.Config{}), + }, + { + Name: genutiltypes.ModuleName, + Config: appconfig.WrapAny(&genutilmodulev1.Module{}), + }, + { + Name: authz.ModuleName, + Config: appconfig.WrapAny(&authzmodulev1.Module{}), + }, + { + Name: upgradetypes.ModuleName, + Config: appconfig.WrapAny(&upgrademodulev1.Module{}), + }, + { + Name: evidencetypes.ModuleName, + Config: appconfig.WrapAny(&evidencemodulev1.Module{}), + }, + { + Name: group.ModuleName, + Config: appconfig.WrapAny(&groupmodulev1.Module{ + MaxExecutionPeriod: durationpb.New(time.Second * 1209600), + MaxMetadataLen: 255, + }), + }, + { + Name: feegrant.ModuleName, + Config: appconfig.WrapAny(&feegrantmodulev1.Module{}), + }, + { + Name: crisistypes.ModuleName, + Config: appconfig.WrapAny(&crisismodulev1.Module{}), + }, + { + Name: consensustypes.ModuleName, + Config: appconfig.WrapAny(&consensusmodulev1.Module{}), + }, + { + Name: circuittypes.ModuleName, + Config: appconfig.WrapAny(&circuitmodulev1.Module{}), + }, + // this line is used by starport scaffolding # stargate/app/moduleConfig + }, + }) +) diff --git a/ignite/templates/app/files-consumer/app/consumer_devel.go.plush b/ignite/templates/app/files-consumer/app/consumer_devel.go.plush new file mode 100644 index 0000000000..96029aa81b --- /dev/null +++ b/ignite/templates/app/files-consumer/app/consumer_devel.go.plush @@ -0,0 +1,7 @@ +//go:build consumer_devel + +package app + +const ( + ConsumerSkipMsgFilter = true +) diff --git a/ignite/templates/app/files-consumer/app/consumer_final.go.plush b/ignite/templates/app/files-consumer/app/consumer_final.go.plush new file mode 100644 index 0000000000..fa77783545 --- /dev/null +++ b/ignite/templates/app/files-consumer/app/consumer_final.go.plush @@ -0,0 +1,7 @@ +//go:build !consumer_devel + +package app + +const ( + ConsumerSkipMsgFilter = false +) diff --git a/ignite/templates/app/files-consumer/app/export.go.plush b/ignite/templates/app/files-consumer/app/export.go.plush new file mode 100644 index 0000000000..a7b07935fa --- /dev/null +++ b/ignite/templates/app/files-consumer/app/export.go.plush @@ -0,0 +1,71 @@ +package app + +import ( + "encoding/json" + "fmt" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtypes "github.com/cometbft/cometbft/types" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs, modulesToExport []string) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) + + // We export at last height + 1, because that's the height at which + // CometBFT will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + if err != nil { + return servertypes.ExportedApp{}, err + } + + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := app.GetValidatorSet(ctx) + if err != nil { + return servertypes.ExportedApp{}, err + } + + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.BaseApp.GetConsensusParams(ctx), + }, err +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// +// in favor of export at a block height +func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) +} + +// GetValidatorSet returns a slice of bonded validators. +func (app *App) GetValidatorSet(ctx sdk.Context) ([]tmtypes.GenesisValidator, error) { + cVals := app.ConsumerKeeper.GetAllCCValidator(ctx) + if len(cVals) == 0 { + return nil, fmt.Errorf("empty validator set") + } + + vals := []tmtypes.GenesisValidator{} + for _, v := range cVals { + vals = append(vals, tmtypes.GenesisValidator{Address: v.Address, Power: v.Power}) + } + return vals, nil +} diff --git a/ignite/templates/app/files-consumer/app/ibc.go.plush b/ignite/templates/app/files-consumer/app/ibc.go.plush new file mode 100644 index 0000000000..5c8c756d8b --- /dev/null +++ b/ignite/templates/app/files-consumer/app/ibc.go.plush @@ -0,0 +1,241 @@ +package app + +import ( + "cosmossdk.io/core/appmodule" + storetypes "cosmossdk.io/store/types" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/ibc-go/modules/capability" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + icamodule "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" + ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" + ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" + ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" + ibctransfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v8/modules/core" + ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" // nolint:staticcheck // Deprecated: params key table is needed for params migration + ibcconnectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ibcconsumer "github.com/cosmos/interchain-security/v3/x/ccv/consumer" + ibcconsumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + ibcconsumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + ibcccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" + // this line is used by starport scaffolding # ibc/app/import +) + +// registerIBCModules register IBC keepers and non dependency inject modules. +func (app *App) registerIBCModules(appOpts servertypes.AppOptions) error { + // set up non depinject support modules store keys + if err := app.RegisterStores( + storetypes.NewKVStoreKey(capabilitytypes.StoreKey), + storetypes.NewKVStoreKey(ibcexported.StoreKey), + storetypes.NewKVStoreKey(ibctransfertypes.StoreKey), + storetypes.NewKVStoreKey(ibcfeetypes.StoreKey), + storetypes.NewKVStoreKey(icahosttypes.StoreKey), + storetypes.NewKVStoreKey(ibcccvtypes.StoreKey), + storetypes.NewKVStoreKey(icacontrollertypes.StoreKey), + storetypes.NewMemoryStoreKey(capabilitytypes.MemStoreKey), + storetypes.NewTransientStoreKey(paramstypes.TStoreKey), + ); err != nil { + return err + } + + // register the key tables for legacy param subspaces + keyTable := ibcclienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&ibcconnectiontypes.Params{}) + app.ParamsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(keyTable) + app.ParamsKeeper.Subspace(ibctransfertypes.ModuleName).WithKeyTable(ibctransfertypes.ParamKeyTable()) + app.ParamsKeeper.Subspace(icacontrollertypes.SubModuleName).WithKeyTable(icacontrollertypes.ParamKeyTable()) + app.ParamsKeeper.Subspace(ibcccvtypes.ModuleName).WithKeyTable(ibcccvtypes.ParamKeyTable()) + app.ParamsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper( + app.AppCodec(), + app.GetKey(capabilitytypes.StoreKey), + app.GetMemKey(capabilitytypes.MemStoreKey), + ) + + // add capability keeper and ScopeToModule for ibc module + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) + scopedIBCTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) + scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + scopedIBCConsumerKeeper := app.CapabilityKeeper.ScopeToModule(ibcccvtypes.ModuleName) + + // pre-initialize ConsumerKeeper to satisfy ibckeeper.NewKeeper + // which would panic on nil or zero keeper + // ConsumerKeeper implements StakingKeeper but all function calls result in no-ops so this is safe + // communication over IBC is not affected by these changes + app.ConsumerKeeper = ibcconsumerkeeper.NewNonZeroKeeper( + app.appCodec, + app.GetKey(ibcccvtypes.StoreKey), + app.GetSubspace(ibcccvtypes.ModuleName), + ) + + // Create IBC keeper + app.IBCKeeper = ibckeeper.NewKeeper( + app.appCodec, + app.GetKey(ibcexported.StoreKey), + app.GetSubspace(ibcexported.ModuleName), + app.ConsumerKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // initialize the actual consumer keeper + app.ConsumerKeeper = ibcconsumerkeeper.NewKeeper( + app.appCodec, + app.GetKey(ibcccvtypes.StoreKey), + app.GetSubspace(ibcccvtypes.ModuleName), + scopedIBCConsumerKeeper, + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + app.IBCKeeper.ConnectionKeeper, + app.IBCKeeper.ClientKeeper, + app.SlashingKeeper, + app.BankKeeper, + app.AccountKeeper, + &app.TransferKeeper, + app.IBCKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), + authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + + // register slashing module Slashing hooks to the consumer keeper + app.ConsumerKeeper = *app.ConsumerKeeper.SetHooks(app.SlashingKeeper.Hooks()) + consumerModule := ibcconsumer.NewAppModule(app.ConsumerKeeper, app.GetSubspace(ibcccvtypes.ModuleName)) + + app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + app.appCodec, app.GetKey(ibcfeetypes.StoreKey), + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, + ) + + // Create IBC transfer keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + app.appCodec, + app.GetKey(ibctransfertypes.StoreKey), + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + app.AccountKeeper, + app.BankKeeper, + scopedIBCTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create interchain account keepers + app.ICAHostKeeper = icahostkeeper.NewKeeper( + app.appCodec, + app.GetKey(icahosttypes.StoreKey), + app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + app.AccountKeeper, + scopedICAHostKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + app.appCodec, + app.GetKey(icacontrollertypes.StoreKey), + app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create IBC modules with ibcfee middleware + transferIBCModule := ibcfee.NewIBCMiddleware(ibctransfer.NewIBCModule(app.TransferKeeper), app.IBCFeeKeeper) + + // integration point for custom authentication modules + var noAuthzModule porttypes.IBCModule + icaControllerIBCModule := ibcfee.NewIBCMiddleware( + icacontroller.NewIBCMiddleware(noAuthzModule, app.ICAControllerKeeper), + app.IBCFeeKeeper, + ) + + icaHostIBCModule := ibcfee.NewIBCMiddleware(icahost.NewIBCModule(app.ICAHostKeeper), app.IBCFeeKeeper) + + // Create static IBC router, add transfer route, then set and seal it + ibcRouter := porttypes.NewRouter(). + AddRoute(ibctransfertypes.ModuleName, transferIBCModule). + AddRoute(icacontrollertypes.SubModuleName, icaControllerIBCModule). + AddRoute(ibcccvtypes.ModuleName, consumerModule). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) + + // this line is used by starport scaffolding # ibc/app/module + + app.IBCKeeper.SetRouter(ibcRouter) + + app.ScopedIBCKeeper = scopedIBCKeeper + app.ScopedIBCTransferKeeper = scopedIBCTransferKeeper + app.ScopedICAHostKeeper = scopedICAHostKeeper + app.ScopedICAControllerKeeper = scopedICAControllerKeeper + + // register IBC modules + if err := app.RegisterModules( + ibc.NewAppModule(app.IBCKeeper), + ibctransfer.NewAppModule(app.TransferKeeper), + ibcfee.NewAppModule(app.IBCFeeKeeper), + icamodule.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + capability.NewAppModule(app.appCodec, *app.CapabilityKeeper, false), + ibctm.NewAppModule(), + solomachine.NewAppModule(), + consumerModule, + ); err != nil { + return err + } + + return nil +} + +// Since the IBC modules don't support dependency injection, we need to +// manually register the modules on the client side. +// This needs to be removed after IBC supports App Wiring. +func RegisterIBC(registry cdctypes.InterfaceRegistry) map[string]appmodule.AppModule { + modules := map[string]appmodule.AppModule{ + ibcexported.ModuleName: ibc.AppModule{}, + ibctransfertypes.ModuleName: ibctransfer.AppModule{}, + ibcfeetypes.ModuleName: ibcfee.AppModule{}, + icatypes.ModuleName: icamodule.AppModule{}, + capabilitytypes.ModuleName: capability.AppModule{}, + ibctm.ModuleName: ibctm.AppModule{}, + solomachine.ModuleName: solomachine.AppModule{}, + ibcconsumertypes.ModuleName: ibcconsumer.AppModule{}, + } + + for name, m := range modules { + module.CoreAppModuleBasicAdaptor(name, m).RegisterInterfaces(registry) + } + + return modules +} diff --git a/ignite/templates/app/files-consumer/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush b/ignite/templates/app/files-consumer/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush new file mode 100644 index 0000000000..b54a5b5395 --- /dev/null +++ b/ignite/templates/app/files-consumer/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush @@ -0,0 +1,183 @@ +package cmd + +import ( + "os" + "strings" + + "cosmossdk.io/client/v2/autocli" + clientv2keyring "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/core/address" + "cosmossdk.io/depinject" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + "<%= ModulePath %>/app" +) + +// NewRootCmd creates a new root command for <%= BinaryNamePrefix %>d. It is called once in the main function. +func NewRootCmd() *cobra.Command { + initSDKConfig() + + var ( + txConfigOpts tx.ConfigOptions + autoCliOpts autocli.AppOptions + moduleBasicManager module.BasicManager + clientCtx client.Context + ) + + if err := depinject.Inject( + depinject.Configs(app.AppConfig(), + depinject.Supply( + log.NewNopLogger(), + ), + depinject.Provide( + ProvideClientContext, + ProvideKeyring, + ProvideStakingKeeper, + ), + ), + &txConfigOpts, + &autoCliOpts, + &moduleBasicManager, + &clientCtx, + ); err != nil { + panic(err) + } + + rootCmd := &cobra.Command{ + Use: app.Name + "d", + Short: "Start <%= AppName %> node", + SilenceErrors: true, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + clientCtx = clientCtx.WithCmdContext(cmd.Context()) + clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + clientCtx, err = config.ReadFromClientConfig(clientCtx) + if err != nil { + return err + } + + // This needs to go after ReadFromClientConfig, as that function + // sets the RPC client needed for SIGN_MODE_TEXTUAL. + txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) + txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx) + txConfigWithTextual, err := tx.NewTxConfigWithOptions( + codec.NewProtoCodec(clientCtx.InterfaceRegistry), + txConfigOpts, + ) + if err != nil { + return err + } + + clientCtx = clientCtx.WithTxConfig(txConfigWithTextual) + if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { + return err + } + + if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + customCMTConfig := initCometBFTConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) + }, + } + +<%= if (!IsChainMinimal) { %> + // Since the IBC modules don't support dependency injection, we need to + // manually register the modules on the client side. + // This needs to be removed after IBC supports App Wiring. + ibcModules := app.RegisterIBC(clientCtx.InterfaceRegistry) + for name, mod := range ibcModules { + moduleBasicManager[name] = module.CoreAppModuleBasicAdaptor(name, mod) + autoCliOpts.Modules[name] = mod + } +<% } %> + initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager) + + overwriteFlagDefaults(rootCmd, map[string]string{ + flags.FlagChainID: strings.ReplaceAll(app.Name, "-", ""), + flags.FlagKeyringBackend: "test", + }) + + if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { + panic(err) + } + + return rootCmd +} + +func overwriteFlagDefaults(c *cobra.Command, defaults map[string]string) { + set := func(s *pflag.FlagSet, key, val string) { + if f := s.Lookup(key); f != nil { + f.DefValue = val + _ = f.Value.Set(val) + } + } + for key, val := range defaults { + set(c.Flags(), key, val) + set(c.PersistentFlags(), key, val) + } + for _, c := range c.Commands() { + overwriteFlagDefaults(c, defaults) + } +} + +func ProvideClientContext( + appCodec codec.Codec, + interfaceRegistry codectypes.InterfaceRegistry, + txConfig client.TxConfig, + legacyAmino *codec.LegacyAmino, +) client.Context { + clientCtx := client.Context{}. + WithCodec(appCodec). + WithInterfaceRegistry(interfaceRegistry). + WithTxConfig(txConfig). + WithLegacyAmino(legacyAmino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(app.DefaultNodeHome). + WithViper(app.Name) // env variable prefix + + // Read the config again to overwrite the default values with the values from the config file + clientCtx, _ = config.ReadFromClientConfig(clientCtx) + + return clientCtx +} + +func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clientv2keyring.Keyring, error) { + kb, err := client.NewKeyringFromBackend(clientCtx, clientCtx.Keyring.Backend()) + if err != nil { + return nil, err + } + + return keyring.NewAutoCLIKeyring(kb) +} + +// This is a hack for root.go, needs to be reverted eventually. +func ProvideStakingKeeper() stakingkeeper.Keeper { + return stakingkeeper.Keeper{} +} \ No newline at end of file diff --git a/ignite/templates/app/files/app/app.go.plush b/ignite/templates/app/files/app/app.go.plush index 2673a020f2..a7b997459e 100644 --- a/ignite/templates/app/files/app/app.go.plush +++ b/ignite/templates/app/files/app/app.go.plush @@ -45,7 +45,6 @@ import ( "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" - testdata_pulsar "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/auth" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" diff --git a/ignite/templates/app/files/app/ibc.go.plush b/ignite/templates/app/files/app/ibc.go.plush index 9e255b0cab..9d46ba8cbc 100644 --- a/ignite/templates/app/files/app/ibc.go.plush +++ b/ignite/templates/app/files/app/ibc.go.plush @@ -172,8 +172,8 @@ func (app *App) registerIBCModules(appOpts servertypes.AppOptions) error { ibcfee.NewAppModule(app.IBCFeeKeeper), icamodule.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), capability.NewAppModule(app.appCodec, *app.CapabilityKeeper, false), - ibctm.AppModule{}, - solomachine.AppModule{}, + ibctm.NewAppModule(), + solomachine.NewAppModule(), ); err != nil { return err } diff --git a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush index 8bb54e2f73..685899a07d 100644 --- a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush +++ b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush @@ -31,8 +31,6 @@ import ( func initRootCmd( rootCmd *cobra.Command, txConfig client.TxConfig, - interfaceRegistry codectypes.InterfaceRegistry, - appCodec codec.Codec, basicManager module.BasicManager, ) { rootCmd.AddCommand( diff --git a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush index 788a5eabc7..f49f6a3050 100644 --- a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush +++ b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/root.go.plush @@ -114,7 +114,7 @@ func NewRootCmd() *cobra.Command { autoCliOpts.Modules[name] = mod } <% } %> - initRootCmd(rootCmd, clientCtx.TxConfig, clientCtx.InterfaceRegistry, clientCtx.Codec, moduleBasicManager) + initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager) overwriteFlagDefaults(rootCmd, map[string]string{ flags.FlagChainID: strings.ReplaceAll(app.Name, "-", ""), diff --git a/ignite/templates/app/files/config.yml b/ignite/templates/app/files/config.yml.plush similarity index 75% rename from ignite/templates/app/files/config.yml rename to ignite/templates/app/files/config.yml.plush index 868e0e1f31..7a0bcb8594 100644 --- a/ignite/templates/app/files/config.yml +++ b/ignite/templates/app/files/config.yml.plush @@ -1,5 +1,6 @@ version: 1 -accounts: +validation: <%= if (IsConsumerChain) { %>consumer<% } else { %>sovereign<% } %> +accounts: - name: alice coins: - 20000token diff --git a/ignite/templates/app/files/go.mod.plush b/ignite/templates/app/files/go.mod.plush index 81a1bf39b4..db5e037b8b 100644 --- a/ignite/templates/app/files/go.mod.plush +++ b/ignite/templates/app/files/go.mod.plush @@ -23,6 +23,9 @@ require ( cosmossdk.io/x/feegrant v0.1.0 cosmossdk.io/x/nft v0.1.0 cosmossdk.io/x/upgrade v0.1.1 + <%= if (IsConsumerChain) { %> + github.com/cosmos/interchain-security/v3 v3.2.0-consumer-rc0.0.20231123140529-1819e73f6197 + <% } %> github.com/bufbuild/buf v1.30.0 github.com/cometbft/cometbft v0.38.5 github.com/cosmos/cosmos-db v1.0.2 diff --git a/ignite/templates/app/options.go b/ignite/templates/app/options.go index 0176c02db1..c60b966de5 100644 --- a/ignite/templates/app/options.go +++ b/ignite/templates/app/options.go @@ -11,6 +11,7 @@ type Options struct { // IncludePrefixes is used to filter the files to include from the generator IncludePrefixes []string IsChainMinimal bool + IsConsumerChain bool } // Validate that options are usable. diff --git a/integration/app/cmd_app_test.go b/integration/app/cmd_app_test.go index 58abdabd2a..373e9f6116 100644 --- a/integration/app/cmd_app_test.go +++ b/integration/app/cmd_app_test.go @@ -211,3 +211,15 @@ func TestGenerateAppWithEmptyModule(t *testing.T) { app.EnsureSteady() } + +func TestGenerateAConsumerApp(t *testing.T) { + var ( + env = envtest.New(t) + app = env.Scaffold("github.com/test/blog", "--consumer") + ) + + _, statErr := os.Stat(filepath.Join(app.SourcePath(), "x", "blog")) + require.False(t, os.IsNotExist(statErr), "the default module should be scaffolded") + + app.EnsureSteady() +}