From 50f81b173b96fa0ef0000f28dea04512ad1fa0db Mon Sep 17 00:00:00 2001 From: SaReN Date: Mon, 20 Apr 2020 18:47:21 +0530 Subject: [PATCH] Add simd and simcli commands (#5998) Reference: #5951 --- Makefile | 14 ++- simapp/cmd/simcli/main.go | 168 ++++++++++++++++++++++++++++++++ simapp/cmd/simd/genaccounts.go | 169 +++++++++++++++++++++++++++++++++ simapp/cmd/simd/main.go | 113 ++++++++++++++++++++++ 4 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 simapp/cmd/simcli/main.go create mode 100644 simapp/cmd/simd/genaccounts.go create mode 100644 simapp/cmd/simd/main.go diff --git a/Makefile b/Makefile index 25ef7ea37..9a1b851bb 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,19 @@ all: tools build lint test build: go.sum @go build -mod=readonly ./... -.PHONY: build + +build-sim: go.sum +ifeq ($(OS),Windows_NT) + go build -mod=readonly $(BUILD_FLAGS) -o build/simd.exe ./simapp/cmd/simd + go build -mod=readonly $(BUILD_FLAGS) -o build/simcli.exe ./simapp/cmd/simcli +else + go build -mod=readonly $(BUILD_FLAGS) -o build/simd ./simapp/cmd/simd + go build -mod=readonly $(BUILD_FLAGS) -o build/simcli ./simapp/cmd/simcli +endif + +.PHONY: \ + build \ + build-sim mocks: $(MOCKS_DIR) mockgen -source=x/auth/types/account_retriever.go -package mocks -destination tests/mocks/account_retriever.go diff --git a/simapp/cmd/simcli/main.go b/simapp/cmd/simcli/main.go new file mode 100644 index 000000000..00f0ab231 --- /dev/null +++ b/simapp/cmd/simcli/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "fmt" + "os" + "path" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + codecstd "github.com/cosmos/cosmos-sdk/codec/std" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" +) + +var ( + cdc = codecstd.MakeCodec(simapp.ModuleBasics) + appCodec = codecstd.NewAppCodec(cdc) +) + +func init() { + authclient.Codec = appCodec +} + +func main() { + // Configure cobra to sort commands + cobra.EnableCommandSorting = false + + // Read in the configuration file for the sdk + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + // TODO: setup keybase, viper object, etc. to be passed into + // the below functions and eliminate global vars, like we do + // with the cdc + + rootCmd := &cobra.Command{ + Use: "simcli", + Short: "Command line interface for interacting with simd", + } + + // Add --chain-id to persistent flags and mark it required + rootCmd.PersistentFlags().String(flags.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { + return initConfig(rootCmd) + } + + // Construct Root Command + rootCmd.AddCommand( + rpc.StatusCommand(), + client.ConfigCmd(simapp.DefaultCLIHome), + queryCmd(cdc), + txCmd(cdc), + flags.LineBreak, + lcd.ServeCommand(cdc, registerRoutes), + flags.LineBreak, + keys.Commands(), + flags.LineBreak, + flags.NewCompletionCmd(rootCmd, true), + ) + + // Add flags and prefix all env exposed with GA + executor := cli.PrepareMainCmd(rootCmd, "GA", simapp.DefaultCLIHome) + + err := executor.Execute() + if err != nil { + fmt.Printf("Failed executing CLI command: %s, exiting...\n", err) + os.Exit(1) + } +} + +func queryCmd(cdc *amino.Codec) *cobra.Command { + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + authcmd.GetAccountCmd(cdc), + flags.LineBreak, + rpc.ValidatorCommand(cdc), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(cdc), + authcmd.QueryTxCmd(cdc), + flags.LineBreak, + ) + + // add modules' query commands + simapp.ModuleBasics.AddQueryCommands(queryCmd, cdc) + + return queryCmd +} + +func txCmd(cdc *amino.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + bankcmd.SendTxCmd(cdc), + flags.LineBreak, + authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), + flags.LineBreak, + authcmd.GetBroadcastCommand(cdc), + authcmd.GetEncodeCommand(cdc), + authcmd.GetDecodeCommand(cdc), + flags.LineBreak, + ) + + // add modules' tx commands + simapp.ModuleBasics.AddTxCommands(txCmd, cdc) + + return txCmd +} + +// registerRoutes registers the routes from the different modules for the REST client. +// NOTE: details on the routes added for each module are in the module documentation +func registerRoutes(rs *lcd.RestServer) { + client.RegisterRoutes(rs.CliCtx, rs.Mux) + authrest.RegisterTxRoutes(rs.CliCtx, rs.Mux) + simapp.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux) +} + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + if err := viper.BindPFlag(flags.FlagChainID, cmd.PersistentFlags().Lookup(flags.FlagChainID)); err != nil { + return err + } + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/simapp/cmd/simd/genaccounts.go b/simapp/cmd/simd/genaccounts.go new file mode 100644 index 000000000..a4085ca01 --- /dev/null +++ b/simapp/cmd/simd/genaccounts.go @@ -0,0 +1,169 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/client/flags" + codecstd "github.com/cosmos/cosmos-sdk/codec/std" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/genutil" +) + +const ( + flagClientHome = "home-client" + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" +) + +// AddGenesisAccountCmd returns add-genesis-account cobra Command. +func AddGenesisAccountCmd( + ctx *server.Context, depCdc *amino.Codec, cdc *codecstd.Codec, defaultNodeHome, defaultClientHome string, +) *cobra.Command { + + cmd := &cobra.Command{ + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", + Short: "Add a genesis account to genesis.json", + Long: `Add a genesis account to genesis.json. The provided account must specify +the account address or key name and a list of initial coins. If a key name is given, +the address will be looked up in the local Keybase. The list of initial tokens must +contain valid denominations. Accounts may optionally be supplied with vesting parameters. +`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + + addr, err := sdk.AccAddressFromBech32(args[0]) + inBuf := bufio.NewReader(cmd.InOrStdin()) + if err != nil { + // attempt to lookup address from Keybase if no address was provided + kb, err := keyring.New( + sdk.KeyringServiceName(), + viper.GetString(flags.FlagKeyringBackend), + viper.GetString(flagClientHome), + inBuf, + ) + if err != nil { + return err + } + + info, err := kb.Key(args[0]) + if err != nil { + return fmt.Errorf("failed to get address from Keybase: %w", err) + } + + addr = info.GetAddress() + } + + coins, err := sdk.ParseCoins(args[1]) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + + vestingStart := viper.GetInt64(flagVestingStart) + vestingEnd := viper.GetInt64(flagVestingEnd) + vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt)) + if err != nil { + return fmt.Errorf("failed to parse vesting amount: %w", err) + } + + // create concrete account type based on input parameters + var genAccount authexported.GenesisAccount + + balances := bank.Balance{Address: addr, Coins: coins.Sort()} + baseAccount := auth.NewBaseAccount(addr, nil, 0, 0) + if !vestingAmt.IsZero() { + baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + + if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || + baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + + switch { + case vestingStart != 0 && vestingEnd != 0: + genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) + + case vestingEnd != 0: + genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) + + default: + return errors.New("invalid vesting parameters; must supply start and end time or end time") + } + } else { + genAccount = baseAccount + } + + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) + } + + genFile := config.GenesisFile() + appState, genDoc, err := genutil.GenesisStateFromGenFile(depCdc, genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := auth.GetGenesisStateFromAppState(cdc, appState) + + if authGenState.Accounts.Contains(addr) { + return fmt.Errorf("cannot add account at existing address %s", addr) + } + + // Add the new account to the set of genesis accounts and sanitize the + // accounts afterwards. + authGenState.Accounts = append(authGenState.Accounts, genAccount) + authGenState.Accounts = auth.SanitizeGenesisAccounts(authGenState.Accounts) + + authGenStateBz, err := cdc.MarshalJSON(authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) + } + + appState[auth.ModuleName] = authGenStateBz + + bankGenState := bank.GetGenesisStateFromAppState(depCdc, appState) + bankGenState.Balances = append(bankGenState.Balances, balances) + bankGenState.Balances = bank.SanitizeGenesisBalances(bankGenState.Balances) + + bankGenStateBz, err := cdc.MarshalJSON(bankGenState) + if err != nil { + return fmt.Errorf("failed to marshal bank genesis state: %w", err) + } + + appState[bank.ModuleName] = bankGenStateBz + + appStateJSON, err := cdc.MarshalJSON(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory") + cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)") + cmd.Flags().String(flagClientHome, defaultClientHome, "client's home directory") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + + return cmd +} diff --git a/simapp/cmd/simd/main.go b/simapp/cmd/simd/main.go new file mode 100644 index 000000000..ab9278cf5 --- /dev/null +++ b/simapp/cmd/simd/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "encoding/json" + "io" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + codecstd "github.com/cosmos/cosmos-sdk/codec/std" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const flagInvCheckPeriod = "inv-check-period" + +var invCheckPeriod uint + +func main() { + cdc := codecstd.MakeCodec(simapp.ModuleBasics) + appCodec := codecstd.NewAppCodec(cdc) + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + ctx := server.NewDefaultContext() + cobra.EnableCommandSorting = false + rootCmd := &cobra.Command{ + Use: "simd", + Short: "Simulation Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + + rootCmd.AddCommand( + genutilcli.InitCmd(ctx, cdc, simapp.ModuleBasics, simapp.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(ctx, cdc, bank.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.MigrateGenesisCmd(ctx, cdc), + genutilcli.GenTxCmd( + ctx, cdc, simapp.ModuleBasics, staking.AppModuleBasic{}, + bank.GenesisBalancesIterator{}, simapp.DefaultNodeHome, simapp.DefaultCLIHome, + ), + genutilcli.ValidateGenesisCmd(ctx, cdc, simapp.ModuleBasics), + AddGenesisAccountCmd(ctx, cdc, appCodec, simapp.DefaultNodeHome, simapp.DefaultCLIHome), + flags.NewCompletionCmd(rootCmd, true), + debug.Cmd(cdc)) + + server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) + + // prepare and add flags + executor := cli.PrepareBaseCmd(rootCmd, "GA", simapp.DefaultNodeHome) + rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod, + 0, "Assert registered invariants every N blocks") + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { + var cache sdk.MultiStorePersistentCache + + if viper.GetBool(server.FlagInterBlockCache) { + cache = store.NewCommitKVStoreCacheManager() + } + + skipUpgradeHeights := make(map[int64]bool) + for _, h := range viper.GetIntSlice(server.FlagUnsafeSkipUpgrades) { + skipUpgradeHeights[int64(h)] = true + } + + return simapp.NewSimApp( + logger, db, traceStore, true, skipUpgradeHeights, + viper.GetString(flags.FlagHome), invCheckPeriod, + baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))), + baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), + baseapp.SetHaltHeight(viper.GetUint64(server.FlagHaltHeight)), + baseapp.SetHaltTime(viper.GetUint64(server.FlagHaltTime)), + baseapp.SetInterBlockCache(cache), + ) +} + +func exportAppStateAndTMValidators( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, +) (json.RawMessage, []tmtypes.GenesisValidator, *abci.ConsensusParams, error) { + + var simApp *simapp.SimApp + if height != -1 { + simApp = simapp.NewSimApp(logger, db, traceStore, false, map[int64]bool{}, "", uint(1)) + err := simApp.LoadHeight(height) + if err != nil { + return nil, nil, nil, err + } + } else { + simApp = simapp.NewSimApp(logger, db, traceStore, true, map[int64]bool{}, "", uint(1)) + } + return simApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) +}