feat: simapp/v2 (#20412)

Co-authored-by: marbar3778 <marbar3778@yahoo.com>
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: Likhita Polavarapu <78951027+likhita-809@users.noreply.github.com>
Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
Matt Kocubinski
2024-06-19 15:18:32 +00:00
committed by GitHub
co-authored by marbar3778 Marko Likhita Polavarapu unknown unknown
parent ca195c1527
commit 2d014b26c2
28 changed files with 3137 additions and 12 deletions
+240
View File
@@ -0,0 +1,240 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
"syscall"
dbm "github.com/cosmos/cosmos-db"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
"cosmossdk.io/client/v2/offchain"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
runtimev2 "cosmossdk.io/runtime/v2"
"cosmossdk.io/server/v2/cometbft"
"cosmossdk.io/simapp/v2"
confixcmd "cosmossdk.io/tools/confix/cmd"
authcmd "cosmossdk.io/x/auth/client/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/debug"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
// TODO migrate all server dependencies to server/v2
"github.com/cosmos/cosmos-sdk/server"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
)
var _ transaction.Codec[transaction.Tx] = &temporaryTxDecoder{}
type temporaryTxDecoder struct {
txConfig client.TxConfig
}
// Decode implements transaction.Codec.
func (t *temporaryTxDecoder) Decode(bz []byte) (transaction.Tx, error) {
return t.txConfig.TxDecoder()(bz)
}
// DecodeJSON implements transaction.Codec.
func (t *temporaryTxDecoder) DecodeJSON(bz []byte) (transaction.Tx, error) {
return t.txConfig.TxJSONDecoder()(bz)
}
func initRootCmd(
rootCmd *cobra.Command,
txConfig client.TxConfig,
_ codectypes.InterfaceRegistry,
_ codec.Codec,
moduleManager *runtimev2.MM,
v1moduleManager *module.Manager,
) {
cfg := sdk.GetConfig()
cfg.Seal()
rootCmd.AddCommand(
genutilcli.InitCmd(moduleManager),
debug.Cmd(),
confixcmd.ConfigCommand(),
startCommand(&temporaryTxDecoder{txConfig}),
// TODO pruning.Cmd(newApp),
// TODO snapshot.Cmd(newApp),
)
// add keybase, auxiliary RPC, query, genesis, and tx child commands
rootCmd.AddCommand(
server.StatusCommand(),
genesisCommand(txConfig, v1moduleManager, appExport),
queryCommand(),
txCommand(),
keys.Commands(),
offchain.OffChain(),
)
}
func startCommand(txCodec transaction.Codec[transaction.Tx]) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Start the application",
RunE: func(cmd *cobra.Command, args []string) error {
serverCtx := server.GetServerContextFromCmd(cmd)
sa := simapp.NewSimApp(serverCtx.Logger, serverCtx.Viper)
am := sa.App.AppManager
serverCfg := cometbft.Config{CmtConfig: serverCtx.Config, ConsensusAuthority: sa.GetConsensusAuthority()}
cometServer := cometbft.NewCometBFTServer[transaction.Tx](
am,
sa.GetStore(),
sa.GetLogger(),
serverCfg,
txCodec,
)
ctx := cmd.Context()
ctx, cancelFn := context.WithCancel(ctx)
g, _ := errgroup.WithContext(ctx)
g.Go(func() error {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigCh
cancelFn()
cmd.Printf("caught %s signal\n", sig.String())
if err := cometServer.Stop(ctx); err != nil {
cmd.PrintErrln("failed to stop servers:", err)
}
return nil
})
if err := cometServer.Start(ctx); err != nil {
return fmt.Errorf("failed to start servers: %w", err)
}
return g.Wait()
},
}
return cmd
}
// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter
func genesisCommand(
txConfig client.TxConfig,
moduleManager *module.Manager,
appExport servertypes.AppExporter,
cmds ...*cobra.Command,
) *cobra.Command {
cmd := genutilcli.Commands(txConfig, moduleManager, appExport)
for _, subCmd := range cmds {
cmd.AddCommand(subCmd)
}
return cmd
}
func queryCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
cmd.AddCommand(
rpc.QueryEventForTxCmd(),
server.QueryBlockCmd(),
authcmd.QueryTxsByEventsCmd(),
server.QueryBlocksCmd(),
authcmd.QueryTxCmd(),
server.QueryBlockResultsCmd(),
)
return cmd
}
func txCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tx",
Short: "Transactions subcommands",
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
cmd.AddCommand(
authcmd.GetSignCommand(),
authcmd.GetSignBatchCommand(),
authcmd.GetMultiSignCommand(),
authcmd.GetMultiSignBatchCmd(),
authcmd.GetValidateSignaturesCommand(),
authcmd.GetBroadcastCommand(),
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetSimulateCmd(),
)
return cmd
}
// appExport creates a new simapp (optionally at a given height) and exports state.
func appExport(
logger log.Logger,
_ dbm.DB,
_ io.Writer,
height int64,
forZeroHeight bool,
jailAllowedAddrs []string,
appOpts servertypes.AppOptions,
modulesToExport []string,
) (servertypes.ExportedApp, error) {
// this check is necessary as we use the flag in x/upgrade.
// we can exit more gracefully by checking the flag here.
homePath, ok := appOpts.Get(flags.FlagHome).(string)
if !ok || homePath == "" {
return servertypes.ExportedApp{}, errors.New("application home not set")
}
viperAppOpts, ok := appOpts.(*viper.Viper)
if !ok {
return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper")
}
// overwrite the FlagInvCheckPeriod
viperAppOpts.Set(server.FlagInvCheckPeriod, 1)
appOpts = viperAppOpts
var simApp *simapp.SimApp
if height != -1 {
simApp = simapp.NewSimApp(logger, appOpts)
if err := simApp.LoadHeight(uint64(height)); err != nil {
return servertypes.ExportedApp{}, err
}
} else {
simApp = simapp.NewSimApp(logger, appOpts)
}
return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport)
}
var tempDir = func() string {
dir, err := os.MkdirTemp("", "simapp")
if err != nil {
dir = simapp.DefaultNodeHome
}
defer os.RemoveAll(dir)
return dir
}
+126
View File
@@ -0,0 +1,126 @@
package cmd
import (
"strings"
cmtcfg "github.com/cometbft/cometbft/config"
clientconfig "github.com/cosmos/cosmos-sdk/client/config"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
)
// initCometBFTConfig helps to override default CometBFT Config values.
// return cmtcfg.DefaultConfig if no custom configuration is required for the application.
func initCometBFTConfig() *cmtcfg.Config {
cfg := cmtcfg.DefaultConfig()
// only display only error logs by default except for p2p and state
cfg.LogLevel = "*:error,p2p:info,state:info"
// these values put a higher strain on node memory
// cfg.P2P.MaxNumInboundPeers = 100
// cfg.P2P.MaxNumOutboundPeers = 40
return cfg
}
// initAppConfig helps to override default client config template and configs.
// return "", nil if no custom configuration is required for the application.
func initClientConfig() (string, interface{}) {
type GasConfig struct {
GasAdjustment float64 `mapstructure:"gas-adjustment"`
}
type CustomClientConfig struct {
clientconfig.Config `mapstructure:",squash"`
GasConfig GasConfig `mapstructure:"gas"`
}
// Optionally allow the chain developer to overwrite the SDK's default client config.
clientCfg := clientconfig.DefaultConfig()
// The SDK's default keyring backend is set to "os".
// This is more secure than "test" and is the recommended value.
//
// In simapp, we set the default keyring backend to test, as SimApp is meant
// to be an example and testing application.
clientCfg.KeyringBackend = keyring.BackendTest
// Now we set the custom config default values.
customClientConfig := CustomClientConfig{
Config: *clientCfg,
GasConfig: GasConfig{
GasAdjustment: 1.5,
},
}
// The default SDK app template is defined in serverconfig.DefaultConfigTemplate.
// We append the custom config template to the default one.
// And we set the default config to the custom app template.
customClientConfigTemplate := clientconfig.DefaultClientConfigTemplate + strings.TrimSpace(`
# This is default the gas adjustment factor used in tx commands.
# It can be overwritten by the --gas-adjustment flag in each tx command.
gas-adjustment = {{ .GasConfig.GasAdjustment }}
`)
return customClientConfigTemplate, customClientConfig
}
// initAppConfig helps to override default appConfig template and configs.
// return "", nil if no custom configuration is required for the application.
func initAppConfig() (string, interface{}) {
// The following code snippet is just for reference.
// CustomConfig defines an arbitrary custom config to extend app.toml.
// If you don't need it, you can remove it.
// If you wish to add fields that correspond to flags that aren't in the SDK server config,
// this custom config can as well help.
type CustomConfig struct {
CustomField string `mapstructure:"custom-field"`
}
type CustomAppConfig struct {
serverconfig.Config `mapstructure:",squash"`
Custom CustomConfig `mapstructure:"custom"`
}
// Optionally allow the chain developer to overwrite the SDK's default
// server config.
srvCfg := serverconfig.DefaultConfig()
// The SDK's default minimum gas price is set to "" (empty value) inside
// app.toml. If left empty by validators, the node will halt on startup.
// However, the chain developer can set a default app.toml value for their
// validators here.
//
// In summary:
// - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their
// own app.toml config,
// - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their
// own app.toml to override, or use this default value.
//
// In simapp, we set the min gas prices to 0.
srvCfg.MinGasPrices = "0stake"
// srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default
// Now we set the custom config default values.
customAppConfig := CustomAppConfig{
Config: *srvCfg,
Custom: CustomConfig{
CustomField: "anything",
},
}
// The default SDK app template is defined in serverconfig.DefaultConfigTemplate.
// We append the custom config template to the default one.
// And we set the default config to the custom app template.
customAppTemplate := serverconfig.DefaultConfigTemplate + `
[custom]
# That field will be parsed by server.InterceptConfigsPreRunHandler and held by viper.
# Do not forget to add quotes around the value if it is a string.
custom-field = "{{ .Custom.CustomField }}"`
return customAppTemplate, customAppConfig
}
+174
View File
@@ -0,0 +1,174 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
"cosmossdk.io/client/v2/autocli"
clientv2keyring "cosmossdk.io/client/v2/autocli/keyring"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/legacy"
"cosmossdk.io/depinject"
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2"
"cosmossdk.io/simapp/v2"
"cosmossdk.io/x/auth/tx"
authtxconfig "cosmossdk.io/x/auth/tx/config"
"cosmossdk.io/x/auth/types"
"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/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server" // TODO: remove me: https://github.com/cosmos/cosmos-sdk/pull/20412/files#r1622878528
"github.com/cosmos/cosmos-sdk/std"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/cosmos/cosmos-sdk/types/module"
)
// NewRootCmd creates a new root command for simd. It is called once in the main function.
func NewRootCmd() *cobra.Command {
var (
autoCliOpts autocli.AppOptions
moduleManager *runtime.MM
v1ModuleManager *module.Manager
clientCtx client.Context
)
if err := depinject.Inject(
depinject.Configs(
simapp.AppConfig(),
depinject.Supply(
log.NewNopLogger(),
simtestutil.NewAppOptionsWithFlagHome(tempDir()),
),
depinject.Provide(
codec.ProvideInterfaceRegistry,
codec.ProvideAddressCodec,
codec.ProvideProtoCodec,
codec.ProvideLegacyAmino,
ProvideClientContext,
ProvideKeyring,
ProvideV1ModuleManager,
),
depinject.Invoke(
std.RegisterInterfaces,
std.RegisterLegacyAminoCodec,
),
),
&autoCliOpts,
&moduleManager,
&v1ModuleManager,
&clientCtx,
); err != nil {
panic(err)
}
rootCmd := &cobra.Command{
Use: "simd",
Short: "simulation app",
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()).WithViper("")
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
}
customAppTemplate, customAppConfig := initAppConfig()
customCMTConfig := initCometBFTConfig()
return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig)
},
}
initRootCmd(
rootCmd,
clientCtx.TxConfig,
clientCtx.InterfaceRegistry,
clientCtx.Codec,
moduleManager,
v1ModuleManager)
if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil {
panic(err)
}
return rootCmd
}
func ProvideClientContext(
appCodec codec.Codec,
interfaceRegistry codectypes.InterfaceRegistry,
txConfigOpts tx.ConfigOptions,
legacyAmino legacy.Amino,
addressCodec address.Codec,
validatorAddressCodec address.ValidatorAddressCodec,
consensusAddressCodec address.ConsensusAddressCodec,
) client.Context {
var err error
amino, ok := legacyAmino.(*codec.LegacyAmino)
if !ok {
panic("legacy.Amino 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.ReadDefaultValuesFromDefaultClientConfig(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
}
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)
}
func ProvideV1ModuleManager(modules map[string]appmodule.AppModule) *module.Manager {
return module.NewManagerFromMap(modules)
}
+43
View File
@@ -0,0 +1,43 @@
package cmd_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/simapp/v2"
"cosmossdk.io/simapp/v2/simdv2/cmd"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
)
func TestInitCmd(t *testing.T) {
rootCmd := cmd.NewRootCmd()
rootCmd.SetArgs([]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))
}
func TestHomeFlagRegistration(t *testing.T) {
homeDir := "/tmp/foo"
rootCmd := cmd.NewRootCmd()
rootCmd.SetArgs([]string{
"query",
fmt.Sprintf("--%s", flags.FlagHome),
homeDir,
})
require.NoError(t, svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome))
result, err := rootCmd.Flags().GetString(flags.FlagHome)
require.NoError(t, err)
require.Equal(t, result, homeDir)
}
+19
View File
@@ -0,0 +1,19 @@
package main
import (
"fmt"
"os"
"cosmossdk.io/simapp/v2"
"cosmossdk.io/simapp/v2/simdv2/cmd"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
)
func main() {
rootCmd := cmd.NewRootCmd()
if err := svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome); err != nil {
fmt.Fprintln(rootCmd.OutOrStderr(), err)
os.Exit(1)
}
}