From de2082577955ec29ed02208737b59c0f6f992448 Mon Sep 17 00:00:00 2001 From: Sridhar Ganesan Date: Mon, 16 Jul 2018 19:32:56 +0200 Subject: [PATCH] Adding cosmos-sdk-cli - initial version --- cmd/cosmos-sdk-cli/cmd/init.go | 76 ++++++++++ cmd/cosmos-sdk-cli/cmd/root.go | 23 +++ cmd/cosmos-sdk-cli/main.go | 9 ++ cmd/cosmos-sdk-cli/template/Gopkg.toml | 58 ++++++++ cmd/cosmos-sdk-cli/template/Makefile | 23 +++ cmd/cosmos-sdk-cli/template/app/app.go | 144 +++++++++++++++++++ cmd/cosmos-sdk-cli/template/cmd/cli/main.go | 77 ++++++++++ cmd/cosmos-sdk-cli/template/cmd/node/main.go | 68 +++++++++ cmd/cosmos-sdk-cli/template/types/account.go | 78 ++++++++++ 9 files changed, 556 insertions(+) create mode 100644 cmd/cosmos-sdk-cli/cmd/init.go create mode 100644 cmd/cosmos-sdk-cli/cmd/root.go create mode 100644 cmd/cosmos-sdk-cli/main.go create mode 100644 cmd/cosmos-sdk-cli/template/Gopkg.toml create mode 100644 cmd/cosmos-sdk-cli/template/Makefile create mode 100644 cmd/cosmos-sdk-cli/template/app/app.go create mode 100644 cmd/cosmos-sdk-cli/template/cmd/cli/main.go create mode 100644 cmd/cosmos-sdk-cli/template/cmd/node/main.go create mode 100644 cmd/cosmos-sdk-cli/template/types/account.go diff --git a/cmd/cosmos-sdk-cli/cmd/init.go b/cmd/cosmos-sdk-cli/cmd/init.go new file mode 100644 index 0000000000..eb72f6d943 --- /dev/null +++ b/cmd/cosmos-sdk-cli/cmd/init.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "bufio" + "fmt" + "go/build" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/gobuffalo/packr" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(initCmd) +} + +func resolveProjectPath(remoteProjectPath string) string { + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = build.Default.GOPATH + // Use $HOME/go + } + return gopath + string(os.PathSeparator) + "src" + string(os.PathSeparator) + remoteProjectPath +} + +var initCmd = &cobra.Command{ + Use: "init AwesomeProjectName", + Short: "Initialize your new cosmos zone", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("Project name is required") + } + projectName := args[0] + capitalizedProjectName := strings.Title(projectName) + shortProjectName := strings.ToLower(projectName) + reader := bufio.NewReader(os.Stdin) + fmt.Println("Thank you for using cosmos-zone tool.") + fmt.Println("You are only a few steps away from creating your brand new blockchain project on Cosmos.") + fmt.Print("We will ask you a few more questions to guide you through this process\n\n") + fmt.Print("To configure this project we need a remote project path. If you are unsure you can leave this empty. ") + fmt.Print("Remote project path is usually something like github.com/your_user_name/project_name\n") + fmt.Print("Enter remote project path: ") + remoteProjectPath, _ := reader.ReadString('\n') + remoteProjectPath = strings.ToLower(strings.TrimSpace(remoteProjectPath)) + if remoteProjectPath == "" { + remoteProjectPath = strings.ToLower(shortProjectName) + } + projectPath := resolveProjectPath(remoteProjectPath) + fmt.Print("configuring the project in " + projectPath + "\n\n") + time.Sleep(2 * time.Second) + box := packr.NewBox("../template") + var replacer = strings.NewReplacer("_CAPITALIZED_PROJECT_SHORT_NAME_", capitalizedProjectName, "_PROJECT_SHORT_NAME_", shortProjectName, "_REMOTE_PROJECT_PATH_", remoteProjectPath) + box.Walk(func(path string, file packr.File) error { + actualPath := replacer.Replace(path) + fmt.Println("Creating file: " + actualPath) + contents := box.String(path) + contents = replacer.Replace(contents) + lastIndex := strings.LastIndex(actualPath, string(os.PathSeparator)) + rootDir := "" + if lastIndex != -1 { + rootDir = actualPath[0:lastIndex] + } + // Create directory + os.MkdirAll(projectPath+string(os.PathSeparator)+rootDir, os.ModePerm) + filePath := projectPath + string(os.PathSeparator) + actualPath + ioutil.WriteFile(filePath, []byte(contents), os.ModePerm) + return nil + }) + fmt.Println("Initialized a new project at " + projectPath + ". Happy hacking!") + return nil + }, +} diff --git a/cmd/cosmos-sdk-cli/cmd/root.go b/cmd/cosmos-sdk-cli/cmd/root.go new file mode 100644 index 0000000000..c05234701c --- /dev/null +++ b/cmd/cosmos-sdk-cli/cmd/root.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "cosmos-zone", + Short: "Tools to develop on cosmos-sdk", + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/cosmos-sdk-cli/main.go b/cmd/cosmos-sdk-cli/main.go new file mode 100644 index 0000000000..57dcb505b8 --- /dev/null +++ b/cmd/cosmos-sdk-cli/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/cosmos/cosmos-sdk/cmd/cosmos-sdk-cli/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/cmd/cosmos-sdk-cli/template/Gopkg.toml b/cmd/cosmos-sdk-cli/template/Gopkg.toml new file mode 100644 index 0000000000..d4c3b83520 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/Gopkg.toml @@ -0,0 +1,58 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + +[[constraint]] + name = "github.com/pkg/errors" + version = "~0.8.0" + +[[constraint]] + name = "github.com/spf13/cobra" + version = "~0.0.1" + +[[constraint]] + name = "github.com/spf13/viper" + version = "~1.0.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.2.1" + +[[constraint]] + version = "~0.10.0" + source = "github.com/tendermint/go-amino" + name = "github.com/tendermint/go-wire" + +[[constraint]] + version = "~0.20.0" + name = "github.com/cosmos/cosmos-sdk" + +[[override]] + version = "=1.1.0" + name = "github.com/golang/protobuf" + +[prune] + go-tests = true + unused-packages = true diff --git a/cmd/cosmos-sdk-cli/template/Makefile b/cmd/cosmos-sdk-cli/template/Makefile new file mode 100644 index 0000000000..33ef7263b3 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/Makefile @@ -0,0 +1,23 @@ +PACKAGES=$(shell go list ./... | grep -v '/vendor/') +#BUILD_FLAGS = -ldflags "-X _REMOTE_PROJECT_PATH_/version.GitCommit=`git rev-parse --short HEAD`" + +all: get_tools get_vendor_deps build test + +get_tools: + go get github.com/golang/dep/cmd/dep + +build: + go build -o bin/_PROJECT_SHORT_NAME_cli cmd/cli/main.go && go build -o bin/_PROJECT_SHORT_NAME_d cmd/node/main.go + +get_vendor_deps: + @rm -rf vendor/ + @dep ensure + +test: + @go test $(PACKAGES) + +benchmark: + @go test -bench=. $(PACKAGES) + +.PHONY: all build test benchmark + diff --git a/cmd/cosmos-sdk-cli/template/app/app.go b/cmd/cosmos-sdk-cli/template/app/app.go new file mode 100644 index 0000000000..7c2df96f22 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/app/app.go @@ -0,0 +1,144 @@ +package app + +import ( + "encoding/json" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + + "_REMOTE_PROJECT_PATH_/types" +) + +const ( + appName = "_CAPITALIZED_PROJECT_SHORT_NAME_App" +) + +// Extended ABCI application +type _CAPITALIZED_PROJECT_SHORT_NAME_App struct { + *bam.BaseApp + cdc *wire.Codec + + // keys to access the substores + capKeyMainStore *sdk.KVStoreKey + capKeyAccountStore *sdk.KVStoreKey + + // keepers + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + + // Manage getting and setting accounts + accountMapper auth.AccountMapper +} + +func New_CAPITALIZED_PROJECT_SHORT_NAME_App(logger log.Logger, db dbm.DB) *_CAPITALIZED_PROJECT_SHORT_NAME_App { + + // Create app-level codec for txs and accounts. + var cdc = MakeCodec() + + // Create your application object. + var app = &_CAPITALIZED_PROJECT_SHORT_NAME_App{ + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + capKeyMainStore: sdk.NewKVStoreKey("main"), + capKeyAccountStore: sdk.NewKVStoreKey("acc"), + } + + // Define the accountMapper. + app.accountMapper = auth.NewAccountMapper( + cdc, + app.capKeyAccountStore, // target store + types.ProtoAppAccount(), // prototype + ) + + // Add handlers. + app.coinKeeper = bank.NewKeeper(app.accountMapper) + app.Router(). + AddRoute("bank", bank.NewHandler(app.coinKeeper)) + + // Initialize BaseApp. + app.SetInitChainer(app.initChainerFn()) + app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + err := app.LoadLatestVersion(app.capKeyMainStore) + if err != nil { + cmn.Exit(err.Error()) + } + return app +} + +// custom tx codec +func MakeCodec() *wire.Codec { + var cdc = wire.NewCodec() + wire.RegisterCrypto(cdc) // Register crypto. + sdk.RegisterWire(cdc) // Register Msgs + bank.RegisterWire(cdc) + + // Register AppAccount + cdc.RegisterInterface((*auth.Account)(nil), nil) + cdc.RegisterConcrete(&types.AppAccount{}, "_PROJECT_SHORT_NAME_/Account", nil) + + cdc.Seal() + + return cdc +} + +// custom logic for _PROJECT_SHORT_NAME_ initialization +// nolint: unparam +func (app *_CAPITALIZED_PROJECT_SHORT_NAME_App) initChainerFn() sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + + genesisState := new(types.GenesisState) + err := app.cdc.UnmarshalJSON(stateJSON, genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + for _, gacc := range genesisState.Accounts { + acc, err := gacc.ToAppAccount() + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + app.accountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} + } +} + +// Custom logic for state export +func (app *_CAPITALIZED_PROJECT_SHORT_NAME_App) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + ctx := app.NewContext(true, abci.Header{}) + + // iterate to get the accounts + accounts := []*types.GenesisAccount{} + appendAccount := func(acc auth.Account) (stop bool) { + account := &types.GenesisAccount{ + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + } + accounts = append(accounts, account) + return false + } + app.accountMapper.IterateAccounts(ctx, appendAccount) + + genState := types.GenesisState{ + Accounts: accounts, + } + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + return appState, validators, nil +} diff --git a/cmd/cosmos-sdk-cli/template/cmd/cli/main.go b/cmd/cosmos-sdk-cli/template/cmd/cli/main.go new file mode 100644 index 0000000000..8d03bf8255 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/cmd/cli/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" + + "github.com/cosmos/cosmos-sdk/version" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + + "_REMOTE_PROJECT_PATH_/app" + "_REMOTE_PROJECT_PATH_/types" +) + +// rootCmd is the entry point for this binary +var ( + rootCmd = &cobra.Command{ + Use: "_PROJECT_SHORT_NAME_cli", + Short: "_CAPITALIZED_PROJECT_SHORT_NAME_ light-client", + } +) + +func main() { + // disable sorting + cobra.EnableCommandSorting = false + + // get the codec + cdc := app.MakeCodec() + + // TODO: setup keybase, viper object, etc. to be passed into + // the below functions and eliminate global vars, like we do + // with the cdc + + // add standard rpc, and tx commands + rpc.AddCommands(rootCmd) + rootCmd.AddCommand(client.LineBreak) + tx.AddCommands(rootCmd, cdc) + rootCmd.AddCommand(client.LineBreak) + + // add query/post commands (custom to binary) + // start with commands common to basecoin + rootCmd.AddCommand( + client.GetCommands( + authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)), + )...) + rootCmd.AddCommand( + client.PostCommands( + bankcmd.SendTxCmd(cdc), + )...) + // and now _PROJECT_SHORT_NAME_ specific commands here + + // add proxy, version and key info + rootCmd.AddCommand( + client.LineBreak, + lcd.ServeCommand(cdc), + keys.Commands(), + client.LineBreak, + version.VersionCmd, + ) + + // prepare and add flags + executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/._PROJECT_SHORT_NAME_cli")) + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } +} diff --git a/cmd/cosmos-sdk-cli/template/cmd/node/main.go b/cmd/cosmos-sdk-cli/template/cmd/node/main.go new file mode 100644 index 0000000000..edc20449e4 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/cmd/node/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "encoding/json" + "os" + + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "_REMOTE_PROJECT_PATH_/app" + + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/wire" +) + +// init parameters +var _CAPITALIZED_PROJECT_SHORT_NAME_AppInit = server.AppInit{ + AppGenState: _CAPITALIZED_PROJECT_SHORT_NAME_AppGenState, + AppGenTx: server.SimpleAppGenTx, +} + +// GenAppParams sets up the app_state, append any other app specific components. +func _CAPITALIZED_PROJECT_SHORT_NAME_AppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { + appState, err = server.SimpleAppGenState(cdc, appGenTxs) + if err != nil { + return + } + + return +} + +func newApp(logger log.Logger, db dbm.DB) abci.Application { + return app.New_CAPITALIZED_PROJECT_SHORT_NAME_App(logger, db) +} + +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { + dapp := app.New_CAPITALIZED_PROJECT_SHORT_NAME_App(logger, db) + return dapp.ExportAppStateAndValidators() +} + +func main() { + cdc := app.MakeCodec() + ctx := server.NewDefaultContext() + + rootCmd := &cobra.Command{ + Use: "_PROJECT_SHORT_NAME_d", + Short: "_CAPITALIZED_PROJECT_SHORT_NAME_ Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + + server.AddCommands(ctx, cdc, rootCmd, _CAPITALIZED_PROJECT_SHORT_NAME_AppInit, + server.ConstructAppCreator(newApp, "_PROJECT_SHORT_NAME_"), + server.ConstructAppExporter(exportAppStateAndTMValidators, "_PROJECT_SHORT_NAME_")) + + // prepare and add flags + rootDir := os.ExpandEnv("$HOME/._PROJECT_SHORT_NAME_d") + executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) + err := executor.Execute() + if err != nil { + // handle with #870 + panic(err) + } +} diff --git a/cmd/cosmos-sdk-cli/template/types/account.go b/cmd/cosmos-sdk-cli/template/types/account.go new file mode 100644 index 0000000000..d24ac045f3 --- /dev/null +++ b/cmd/cosmos-sdk-cli/template/types/account.go @@ -0,0 +1,78 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + +) + +var _ auth.Account = (*AppAccount)(nil) + +// Custom extensions for this application. This is just an example of +// extending auth.BaseAccount with custom fields. +// +// This is compatible with the stock auth.AccountStore, since +// auth.AccountStore uses the flexible go-amino library. +type AppAccount struct { + auth.BaseAccount + Name string `json:"name"` +} + +// Constructor for AppAccount +func ProtoAppAccount() auth.Account { + return &AppAccount{} +} + +// nolint +func (acc AppAccount) GetName() string { return acc.Name } +func (acc *AppAccount) SetName(name string) { acc.Name = name } + +// Get the AccountDecoder function for the custom AppAccount +func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { + return func(accBytes []byte) (res auth.Account, err error) { + if len(accBytes) == 0 { + return nil, sdk.ErrTxDecode("accBytes are empty") + } + acct := new(AppAccount) + err = cdc.UnmarshalBinaryBare(accBytes, &acct) + if err != nil { + panic(err) + } + return acct, err + } +} + +//___________________________________________________________________________________ + +// State to Unmarshal +type GenesisState struct { + Accounts []*GenesisAccount `json:"accounts"` +} + +// GenesisAccount doesn't need pubkey or sequence +type GenesisAccount struct { + Name string `json:"name"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` +} + +func NewGenesisAccount(aa *AppAccount) *GenesisAccount { + return &GenesisAccount{ + Name: aa.Name, + Address: aa.Address, + Coins: aa.Coins.Sort(), + } +} + +// convert GenesisAccount to AppAccount +func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) { + baseAcc := auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + } + return &AppAccount{ + BaseAccount: baseAcc, + Name: ga.Name, + }, nil +}