From 12c2c236c2d152b2c176a31d7393cbb08b763b64 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Mon, 6 Aug 2018 14:11:30 -0400 Subject: [PATCH] Merge PR #1741: CoreContext Refactor --- Gopkg.lock | 31 +- PENDING.md | 12 + client/context/context.go | 115 ++++++ client/context/errors.go | 13 + client/context/helpers.go | 347 ------------------ client/context/query.go | 311 ++++++++++++++++ client/context/types.go | 113 ------ client/context/viper.go | 141 ------- client/flags.go | 1 - client/keys/utils.go | 51 ++- client/lcd/lcd_test.go | 4 +- client/lcd/root.go | 20 +- client/lcd/version.go | 5 +- client/rpc/block.go | 28 +- client/rpc/root.go | 14 +- client/rpc/status.go | 18 +- client/rpc/validators.go | 25 +- client/tx/broadcast.go | 4 +- client/tx/query.go | 27 +- client/tx/root.go | 6 +- client/tx/search.go | 30 +- client/utils/utils.go | 60 +++ crypto/keys/types.go | 35 +- examples/democoin/x/cool/client/cli/tx.go | 57 ++- examples/democoin/x/pow/client/cli/tx.go | 32 +- .../x/simplestake/client/cli/commands.go | 52 +-- x/auth/client/cli/account.go | 38 +- x/auth/client/context/context.go | 152 ++++++++ x/auth/client/context/utils.go | 25 ++ x/auth/client/rest/query.go | 15 +- x/bank/client/cli/sendtx.go | 47 ++- x/bank/client/rest/sendtx.go | 32 +- x/gov/client/cli/tx.go | 120 +++--- x/gov/client/rest/rest.go | 96 +++-- x/gov/client/rest/util.go | 25 +- x/ibc/client/cli/ibctx.go | 31 +- x/ibc/client/cli/relay.go | 42 ++- x/ibc/client/rest/transfer.go | 32 +- x/slashing/client/cli/query.go | 10 +- x/slashing/client/cli/tx.go | 24 +- x/slashing/client/rest/query.go | 15 +- x/slashing/client/rest/rest.go | 10 +- x/slashing/client/rest/tx.go | 26 +- x/stake/client/cli/query.go | 123 ++++--- x/stake/client/cli/tx.go | 195 ++++++---- x/stake/client/rest/query.go | 37 +- x/stake/client/rest/rest.go | 12 +- x/stake/client/rest/tx.go | 59 ++- 48 files changed, 1522 insertions(+), 1196 deletions(-) create mode 100644 client/context/context.go create mode 100644 client/context/errors.go delete mode 100644 client/context/helpers.go create mode 100644 client/context/query.go delete mode 100644 client/context/types.go delete mode 100644 client/context/viper.go create mode 100644 client/utils/utils.go create mode 100644 x/auth/client/context/context.go create mode 100644 x/auth/client/context/utils.go diff --git a/Gopkg.lock b/Gopkg.lock index e6f1568355..ec32cccba0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -38,7 +38,7 @@ name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898" + revision = "9a2f9524024889e129a5422aca2cff73cb3eabf6" [[projects]] digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" @@ -165,12 +165,13 @@ [[projects]] branch = "master" - digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240" + digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", + "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -231,11 +232,11 @@ [[projects]] branch = "master" - digest = "1:e730597b38a4d56e2361e0b6236cb800e52c73cace2ff91396f4ff35792ddfa7" + digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b" + revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -273,15 +274,15 @@ [[projects]] branch = "master" - digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b" + digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "UT" - revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" + revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" [[projects]] branch = "master" - digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3" + digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -289,11 +290,11 @@ "model", ] pruneopts = "UT" - revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" + revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" [[projects]] branch = "master" - digest = "1:20d9bb50dbee172242f9bcd6ec24a917dd7a5bb17421bf16a79c33111dea7db1" + digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" name = "github.com/prometheus/procfs" packages = [ ".", @@ -302,7 +303,7 @@ "xfs", ] pruneopts = "UT" - revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" + revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" [[projects]] digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c" @@ -514,7 +515,7 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" + revision = "c126467f60eb25f8f27e5a981f32a87e3965053f" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -534,11 +535,11 @@ [[projects]] branch = "master" - digest = "1:d773e525476aefa22ea944a5425a9bfb99819b2e67eeb9b1966454fd57522bbf" + digest = "1:8466957fb2af510f68b13aec64ccad13e9e756dc1da3ea28c422f8ac410e56f0" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" + revision = "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -565,11 +566,11 @@ [[projects]] branch = "master" - digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3" + digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "e92b116572682a5b432ddd840aeaba2a559eeff1" + revision = "daca94659cb50e9f37c1b834680f2e46358f10b0" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" diff --git a/PENDING.md b/PENDING.md index f13f3021e8..9d405a0f66 100644 --- a/PENDING.md +++ b/PENDING.md @@ -27,6 +27,7 @@ BREAKING CHANGES * [x/gov] Governance parameters are now stored in globalparams store * [lcd] \#1866 Updated lcd /slashing/signing_info endpoint to take cosmosvalpub instead of cosmosvaladdr * [types] sdk.NewCoin now takes sdk.Int, sdk.NewInt64Coin takes int64 +* [cli] #1551: Officially removed `--name` from CLI commands * [cli] Genesis/key creation (`init`) now supports user-provided key passwords FEATURES @@ -69,3 +70,14 @@ BUG FIXES * \#1828 Force user to specify amount on create-validator command by removing default * \#1839 Fixed bug where intra-tx counter wasn't set correctly for genesis validators * [tests] \#1675 Fix non-deterministic `test_cover` +* [client] \#1551: Refactored `CoreContext` + * Renamed `CoreContext` to `QueryContext` + * Removed all tx related fields and logic (building & signing) to separate + structure `TxContext` in `x/auth/client/context` + * Cleaned up documentation and API of what used to be `CoreContext` + * Implemented `KeyType` enum for key info + +BUG FIXES +* \#1666 Add intra-tx counter to the genesis validators +* [tests] \#1551: Fixed invalid LCD test JSON payload in `doIBCTransfer` +* \#1787 Fixed bug where Tally fails due to revoked/unbonding validator diff --git a/client/context/context.go b/client/context/context.go new file mode 100644 index 0000000000..1b0443b0c7 --- /dev/null +++ b/client/context/context.go @@ -0,0 +1,115 @@ +package context + +import ( + "io" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/spf13/viper" + + rpcclient "github.com/tendermint/tendermint/rpc/client" +) + +const ctxAccStoreName = "acc" + +// CLIContext implements a typical CLI context created in SDK modules for +// transaction handling and queries. +type CLIContext struct { + Codec *wire.Codec + AccDecoder auth.AccountDecoder + Client rpcclient.Client + Logger io.Writer + Height int64 + NodeURI string + FromAddressName string + AccountStore string + TrustNode bool + UseLedger bool + Async bool + JSON bool + PrintResponse bool +} + +// NewCLIContext returns a new initialized CLIContext with parameters from the +// command line using Viper. +func NewCLIContext() CLIContext { + var rpc rpcclient.Client + + nodeURI := viper.GetString(client.FlagNode) + if nodeURI != "" { + rpc = rpcclient.NewHTTP(nodeURI, "/websocket") + } + + return CLIContext{ + Client: rpc, + NodeURI: nodeURI, + AccountStore: ctxAccStoreName, + FromAddressName: viper.GetString(client.FlagFrom), + Height: viper.GetInt64(client.FlagHeight), + TrustNode: viper.GetBool(client.FlagTrustNode), + UseLedger: viper.GetBool(client.FlagUseLedger), + Async: viper.GetBool(client.FlagAsync), + JSON: viper.GetBool(client.FlagJson), + PrintResponse: viper.GetBool(client.FlagPrintResponse), + } +} + +// WithCodec returns a copy of the context with an updated codec. +func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext { + ctx.Codec = cdc + return ctx +} + +// WithAccountDecoder returns a copy of the context with an updated account +// decoder. +func (ctx CLIContext) WithAccountDecoder(decoder auth.AccountDecoder) CLIContext { + ctx.AccDecoder = decoder + return ctx +} + +// WithLogger returns a copy of the context with an updated logger. +func (ctx CLIContext) WithLogger(w io.Writer) CLIContext { + ctx.Logger = w + return ctx +} + +// WithAccountStore returns a copy of the context with an updated AccountStore. +func (ctx CLIContext) WithAccountStore(accountStore string) CLIContext { + ctx.AccountStore = accountStore + return ctx +} + +// WithFromAddressName returns a copy of the context with an updated from +// address. +func (ctx CLIContext) WithFromAddressName(addrName string) CLIContext { + ctx.FromAddressName = addrName + return ctx +} + +// WithTrustNode returns a copy of the context with an updated TrustNode flag. +func (ctx CLIContext) WithTrustNode(trustNode bool) CLIContext { + ctx.TrustNode = trustNode + return ctx +} + +// WithNodeURI returns a copy of the context with an updated node URI. +func (ctx CLIContext) WithNodeURI(nodeURI string) CLIContext { + ctx.NodeURI = nodeURI + ctx.Client = rpcclient.NewHTTP(nodeURI, "/websocket") + return ctx +} + +// WithClient returns a copy of the context with an updated RPC client +// instance. +func (ctx CLIContext) WithClient(client rpcclient.Client) CLIContext { + ctx.Client = client + return ctx +} + +// WithUseLedger returns a copy of the context with an updated UseLedger flag. +func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { + ctx.UseLedger = useLedger + return ctx +} diff --git a/client/context/errors.go b/client/context/errors.go new file mode 100644 index 0000000000..9c611494a5 --- /dev/null +++ b/client/context/errors.go @@ -0,0 +1,13 @@ +package context + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" +) + +// ErrInvalidAccount returns a standardized error reflecting that a given +// account address does not exist. +func ErrInvalidAccount(addr sdk.AccAddress) error { + return errors.Errorf(`No account with address %s was found in the state. +Are you sure there has been a transaction involving it?`, addr) +} diff --git a/client/context/helpers.go b/client/context/helpers.go deleted file mode 100644 index 7742dfe03a..0000000000 --- a/client/context/helpers.go +++ /dev/null @@ -1,347 +0,0 @@ -package context - -import ( - "fmt" - - "github.com/tendermint/tendermint/libs/common" - - "github.com/pkg/errors" - - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" - cmn "github.com/tendermint/tendermint/libs/common" - rpcclient "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Broadcast the transaction bytes to Tendermint -func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { - - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxCommit(tx) - if err != nil { - return res, err - } - - if res.CheckTx.Code != uint32(0) { - return res, errors.Errorf("checkTx failed: (%d) %s", - res.CheckTx.Code, - res.CheckTx.Log) - } - if res.DeliverTx.Code != uint32(0) { - return res, errors.Errorf("deliverTx failed: (%d) %s", - res.DeliverTx.Code, - res.DeliverTx.Log) - } - return res, err -} - -// Broadcast the transaction bytes to Tendermint -func (ctx CoreContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { - - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxAsync(tx) - if err != nil { - return res, err - } - - return res, err -} - -// Query information about the connected node -func (ctx CoreContext) Query(path string) (res []byte, err error) { - return ctx.query(path, nil) -} - -// QueryStore from Tendermint with the provided key and storename -func (ctx CoreContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) { - return ctx.queryStore(key, storeName, "key") -} - -// Query from Tendermint with the provided storename and subspace -func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) { - resRaw, err := ctx.queryStore(subspace, storeName, "subspace") - if err != nil { - return res, err - } - cdc.MustUnmarshalBinary(resRaw, &res) - return -} - -// Query from Tendermint with the provided storename and path -func (ctx CoreContext) query(path string, key common.HexBytes) (res []byte, err error) { - node, err := ctx.GetNode() - if err != nil { - return res, err - } - - opts := rpcclient.ABCIQueryOptions{ - Height: ctx.Height, - Trusted: ctx.TrustNode, - } - result, err := node.ABCIQueryWithOptions(path, key, opts) - if err != nil { - return res, err - } - resp := result.Response - if resp.Code != uint32(0) { - return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) - } - return resp.Value, nil -} - -// Query from Tendermint with the provided storename and path -func (ctx CoreContext) queryStore(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { - path := fmt.Sprintf("/store/%s/%s", storeName, endPath) - return ctx.query(path, key) -} - -// Get the from address from the name flag -func (ctx CoreContext) GetFromAddress() (from sdk.AccAddress, err error) { - - keybase, err := keys.GetKeyBase() - if err != nil { - return nil, err - } - - name := ctx.FromAddressName - if name == "" { - return nil, errors.Errorf("must provide a from address name") - } - - info, err := keybase.Get(name) - if err != nil { - return nil, errors.Errorf("no key for: %s", name) - } - - return sdk.AccAddress(info.GetPubKey().Address()), nil -} - -// sign and build the transaction from the msg -func (ctx CoreContext) SignAndBuild(name, passphrase string, msgs []sdk.Msg, cdc *wire.Codec) ([]byte, error) { - - // build the Sign Messsage from the Standard Message - chainID := ctx.ChainID - if chainID == "" { - return nil, errors.Errorf("chain ID required but not specified") - } - accnum := ctx.AccountNumber - sequence := ctx.Sequence - memo := ctx.Memo - - fee := sdk.Coin{} - if ctx.Fee != "" { - parsedFee, err := sdk.ParseCoin(ctx.Fee) - if err != nil { - return nil, err - } - fee = parsedFee - } - - signMsg := auth.StdSignMsg{ - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - Msgs: msgs, - Memo: memo, - Fee: auth.NewStdFee(ctx.Gas, fee), // TODO run simulate to estimate gas? - } - - keybase, err := keys.GetKeyBase() - if err != nil { - return nil, err - } - - // sign and build - bz := signMsg.Bytes() - - sig, pubkey, err := keybase.Sign(name, passphrase, bz) - if err != nil { - return nil, err - } - sigs := []auth.StdSignature{{ - PubKey: pubkey, - Signature: sig, - AccountNumber: accnum, - Sequence: sequence, - }} - - // marshal bytes - tx := auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, memo) - - return cdc.MarshalBinary(tx) -} - -// sign and build the transaction from the msg -func (ctx CoreContext) ensureSignBuild(name string, msgs []sdk.Msg, cdc *wire.Codec) (tyBytes []byte, err error) { - err = EnsureAccountExists(ctx, name) - if err != nil { - return nil, err - } - - ctx, err = EnsureAccountNumber(ctx) - if err != nil { - return nil, err - } - // default to next sequence number if none provided - ctx, err = EnsureSequence(ctx) - if err != nil { - return nil, err - } - - var txBytes []byte - - keybase, err := keys.GetKeyBase() - if err != nil { - return nil, err - } - - info, err := keybase.Get(name) - if err != nil { - return nil, err - } - var passphrase string - // Only need a passphrase for locally-stored keys - if info.GetType() == "local" { - passphrase, err = ctx.GetPassphraseFromStdin(name) - if err != nil { - return nil, fmt.Errorf("Error fetching passphrase: %v", err) - } - } - txBytes, err = ctx.SignAndBuild(name, passphrase, msgs, cdc) - if err != nil { - return nil, fmt.Errorf("Error signing transaction: %v", err) - } - - return txBytes, err -} - -// sign and build the transaction from the msg -func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (err error) { - - txBytes, err := ctx.ensureSignBuild(name, msgs, cdc) - if err != nil { - return err - } - - if ctx.Async { - res, err := ctx.BroadcastTxAsync(txBytes) - if err != nil { - return err - } - if ctx.JSON { - type toJSON struct { - TxHash string - } - valueToJSON := toJSON{res.Hash.String()} - JSON, err := cdc.MarshalJSON(valueToJSON) - if err != nil { - return err - } - fmt.Println(string(JSON)) - } else { - fmt.Println("Async tx sent. tx hash: ", res.Hash.String()) - } - return nil - } - res, err := ctx.BroadcastTx(txBytes) - if err != nil { - return err - } - if ctx.JSON { - // Since JSON is intended for automated scripts, always include response in JSON mode - type toJSON struct { - Height int64 - TxHash string - Response string - } - valueToJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)} - JSON, err := cdc.MarshalJSON(valueToJSON) - if err != nil { - return err - } - fmt.Println(string(JSON)) - return nil - } - if ctx.PrintResponse { - fmt.Printf("Committed at block %d. Hash: %s Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx) - } else { - fmt.Printf("Committed at block %d. Hash: %s \n", res.Height, res.Hash.String()) - } - return nil -} - -// get the next sequence for the account address -func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) { - if ctx.Decoder == nil { - return 0, errors.New("accountDecoder required but not provided") - } - - res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) - if err != nil { - return 0, err - } - - if len(res) == 0 { - fmt.Printf("No account found. Returning 0.\n") - return 0, err - } - - account, err := ctx.Decoder(res) - if err != nil { - panic(err) - } - - return account.GetAccountNumber(), nil -} - -// get the next sequence for the account address -func (ctx CoreContext) NextSequence(address []byte) (int64, error) { - if ctx.Decoder == nil { - return 0, errors.New("accountDecoder required but not provided") - } - - res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) - if err != nil { - return 0, err - } - - if len(res) == 0 { - fmt.Printf("No account found, defaulting to sequence 0\n") - return 0, err - } - - account, err := ctx.Decoder(res) - if err != nil { - panic(err) - } - - return account.GetSequence(), nil -} - -// get passphrase from std input -func (ctx CoreContext) GetPassphraseFromStdin(name string) (pass string, err error) { - buf := client.BufferStdin() - prompt := fmt.Sprintf("Password to sign with '%s':", name) - return client.GetPassword(prompt, buf) -} - -// GetNode prepares a simple rpc.Client -func (ctx CoreContext) GetNode() (rpcclient.Client, error) { - if ctx.Client == nil { - return nil, errors.New("must define node URI") - } - return ctx.Client, nil -} diff --git a/client/context/query.go b/client/context/query.go new file mode 100644 index 0000000000..081f723b5c --- /dev/null +++ b/client/context/query.go @@ -0,0 +1,311 @@ +package context + +import ( + "fmt" + "io" + + "github.com/cosmos/cosmos-sdk/client/keys" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/pkg/errors" + + "github.com/tendermint/tendermint/libs/common" + cmn "github.com/tendermint/tendermint/libs/common" + rpcclient "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// GetNode returns an RPC client. If the context's client is not defined, an +// error is returned. +func (ctx CLIContext) GetNode() (rpcclient.Client, error) { + if ctx.Client == nil { + return nil, errors.New("no RPC client defined") + } + + return ctx.Client, nil +} + +// Query performs a query for information about the connected node. +func (ctx CLIContext) Query(path string) (res []byte, err error) { + return ctx.query(path, nil) +} + +// QueryStore performs a query from a Tendermint node with the provided key and +// store name. +func (ctx CLIContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) { + return ctx.queryStore(key, storeName, "key") +} + +// QuerySubspace performs a query from a Tendermint node with the provided +// store name and subspace. +func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sdk.KVPair, err error) { + resRaw, err := ctx.queryStore(subspace, storeName, "subspace") + if err != nil { + return res, err + } + + ctx.Codec.MustUnmarshalBinary(resRaw, &res) + return +} + +// GetAccount queries for an account given an address and a block height. An +// error is returned if the query or decoding fails. +func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { + if ctx.AccDecoder == nil { + return nil, errors.New("account decoder required but not provided") + } + + res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, err + } + + account, err := ctx.AccDecoder(res) + if err != nil { + return nil, err + } + + return account, nil +} + +// GetFromAddress returns the from address from the context's name. +func (ctx CLIContext) GetFromAddress() (from sdk.AccAddress, err error) { + if ctx.FromAddressName == "" { + return nil, errors.Errorf("must provide a from address name") + } + + keybase, err := keys.GetKeyBase() + if err != nil { + return nil, err + } + + info, err := keybase.Get(ctx.FromAddressName) + if err != nil { + return nil, errors.Errorf("no key for: %s", ctx.FromAddressName) + } + + return sdk.AccAddress(info.GetPubKey().Address()), nil +} + +// GetAccountNumber returns the next account number for the given account +// address. +func (ctx CLIContext) GetAccountNumber(address []byte) (int64, error) { + account, err := ctx.GetAccount(address) + if err != nil { + return 0, err + } + + return account.GetAccountNumber(), nil +} + +// GetAccountSequence returns the sequence number for the given account +// address. +func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) { + account, err := ctx.GetAccount(address) + if err != nil { + return 0, err + } + + return account.GetSequence(), nil +} + +// BroadcastTx broadcasts transaction bytes to a Tendermint node. +func (ctx CLIContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxCommit(tx) + if err != nil { + return res, err + } + + if !res.CheckTx.IsOK() { + return res, errors.Errorf("checkTx failed: (%d) %s", + res.CheckTx.Code, + res.CheckTx.Log) + } + + if !res.DeliverTx.IsOK() { + return res, errors.Errorf("deliverTx failed: (%d) %s", + res.DeliverTx.Code, + res.DeliverTx.Log) + } + + return res, err +} + +// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node +// asynchronously. +func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxAsync(tx) + if err != nil { + return res, err + } + + return res, err +} + +// EnsureAccountExists ensures that an account exists for a given context. An +// error is returned if it does not. +func (ctx CLIContext) EnsureAccountExists() error { + addr, err := ctx.GetFromAddress() + if err != nil { + return err + } + + accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) + if err != nil { + return err + } + + if len(accountBytes) == 0 { + return ErrInvalidAccount(addr) + } + + return nil +} + +// EnsureAccountExistsFromAddr ensures that an account exists for a given +// address. Instead of using the context's from name, a direct address is +// given. An error is returned if it does not. +func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { + accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) + if err != nil { + return err + } + + if len(accountBytes) == 0 { + return ErrInvalidAccount(addr) + } + + return nil +} + +// EnsureBroadcastTx broadcasts a transactions either synchronously or +// asynchronously based on the context parameters. The result of the broadcast +// is parsed into an intermediate structure which is logged if the context has +// a logger defined. +func (ctx CLIContext) EnsureBroadcastTx(txBytes []byte) error { + if ctx.Async { + return ctx.ensureBroadcastTxAsync(txBytes) + } + + return ctx.ensureBroadcastTx(txBytes) +} + +func (ctx CLIContext) ensureBroadcastTxAsync(txBytes []byte) error { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return err + } + + if ctx.JSON { + type toJSON struct { + TxHash string + } + + if ctx.Logger != nil { + resJSON := toJSON{res.Hash.String()} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } + } else { + if ctx.Logger != nil { + io.WriteString(ctx.Logger, fmt.Sprintf("Async tx sent (tx hash: %s)\n", res.Hash)) + } + } + + return nil +} + +func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + return err + } + + if ctx.JSON { + // since JSON is intended for automated scripts, always include + // response in JSON mode. + type toJSON struct { + Height int64 + TxHash string + Response string + } + + if ctx.Logger != nil { + resJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } + + return nil + } + + if ctx.Logger != nil { + resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String()) + + if ctx.PrintResponse { + resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n", + res.Height, res.Hash.String(), res.DeliverTx, + ) + } + + io.WriteString(ctx.Logger, resStr) + } + + return nil +} + +// query performs a query from a Tendermint node with the provided store name +// and path. +func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) { + node, err := ctx.GetNode() + if err != nil { + return res, err + } + + opts := rpcclient.ABCIQueryOptions{ + Height: ctx.Height, + Trusted: ctx.TrustNode, + } + + result, err := node.ABCIQueryWithOptions(path, key, opts) + if err != nil { + return res, err + } + + resp := result.Response + if !resp.IsOK() { + return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) + } + + return resp.Value, nil +} + +// queryStore performs a query from a Tendermint node with the provided a store +// name and path. +func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) { + path := fmt.Sprintf("/store/%s/%s", storeName, endPath) + return ctx.query(path, key) +} diff --git a/client/context/types.go b/client/context/types.go deleted file mode 100644 index 03dd6b9d03..0000000000 --- a/client/context/types.go +++ /dev/null @@ -1,113 +0,0 @@ -package context - -import ( - rpcclient "github.com/tendermint/tendermint/rpc/client" - - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// typical context created in sdk modules for transactions/queries -type CoreContext struct { - ChainID string - Height int64 - Gas int64 - Fee string - TrustNode bool - NodeURI string - FromAddressName string - AccountNumber int64 - Sequence int64 - Memo string - Client rpcclient.Client - Decoder auth.AccountDecoder - AccountStore string - UseLedger bool - Async bool - JSON bool - PrintResponse bool -} - -// WithChainID - return a copy of the context with an updated chainID -func (c CoreContext) WithChainID(chainID string) CoreContext { - c.ChainID = chainID - return c -} - -// WithHeight - return a copy of the context with an updated height -func (c CoreContext) WithHeight(height int64) CoreContext { - c.Height = height - return c -} - -// WithGas - return a copy of the context with an updated gas -func (c CoreContext) WithGas(gas int64) CoreContext { - c.Gas = gas - return c -} - -// WithFee - return a copy of the context with an updated fee -func (c CoreContext) WithFee(fee string) CoreContext { - c.Fee = fee - return c -} - -// WithTrustNode - return a copy of the context with an updated TrustNode flag -func (c CoreContext) WithTrustNode(trustNode bool) CoreContext { - c.TrustNode = trustNode - return c -} - -// WithNodeURI - return a copy of the context with an updated node URI -func (c CoreContext) WithNodeURI(nodeURI string) CoreContext { - c.NodeURI = nodeURI - c.Client = rpcclient.NewHTTP(nodeURI, "/websocket") - return c -} - -// WithFromAddressName - return a copy of the context with an updated from address -func (c CoreContext) WithFromAddressName(fromAddressName string) CoreContext { - c.FromAddressName = fromAddressName - return c -} - -// WithSequence - return a copy of the context with an account number -func (c CoreContext) WithAccountNumber(accnum int64) CoreContext { - c.AccountNumber = accnum - return c -} - -// WithSequence - return a copy of the context with an updated sequence number -func (c CoreContext) WithSequence(sequence int64) CoreContext { - c.Sequence = sequence - return c -} - -// WithMemo - return a copy of the context with an updated memo -func (c CoreContext) WithMemo(memo string) CoreContext { - c.Memo = memo - return c -} - -// WithClient - return a copy of the context with an updated RPC client instance -func (c CoreContext) WithClient(client rpcclient.Client) CoreContext { - c.Client = client - return c -} - -// WithDecoder - return a copy of the context with an updated Decoder -func (c CoreContext) WithDecoder(decoder auth.AccountDecoder) CoreContext { - c.Decoder = decoder - return c -} - -// WithAccountStore - return a copy of the context with an updated AccountStore -func (c CoreContext) WithAccountStore(accountStore string) CoreContext { - c.AccountStore = accountStore - return c -} - -// WithUseLedger - return a copy of the context with an updated UseLedger -func (c CoreContext) WithUseLedger(useLedger bool) CoreContext { - c.UseLedger = useLedger - return c -} diff --git a/client/context/viper.go b/client/context/viper.go deleted file mode 100644 index 6c7646079e..0000000000 --- a/client/context/viper.go +++ /dev/null @@ -1,141 +0,0 @@ -package context - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/viper" - - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - rpcclient "github.com/tendermint/tendermint/rpc/client" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// NewCoreContextFromViper - return a new context with parameters from the command line -func NewCoreContextFromViper() CoreContext { - nodeURI := viper.GetString(client.FlagNode) - var rpc rpcclient.Client - if nodeURI != "" { - rpc = rpcclient.NewHTTP(nodeURI, "/websocket") - } - chainID := viper.GetString(client.FlagChainID) - // if chain ID is not specified manually, read default chain ID - if chainID == "" { - def, err := defaultChainID() - if err != nil { - chainID = def - } - } - // TODO: Remove the following deprecation code after Gaia-7000 is launched - keyName := viper.GetString(client.FlagName) - if keyName != "" { - fmt.Println("** Note --name is deprecated and will be removed next release. Please use --from instead **") - } else { - keyName = viper.GetString(client.FlagFrom) - } - return CoreContext{ - ChainID: chainID, - Height: viper.GetInt64(client.FlagHeight), - Gas: viper.GetInt64(client.FlagGas), - Fee: viper.GetString(client.FlagFee), - TrustNode: viper.GetBool(client.FlagTrustNode), - FromAddressName: keyName, - NodeURI: nodeURI, - AccountNumber: viper.GetInt64(client.FlagAccountNumber), - Sequence: viper.GetInt64(client.FlagSequence), - Memo: viper.GetString(client.FlagMemo), - Client: rpc, - Decoder: nil, - AccountStore: "acc", - UseLedger: viper.GetBool(client.FlagUseLedger), - Async: viper.GetBool(client.FlagAsync), - JSON: viper.GetBool(client.FlagJson), - PrintResponse: viper.GetBool(client.FlagPrintResponse), - } -} - -// read chain ID from genesis file, if present -func defaultChainID() (string, error) { - cfg, err := tcmd.ParseConfig() - if err != nil { - return "", err - } - doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) - if err != nil { - return "", err - } - return doc.ChainID, nil -} - -// EnsureAccountExists - Make sure account exists -func EnsureAccountExists(ctx CoreContext, name string) error { - keybase, err := keys.GetKeyBase() - if err != nil { - return err - } - - if name == "" { - return errors.Errorf("must provide a from address name") - } - - info, err := keybase.Get(name) - if err != nil { - return errors.Errorf("no key for: %s", name) - } - - accAddr := sdk.AccAddress(info.GetPubKey().Address()) - - Acc, err := ctx.QueryStore(auth.AddressStoreKey(accAddr), ctx.AccountStore) - if err != nil { - return err - } - - // Check if account was found - if Acc == nil { - return errors.Errorf("No account with address %s was found in the state.\nAre you sure there has been a transaction involving it?", accAddr) - } - return nil -} - -// EnsureAccount - automatically set account number if none provided -func EnsureAccountNumber(ctx CoreContext) (CoreContext, error) { - // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 - if viper.GetInt64(client.FlagAccountNumber) != 0 { - return ctx, nil - } - from, err := ctx.GetFromAddress() - if err != nil { - return ctx, err - } - accnum, err := ctx.GetAccountNumber(from) - if err != nil { - return ctx, err - } - fmt.Printf("Defaulting to account number: %d\n", accnum) - ctx = ctx.WithAccountNumber(accnum) - return ctx, nil -} - -// EnsureSequence - automatically set sequence number if none provided -func EnsureSequence(ctx CoreContext) (CoreContext, error) { - // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 - if viper.GetInt64(client.FlagSequence) != 0 { - return ctx, nil - } - from, err := ctx.GetFromAddress() - if err != nil { - return ctx, err - } - seq, err := ctx.NextSequence(from) - if err != nil { - return ctx, err - } - fmt.Printf("Defaulting to next sequence number: %d\n", seq) - ctx = ctx.WithSequence(seq) - return ctx, nil -} diff --git a/client/flags.go b/client/flags.go index b96012da7d..8616f9e78d 100644 --- a/client/flags.go +++ b/client/flags.go @@ -42,7 +42,6 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { c.Flags().String(FlagFrom, "", "Name of private key with which to sign") - c.Flags().String(FlagName, "", "DEPRECATED - Name of private key with which to sign") c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagMemo, "", "Memo to send along with transaction") diff --git a/client/keys/utils.go b/client/keys/utils.go index 5462597bac..907f9eda80 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -29,6 +29,55 @@ func GetKeyBase() (keys.Keybase, error) { return GetKeyBaseFromDir(rootDir) } +// GetKeyInfo returns key info for a given name. An error is returned if the +// keybase cannot be retrieved or getting the info fails. +func GetKeyInfo(name string) (keys.Info, error) { + keybase, err := GetKeyBase() + if err != nil { + return nil, err + } + + return keybase.Get(name) +} + +// GetPassphrase returns a passphrase for a given name. It will first retrieve +// the key info for that name if the type is local, it'll fetch input from +// STDIN. Otherwise, an empty passphrase is returned. An error is returned if +// the key info cannot be fetched or reading from STDIN fails. +func GetPassphrase(name string) (string, error) { + var passphrase string + + keyInfo, err := GetKeyInfo(name) + if err != nil { + return passphrase, err + } + + // we only need a passphrase for locally stored keys + // TODO: (ref: #864) address security concerns + if keyInfo.GetType() == keys.TypeLocal { + passphrase, err = ReadPassphraseFromStdin(name) + if err != nil { + return passphrase, err + } + } + + return passphrase, nil +} + +// ReadPassphraseFromStdin attempts to read a passphrase from STDIN return an +// error upon failure. +func ReadPassphraseFromStdin(name string) (string, error) { + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password to sign with '%s':", name) + + passphrase, err := client.GetPassword(prompt, buf) + if err != nil { + return passphrase, fmt.Errorf("Error reading passphrase: %v", err) + } + + return passphrase, nil +} + // initialize a keybase based on the configuration func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) { if keybase == nil { @@ -77,7 +126,7 @@ func Bech32KeyOutput(info keys.Info) (KeyOutput, error) { } return KeyOutput{ Name: info.GetName(), - Type: info.GetType(), + Type: info.GetType().String(), Address: account, PubKey: bechPubKey, }, nil diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index ee9ff96f9b..8f803ced9a 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -647,7 +647,6 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - // send coinbz, err := cdc.MarshalJSON(sdk.NewInt64Coin("steak", 1)) if err != nil { @@ -693,7 +692,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc "account_number":"%d", "sequence": "%d", "gas": "100000", - "chain_id": "%s", + "src_chain_id": "%s", "amount":[ { "denom": "%s", @@ -701,6 +700,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc } ] }`, name, password, accnum, sequence, chainID, "steak")) + res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/lcd/root.go b/client/lcd/root.go index 7406a30568..bfa62f1cf0 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -78,21 +78,21 @@ func createHandler(cdc *wire.Codec) http.Handler { panic(err) } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout) // TODO: make more functional? aka r = keys.RegisterRoutes(r) r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") - r.HandleFunc("/node_version", NodeVersionRequestHandler(ctx)).Methods("GET") + r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") keys.RegisterRoutes(r) - rpc.RegisterRoutes(ctx, r) - tx.RegisterRoutes(ctx, r, cdc) - auth.RegisterRoutes(ctx, r, cdc, "acc") - bank.RegisterRoutes(ctx, r, cdc, kb) - ibc.RegisterRoutes(ctx, r, cdc, kb) - stake.RegisterRoutes(ctx, r, cdc, kb) - slashing.RegisterRoutes(ctx, r, cdc, kb) - gov.RegisterRoutes(ctx, r, cdc) + rpc.RegisterRoutes(cliCtx, r) + tx.RegisterRoutes(cliCtx, r, cdc) + auth.RegisterRoutes(cliCtx, r, cdc, "acc") + bank.RegisterRoutes(cliCtx, r, cdc, kb) + ibc.RegisterRoutes(cliCtx, r, cdc, kb) + stake.RegisterRoutes(cliCtx, r, cdc, kb) + slashing.RegisterRoutes(cliCtx, r, cdc, kb) + gov.RegisterRoutes(cliCtx, r, cdc) return r } diff --git a/client/lcd/version.go b/client/lcd/version.go index 4e328b7a0b..377d7ca268 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -15,14 +15,15 @@ func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { } // connected node version REST handler endpoint -func NodeVersionRequestHandler(ctx context.CoreContext) http.HandlerFunc { +func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - version, err := ctx.Query("/app/version") + version, err := cliCtx.Query("/app/version") if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) return } + w.Write(version) } } diff --git a/client/rpc/block.go b/client/rpc/block.go index 3244e8d122..fb4376bc1a 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -5,11 +5,11 @@ import ( "net/http" "strconv" - "github.com/gorilla/mux" - "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" ) const ( @@ -31,9 +31,9 @@ func BlockCommand() *cobra.Command { return cmd } -func getBlock(ctx context.CoreContext, height *int64) ([]byte, error) { +func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) { // get the node - node, err := ctx.GetNode() + node, err := cliCtx.GetNode() if err != nil { return nil, err } @@ -57,8 +57,8 @@ func getBlock(ctx context.CoreContext, height *int64) ([]byte, error) { } // get the current blockchain height -func GetChainHeight(ctx context.CoreContext) (int64, error) { - node, err := ctx.GetNode() +func GetChainHeight(cliCtx context.CLIContext) (int64, error) { + node, err := cliCtx.GetNode() if err != nil { return -1, err } @@ -86,7 +86,7 @@ func printBlock(cmd *cobra.Command, args []string) error { } } - output, err := getBlock(context.NewCoreContextFromViper(), height) + output, err := getBlock(context.NewCLIContext(), height) if err != nil { return err } @@ -97,7 +97,7 @@ func printBlock(cmd *cobra.Command, args []string) error { // REST // REST handler to get a block -func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) height, err := strconv.ParseInt(vars["height"], 10, 64) @@ -106,13 +106,13 @@ func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'.")) return } - chainHeight, err := GetChainHeight(ctx) + chainHeight, err := GetChainHeight(cliCtx) if height > chainHeight { w.WriteHeader(404) w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) return } - output, err := getBlock(ctx, &height) + output, err := getBlock(cliCtx, &height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -123,15 +123,15 @@ func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { } // REST handler to get the latest block -func LatestBlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - height, err := GetChainHeight(ctx) + height, err := GetChainHeight(cliCtx) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } - output, err := getBlock(ctx, &height) + output, err := getBlock(cliCtx, &height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/rpc/root.go b/client/rpc/root.go index bb5a162a7f..a8171a293a 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -44,11 +44,11 @@ func initClientCommand() *cobra.Command { } // Register REST endpoints -func RegisterRoutes(ctx context.CoreContext, r *mux.Router) { - r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(ctx)).Methods("GET") - r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(ctx)).Methods("GET") - r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(ctx)).Methods("GET") - r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(ctx)).Methods("GET") - r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(ctx)).Methods("GET") - r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(ctx)).Methods("GET") +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(cliCtx)).Methods("GET") } diff --git a/client/rpc/status.go b/client/rpc/status.go index 0c1f415930..ea090d32f9 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -18,23 +18,25 @@ func statusCommand() *cobra.Command { Short: "Query remote node for status", RunE: printNodeStatus, } + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") return cmd } -func getNodeStatus(ctx context.CoreContext) (*ctypes.ResultStatus, error) { +func getNodeStatus(cliCtx context.CLIContext) (*ctypes.ResultStatus, error) { // get the node - node, err := ctx.GetNode() + node, err := cliCtx.GetNode() if err != nil { return &ctypes.ResultStatus{}, err } + return node.Status() } // CMD func printNodeStatus(cmd *cobra.Command, args []string) error { - status, err := getNodeStatus(context.NewCoreContextFromViper()) + status, err := getNodeStatus(context.NewCLIContext()) if err != nil { return err } @@ -52,9 +54,9 @@ func printNodeStatus(cmd *cobra.Command, args []string) error { // REST // REST handler for node info -func NodeInfoRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - status, err := getNodeStatus(ctx) + status, err := getNodeStatus(cliCtx) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -68,14 +70,15 @@ func NodeInfoRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } + w.Write(output) } } // REST handler for node syncing -func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func NodeSyncingRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - status, err := getNodeStatus(ctx) + status, err := getNodeStatus(cliCtx) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -88,6 +91,7 @@ func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } + w.Write([]byte(strconv.FormatBool(syncing))) } } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index b8a6c4cc20..d2daa4ec5c 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -58,9 +58,9 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error }, nil } -func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { +func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) { // get the node - node, err := ctx.GetNode() + node, err := cliCtx.GetNode() if err != nil { return nil, err } @@ -74,6 +74,7 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { BlockHeight: validatorsRes.BlockHeight, Validators: make([]ValidatorOutput, len(validatorsRes.Validators)), } + for i := 0; i < len(validatorsRes.Validators); i++ { outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i]) if err != nil { @@ -85,6 +86,7 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { if err != nil { return nil, err } + return output, nil } @@ -104,7 +106,7 @@ func printValidators(cmd *cobra.Command, args []string) error { } } - output, err := getValidators(context.NewCoreContextFromViper(), height) + output, err := getValidators(context.NewCLIContext(), height) if err != nil { return err } @@ -116,22 +118,25 @@ func printValidators(cmd *cobra.Command, args []string) error { // REST // Validator Set at a height REST handler -func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) + height, err := strconv.ParseInt(vars["height"], 10, 64) if err != nil { w.WriteHeader(400) w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'.")) return } - chainHeight, err := GetChainHeight(ctx) + + chainHeight, err := GetChainHeight(cliCtx) if height > chainHeight { w.WriteHeader(404) w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) return } - output, err := getValidators(ctx, &height) + + output, err := getValidators(cliCtx, &height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -143,20 +148,22 @@ func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { } // Latest Validator Set REST handler -func LatestValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - height, err := GetChainHeight(ctx) + height, err := GetChainHeight(cliCtx) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } - output, err := getValidators(ctx, &height) + + output, err := getValidators(cliCtx, &height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } + w.Write(output) } } diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 21f576db41..89ad48f43f 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -13,7 +13,7 @@ type BroadcastTxBody struct { } // BroadcastTx REST Handler -func BroadcastTxRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { +func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m BroadcastTxBody @@ -25,7 +25,7 @@ func BroadcastTxRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { return } - res, err := ctx.BroadcastTx([]byte(m.TxBytes)) + res, err := cliCtx.BroadcastTx([]byte(m.TxBytes)) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/tx/query.go b/client/tx/query.go index dfe626c38c..239c656a3b 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -21,24 +21,25 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" ) -// Get the default command for a tx query +// QueryTxCmd implements the default command for a tx query. func QueryTxCmd(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "tx [hash]", Short: "Matches this txhash over all committed blocks", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - // find the key to look up the account hashHexStr := args[0] trustNode := viper.GetBool(client.FlagTrustNode) - output, err := queryTx(cdc, context.NewCoreContextFromViper(), hashHexStr, trustNode) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) if err != nil { return err } - fmt.Println(string(output)) + fmt.Println(string(output)) return nil }, } @@ -50,14 +51,13 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command { return cmd } -func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustNode bool) ([]byte, error) { +func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trustNode bool) ([]byte, error) { hash, err := hex.DecodeString(hashHexStr) if err != nil { return nil, err } - // get the node - node, err := ctx.GetNode() + node, err := cliCtx.GetNode() if err != nil { return nil, err } @@ -66,6 +66,7 @@ func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustN if err != nil { return nil, err } + info, err := formatTxResult(cdc, res) if err != nil { return nil, err @@ -81,13 +82,12 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) { return txInfo{}, err } - info := txInfo{ + return txInfo{ Hash: res.Hash, Height: res.Height, Tx: tx, Result: res.TxResult, - } - return info, nil + }, nil } // txInfo is used to prepare info to display @@ -100,17 +100,19 @@ type txInfo struct { func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) { var tx auth.StdTx + err := cdc.UnmarshalBinary(txBytes, &tx) if err != nil { return nil, err } + return tx, nil } // REST // transaction query REST handler -func QueryTxRequestHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { +func QueryTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) hashHexStr := vars["hash"] @@ -120,12 +122,13 @@ func QueryTxRequestHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.Hand trustNode = true } - output, err := queryTx(cdc, ctx, hashHexStr, trustNode) + output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } + w.Write(output) } } diff --git a/client/tx/root.go b/client/tx/root.go index 5def5a5440..7e18d5aae1 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -17,9 +17,9 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { } // register REST routes -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { - r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, ctx)).Methods("GET") - r.HandleFunc("/txs", SearchTxRequestHandlerFn(ctx, cdc)).Methods("GET") +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET") // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") } diff --git a/client/tx/search.go b/client/tx/search.go index 76c394f928..bd35d2a371 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -7,15 +7,15 @@ import ( "net/url" "strings" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + ctypes "github.com/tendermint/tendermint/rpc/core/types" ) const ( @@ -31,14 +31,18 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { tags := viper.GetStringSlice(flagTags) - txs, err := searchTxs(context.NewCoreContextFromViper(), cdc, tags) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txs, err := searchTxs(cliCtx, cdc, tags) if err != nil { return err } + output, err := cdc.MarshalJSON(txs) if err != nil { return err } + fmt.Println(string(output)) return nil }, @@ -53,19 +57,22 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { return cmd } -func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) { +func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]txInfo, error) { if len(tags) == 0 { return nil, errors.New("must declare at least one tag to search") } + // XXX: implement ANY query := strings.Join(tags, " AND ") + // get the node - node, err := ctx.GetNode() + node, err := cliCtx.GetNode() if err != nil { return nil, err } prove := !viper.GetBool(client.FlagTrustNode) + // TODO: take these as args page := 0 perPage := 100 @@ -98,7 +105,7 @@ func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) // REST // Search Tx REST Handler -func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tag := r.FormValue("tag") if tag == "" { @@ -106,14 +113,17 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys")) return } + keyValue := strings.Split(tag, "=") key := keyValue[0] + value, err := url.QueryUnescape(keyValue[1]) if err != nil { w.WriteHeader(400) w.Write([]byte("Could not decode address: " + err.Error())) return } + if strings.HasSuffix(key, "_bech32") { bech32address := strings.Trim(value, "'") prefix := strings.Split(bech32address, "1")[0] @@ -127,7 +137,7 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han tag = strings.TrimRight(key, "_bech32") + "='" + sdk.AccAddress(bz).String() + "'" } - txs, err := searchTxs(ctx, cdc, []string{tag}) + txs, err := searchTxs(cliCtx, cdc, []string{tag}) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/utils/utils.go b/client/utils/utils.go new file mode 100644 index 0000000000..8a058b56fd --- /dev/null +++ b/client/utils/utils.go @@ -0,0 +1,60 @@ +package utils + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/keys" + sdk "github.com/cosmos/cosmos-sdk/types" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" +) + +// SendTx implements a auxiliary handler that facilitates sending a series of +// messages in a signed transaction given a TxContext and a QueryContext. It +// ensures that the account exists, has a proper number and sequence set. In +// addition, it builds and signs a transaction with the supplied messages. +// Finally, it broadcasts the signed transaction to a node. +func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error { + if err := cliCtx.EnsureAccountExists(); err != nil { + return err + } + + from, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + // TODO: (ref #1903) Allow for user supplied account number without + // automatically doing a manual lookup. + if txCtx.AccountNumber == 0 { + accNum, err := cliCtx.GetAccountNumber(from) + if err != nil { + return err + } + + txCtx = txCtx.WithAccountNumber(accNum) + } + + // TODO: (ref #1903) Allow for user supplied account sequence without + // automatically doing a manual lookup. + if txCtx.Sequence == 0 { + accSeq, err := cliCtx.GetAccountSequence(from) + if err != nil { + return err + } + + txCtx = txCtx.WithSequence(accSeq) + } + + passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) + if err != nil { + return err + } + + // build and sign the transaction + txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) + if err != nil { + return err + } + + // broadcast to a Tendermint node + return cliCtx.EnsureBroadcastTx(txBytes) +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go index c3f5d7834f..62ff8bd16a 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -44,10 +44,31 @@ type Keybase interface { ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) } +// KeyType reflects a human-readable type for key listing. +type KeyType uint + +// Info KeyTypes +const ( + TypeLocal KeyType = 0 + TypeLedger KeyType = 1 + TypeOffline KeyType = 2 +) + +var keyTypes = map[KeyType]string{ + TypeLocal: "local", + TypeLedger: "ledger", + TypeOffline: "offline", +} + +// String implements the stringer interface for KeyType. +func (kt KeyType) String() string { + return keyTypes[kt] +} + // Info is the publicly exposed information about a keypair type Info interface { // Human-readable type for key listing - GetType() string + GetType() KeyType // Name of the key GetName() string // Public key @@ -73,8 +94,8 @@ func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info { } } -func (i localInfo) GetType() string { - return "local" +func (i localInfo) GetType() KeyType { + return TypeLocal } func (i localInfo) GetName() string { @@ -100,8 +121,8 @@ func newLedgerInfo(name string, pub crypto.PubKey, path ccrypto.DerivationPath) } } -func (i ledgerInfo) GetType() string { - return "ledger" +func (i ledgerInfo) GetType() KeyType { + return TypeLedger } func (i ledgerInfo) GetName() string { @@ -125,8 +146,8 @@ func newOfflineInfo(name string, pub crypto.PubKey) Info { } } -func (i offlineInfo) GetType() string { - return "offline" +func (i offlineInfo) GetType() KeyType { + return TypeOffline } func (i offlineInfo) GetName() string { diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index 3e034600bd..82b46be738 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -1,78 +1,65 @@ package cli import ( - "github.com/spf13/cobra" - "github.com/spf13/viper" + "os" + + "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" ) -// take the coolness quiz transaction +// QuizTxCmd invokes the coolness quiz transaction. func QuizTxCmd(cdc *wire.Codec) *cobra.Command { return &cobra.Command{ Use: "cool [answer]", Short: "What's cooler than being cool?", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - // get the from address from the name flag - from, err := ctx.GetFromAddress() + from, err := cliCtx.GetFromAddress() if err != nil { return err } - // create the message msg := cool.NewMsgQuiz(from, args[0]) - // get account name - name := viper.GetString(client.FlagName) - - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } } -// set a new cool trend transaction +// SetTrendTxCmd sends a new cool trend transaction. func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { return &cobra.Command{ Use: "setcool [answer]", Short: "You're so cool, tell us what is cool!", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - // get the from address from the name flag - from, err := ctx.GetFromAddress() + from, err := cliCtx.GetFromAddress() if err != nil { return err } - // get account name - name := viper.GetString(client.FlagName) - - // create the message msg := cool.NewMsgSetTrend(from, args[0]) - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index bc958ffaeb..af1a8a060b 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -1,16 +1,18 @@ package cli import ( + "os" "strconv" - "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client/context" - + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" + + "github.com/spf13/cobra" ) // command to mine some pow! @@ -20,9 +22,13 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { Short: "Mine some coins with proof-of-work!", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - from, err := ctx.GetFromAddress() + from, err := cliCtx.GetFromAddress() if err != nil { return err } @@ -31,29 +37,23 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { if err != nil { return err } + count, err := strconv.ParseUint(args[1], 0, 64) if err != nil { return err } + nonce, err := strconv.ParseUint(args[2], 0, 64) if err != nil { return err } solution := []byte(args[3]) - msg := pow.NewMsgMine(from, difficulty, count, nonce, solution) - // get account name - name := ctx.FromAddressName - - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + // Build and sign the transaction, then broadcast to a Tendermint + // node. + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/examples/democoin/x/simplestake/client/cli/commands.go index 20dc6fe978..a387dcac3f 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -3,18 +3,20 @@ package cli import ( "encoding/hex" "fmt" - - "github.com/spf13/cobra" - "github.com/tendermint/tendermint/crypto/ed25519" - - "github.com/spf13/viper" + "os" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/tendermint/crypto/ed25519" ) const ( @@ -28,9 +30,13 @@ func BondTxCmd(cdc *wire.Codec) *cobra.Command { Use: "bond", Short: "Bond to a validator", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper() + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - from, err := ctx.GetFromAddress() + from, err := cliCtx.GetFromAddress() if err != nil { return err } @@ -60,11 +66,15 @@ func BondTxCmd(cdc *wire.Codec) *cobra.Command { msg := simplestake.NewMsgBond(from, stake, pubKeyEd) - return sendMsg(cdc, msg) + // Build and sign the transaction, then broadcast to a Tendermint + // node. + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } + cmd.Flags().String(flagStake, "", "Amount of coins to stake") cmd.Flags().String(flagValidator, "", "Validator address to stake") + return cmd } @@ -74,23 +84,23 @@ func UnbondTxCmd(cdc *wire.Codec) *cobra.Command { Use: "unbond", Short: "Unbond from a validator", RunE: func(cmd *cobra.Command, args []string) error { - from, err := context.NewCoreContextFromViper().GetFromAddress() + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout) + + from, err := cliCtx.GetFromAddress() if err != nil { return err } + msg := simplestake.NewMsgUnbond(from) - return sendMsg(cdc, msg) + + // Build and sign the transaction, then broadcast to a Tendermint + // node. + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } + return cmd } - -func sendMsg(cdc *wire.Codec, msg sdk.Msg) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil -} diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index 5fc8545b95..3b93798a0d 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -1,7 +1,6 @@ package cli import ( - "errors" "fmt" "github.com/spf13/cobra" @@ -12,32 +11,31 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" ) -// GetAccountCmd for the auth.BaseAccount type +// GetAccountCmdDefault invokes the GetAccountCmd for the auth.BaseAccount type. func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command { return GetAccountCmd(storeName, cdc, GetAccountDecoder(cdc)) } -// Get account decoder for auth.DefaultAccount +// GetAccountDecoder gets the account decoder for auth.DefaultAccount. func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { return func(accBytes []byte) (acct auth.Account, err error) { - // acct := new(auth.BaseAccount) err = cdc.UnmarshalBinaryBare(accBytes, &acct) if err != nil { panic(err) } + return acct, err } } -// GetAccountCmd returns a query account that will display the -// state of the account at a given address +// GetAccountCmd returns a query account that will display the state of the +// account at a given address. func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder) *cobra.Command { return &cobra.Command{ Use: "account [address]", Short: "Query account balance", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - // find the key to look up the account addr := args[0] @@ -46,30 +44,24 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode return err } - // perform query - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(auth.AddressStoreKey(key), storeName) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(decoder) + + if err := cliCtx.EnsureAccountExistsFromAddr(key); err != nil { + return err + } + + acc, err := cliCtx.GetAccount(key) if err != nil { return err } - // Check if account was found - if res == nil { - return errors.New("No account with address " + addr + - " was found in the state.\nAre you sure there has been a transaction involving it?") - } - - // decode the value - account, err := decoder(res) + output, err := wire.MarshalJSONIndent(cdc, acc) if err != nil { return err } - // print out whole account - output, err := wire.MarshalJSONIndent(cdc, account) - if err != nil { - return err - } fmt.Println(string(output)) return nil }, diff --git a/x/auth/client/context/context.go b/x/auth/client/context/context.go new file mode 100644 index 0000000000..1cfa435ee5 --- /dev/null +++ b/x/auth/client/context/context.go @@ -0,0 +1,152 @@ +package context + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/pkg/errors" + "github.com/spf13/viper" +) + +// TxContext implements a transaction context created in SDK modules. +type TxContext struct { + Codec *wire.Codec + AccountNumber int64 + Sequence int64 + Gas int64 + ChainID string + Memo string + Fee string +} + +// NewTxContextFromCLI returns a new initialized TxContext with parameters from +// the command line using Viper. +func NewTxContextFromCLI() TxContext { + // if chain ID is not specified manually, read default chain ID + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + defaultChainID, err := defaultChainID() + if err != nil { + chainID = defaultChainID + } + } + + return TxContext{ + ChainID: chainID, + Gas: viper.GetInt64(client.FlagGas), + AccountNumber: viper.GetInt64(client.FlagAccountNumber), + Sequence: viper.GetInt64(client.FlagSequence), + Fee: viper.GetString(client.FlagFee), + Memo: viper.GetString(client.FlagMemo), + } +} + +// WithCodec returns a copy of the context with an updated codec. +func (ctx TxContext) WithCodec(cdc *wire.Codec) TxContext { + ctx.Codec = cdc + return ctx +} + +// WithChainID returns a copy of the context with an updated chainID. +func (ctx TxContext) WithChainID(chainID string) TxContext { + ctx.ChainID = chainID + return ctx +} + +// WithGas returns a copy of the context with an updated gas. +func (ctx TxContext) WithGas(gas int64) TxContext { + ctx.Gas = gas + return ctx +} + +// WithFee returns a copy of the context with an updated fee. +func (ctx TxContext) WithFee(fee string) TxContext { + ctx.Fee = fee + return ctx +} + +// WithSequence returns a copy of the context with an updated sequence number. +func (ctx TxContext) WithSequence(sequence int64) TxContext { + ctx.Sequence = sequence + return ctx +} + +// WithMemo returns a copy of the context with an updated memo. +func (ctx TxContext) WithMemo(memo string) TxContext { + ctx.Memo = memo + return ctx +} + +// WithAccountNumber returns a copy of the context with an account number. +func (ctx TxContext) WithAccountNumber(accnum int64) TxContext { + ctx.AccountNumber = accnum + return ctx +} + +// Build builds a single message to be signed from a TxContext given a set of +// messages. It returns an error if a fee is supplied but cannot be parsed. +func (ctx TxContext) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) { + chainID := ctx.ChainID + if chainID == "" { + return auth.StdSignMsg{}, errors.Errorf("chain ID required but not specified") + } + + fee := sdk.Coin{} + if ctx.Fee != "" { + parsedFee, err := sdk.ParseCoin(ctx.Fee) + if err != nil { + return auth.StdSignMsg{}, err + } + + fee = parsedFee + } + + return auth.StdSignMsg{ + ChainID: ctx.ChainID, + AccountNumber: ctx.AccountNumber, + Sequence: ctx.Sequence, + Memo: ctx.Memo, + Msgs: msgs, + + // TODO: run simulate to estimate gas? + Fee: auth.NewStdFee(ctx.Gas, fee), + }, nil +} + +// Sign signs a transaction given a name, passphrase, and a single message to +// signed. An error is returned if signing fails. +func (ctx TxContext) Sign(name, passphrase string, msg auth.StdSignMsg) ([]byte, error) { + keybase, err := keys.GetKeyBase() + if err != nil { + return nil, err + } + + sig, pubkey, err := keybase.Sign(name, passphrase, msg.Bytes()) + if err != nil { + return nil, err + } + + sigs := []auth.StdSignature{{ + AccountNumber: msg.AccountNumber, + Sequence: msg.Sequence, + PubKey: pubkey, + Signature: sig, + }} + + return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) +} + +// BuildAndSign builds a single message to be signed, and signs a transaction +// with the built message given a name, passphrase, and a set of +// messages. +func (ctx TxContext) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]byte, error) { + msg, err := ctx.Build(msgs) + if err != nil { + return nil, err + } + + return ctx.Sign(name, passphrase, msg) +} diff --git a/x/auth/client/context/utils.go b/x/auth/client/context/utils.go new file mode 100644 index 0000000000..22cc8220bb --- /dev/null +++ b/x/auth/client/context/utils.go @@ -0,0 +1,25 @@ +package context + +import ( + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + tmtypes "github.com/tendermint/tendermint/types" +) + +// defaultChainID returns the chain ID from the genesis file if present. An +// error is returned if the file cannot be read or parsed. +// +// TODO: This should be removed and the chainID should always be provided by +// the end user. +func defaultChainID() (string, error) { + cfg, err := tcmd.ParseConfig() + if err != nil { + return "", err + } + + doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) + if err != nil { + return "", err + } + + return doc.ChainID, nil +} diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 5cdc2ee257..431d3d27d5 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -4,25 +4,28 @@ import ( "fmt" "net/http" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + + "github.com/gorilla/mux" ) // register REST routes -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, storeName string) { +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) { r.HandleFunc( "/accounts/{address}", - QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), ctx), + QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx), ).Methods("GET") } // query accountREST Handler -func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CoreContext) http.HandlerFunc { +func QueryAccountRequestHandlerFn( + storeName string, cdc *wire.Codec, + decoder auth.AccountDecoder, cliCtx context.CLIContext, +) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bech32addr := vars["address"] @@ -34,7 +37,7 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder aut return } - res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) + res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index b294e8cc74..92ac37c1e9 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -1,14 +1,17 @@ package cli import ( - "github.com/pkg/errors" + "os" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/bank/client" + + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -18,36 +21,29 @@ const ( flagAmount = "amount" ) -// SendTxCmd will create a send tx and sign it with the given key +// SendTxCmd will create a send tx and sign it with the given key. func SendTxCmd(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "send", Short: "Create and sign a send tx", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - // get the from/to address - from, err := ctx.GetFromAddress() - if err != nil { + if err := cliCtx.EnsureAccountExists(); err != nil { return err } - fromAcc, err := ctx.QueryStore(auth.AddressStoreKey(from), ctx.AccountStore) - if err != nil { - return err - } - - // Check if account was found - if fromAcc == nil { - return errors.Errorf("No account with address %s was found in the state.\nAre you sure there has been a transaction involving it?", from) - } - toStr := viper.GetString(flagTo) to, err := sdk.AccAddressFromBech32(toStr) if err != nil { return err } + // parse coins trying to be sent amount := viper.GetString(flagAmount) coins, err := sdk.ParseCoins(amount) @@ -55,11 +51,17 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { return err } - // ensure account has enough coins - account, err := ctx.Decoder(fromAcc) + from, err := cliCtx.GetFromAddress() if err != nil { return err } + + account, err := cliCtx.GetAccount(from) + if err != nil { + return err + } + + // ensure account has enough coins if !account.GetCoins().IsGTE(coins) { return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) } @@ -67,12 +69,7 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint msg := client.BuildMsg(from, to, coins) - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil - + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 872b3cfe36..e060a3bb4f 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -4,19 +4,20 @@ import ( "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank/client" + + "github.com/gorilla/mux" ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, ctx)).Methods("POST") +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } type sendBody struct { @@ -38,7 +39,7 @@ func init() { } // SendRequestHandlerFn - http request handler to send coins to a address -func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // collect data vars := mux.Vars(r) @@ -80,23 +81,22 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } - // add gas to context - ctx = ctx.WithGas(m.Gas) - // add chain-id to context - ctx = ctx.WithChainID(m.ChainID) + txCtx := authctx.TxContext{ + Codec: cdc, + Gas: m.Gas, + ChainID: m.ChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + } - // sign - ctx = ctx.WithAccountNumber(m.AccountNumber) - ctx = ctx.WithSequence(m.Sequence) - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) return } - // send - res, err := ctx.BroadcastTx(txBytes) + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 968c66de7c..d19de9f07f 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -2,16 +2,19 @@ package cli import ( "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" + "os" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( @@ -27,21 +30,24 @@ const ( flagLatestProposalIDs = "latest" ) -// submit a proposal tx +// GetCmdSubmitProposal implements submitting a proposal transaction command. func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "submit-proposal", Short: "Submit a proposal along with an initial deposit", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - title := viper.GetString(flagTitle) description := viper.GetString(flagDescription) strProposalType := viper.GetString(flagProposalType) initialDeposit := viper.GetString(flagDeposit) - // get the from address from the name flag - fromAddr, err := ctx.GetFromAddress() + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + fromAddr, err := cliCtx.GetFromAddress() if err != nil { return err } @@ -56,7 +62,6 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { return err } - // create the message msg := gov.NewMsgSubmitProposal(title, description, proposalType, fromAddr, amount) err = msg.ValidateBasic() @@ -64,14 +69,10 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { return err } - // build and sign the transaction, then broadcast to Tendermint - // proposalID must be returned, and it is a part of response - ctx.PrintResponse = true - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + // Build and sign the transaction, then broadcast to Tendermint + // proposalID must be returned, and it is a part of response. + cliCtx.PrintResponse = true + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } @@ -83,16 +84,19 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { return cmd } -// set a new Deposit transaction +// GetCmdDeposit implements depositing tokens for an active proposal. func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "deposit", Short: "deposit tokens for activing proposal", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - // get the from address from the name flag - depositerAddr, err := ctx.GetFromAddress() + depositerAddr, err := cliCtx.GetFromAddress() if err != nil { return err } @@ -104,7 +108,6 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { return err } - // create the message msg := gov.NewMsgDeposit(depositerAddr, proposalID, amount) err = msg.ValidateBasic() @@ -112,12 +115,9 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { return err } - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + // Build and sign the transaction, then broadcast to a Tendermint + // node. + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } @@ -127,21 +127,24 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { return cmd } -// set a new Vote transaction +// GetCmdVote implements creating a new vote command. func GetCmdVote(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "vote", Short: "vote for an active proposal, options: Yes/No/NoWithVeto/Abstain", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - voterAddr, err := ctx.GetFromAddress() + voterAddr, err := cliCtx.GetFromAddress() if err != nil { return err } proposalID := viper.GetInt64(flagProposalID) - option := viper.GetString(flagOption) byteVoteOption, err := gov.VoteOptionFromString(option) @@ -149,7 +152,6 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { return err } - // create the message msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) err = msg.ValidateBasic() @@ -158,14 +160,12 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { } fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", - voterAddr.String(), msg.ProposalID, msg.Option.String()) + voterAddr.String(), msg.ProposalID, msg.Option.String(), + ) - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + // Build and sign the transaction, then broadcast to a Tendermint + // node. + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } @@ -175,27 +175,28 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { return cmd } -// Command to Get a Proposal Information +// GetCmdQueryProposal implements the query proposal command. func GetCmdQueryProposal(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "query-proposal", Short: "query proposal details", RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) - ctx := context.NewCoreContextFromViper() - - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if len(res) == 0 || err != nil { return errors.Errorf("proposalID [%d] is not existed", proposalID) } var proposal gov.Proposal cdc.MustUnmarshalBinary(res, &proposal) + output, err := wire.MarshalJSONIndent(cdc, proposal) if err != nil { return err } + fmt.Println(string(output)) return nil }, @@ -207,7 +208,7 @@ func GetCmdQueryProposal(storeName string, cdc *wire.Codec) *cobra.Command { } // nolint: gocyclo -// Command to Query Proposals +// GetCmdQueryProposals implements a query proposals command. func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "query-proposals", @@ -244,9 +245,9 @@ func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { } } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyNextProposalID, storeName) + res, err := cliCtx.QueryStore(gov.KeyNextProposalID, storeName) if err != nil { return err } @@ -261,20 +262,20 @@ func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { for proposalID := maxProposalID - latestProposalsIDs; proposalID < maxProposalID; proposalID++ { if voterAddr != nil { - res, err = ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + res, err = cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) if err != nil || len(res) == 0 { continue } } if depositerAddr != nil { - res, err = ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + res, err = cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) if err != nil || len(res) == 0 { continue } } - res, err = ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err = cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { continue } @@ -299,6 +300,7 @@ func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { for _, proposal := range matchingProposals { fmt.Printf(" %d - %s\n", proposal.GetProposalID(), proposal.GetTitle()) } + return nil }, } @@ -312,11 +314,13 @@ func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { } // Command to Get a Proposal Information +// GetCmdQueryVote implements the query proposal vote command. func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "query-vote", Short: "query vote", RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) @@ -324,19 +328,19 @@ func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { return err } - ctx := context.NewCoreContextFromViper() - - res, err := ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + res, err := cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) if len(res) == 0 || err != nil { return errors.Errorf("proposalID [%d] does not exist", proposalID) } var vote gov.Vote cdc.MustUnmarshalBinary(res, &vote) + output, err := wire.MarshalJSONIndent(cdc, vote) if err != nil { return err } + fmt.Println(string(output)) return nil }, @@ -348,17 +352,16 @@ func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// Command to Get a Proposal Information +// GetCmdQueryVotes implements the command to query for proposal votes. func GetCmdQueryVotes(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "query-votes", Short: "query votes on a proposal", RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) - ctx := context.NewCoreContextFromViper() - - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if len(res) == 0 || err != nil { return errors.Errorf("proposalID [%d] does not exist", proposalID) } @@ -371,7 +374,7 @@ func GetCmdQueryVotes(storeName string, cdc *wire.Codec) *cobra.Command { return nil } - res2, err := ctx.QuerySubspace(cdc, gov.KeyVotesSubspace(proposalID), storeName) + res2, err := cliCtx.QuerySubspace(gov.KeyVotesSubspace(proposalID), storeName) if err != nil { return err } @@ -389,7 +392,6 @@ func GetCmdQueryVotes(storeName string, cdc *wire.Codec) *cobra.Command { } fmt.Println(string(output)) - return nil }, } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 7efce9f0b4..a410c7791e 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/gorilla/mux" "github.com/pkg/errors" ) @@ -24,10 +25,10 @@ const ( ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { - r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, ctx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, ctx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, ctx)).Methods("POST") +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") @@ -59,7 +60,7 @@ type voteReq struct { Option gov.VoteOption `json:"option"` // option from OptionSet chosen by the voter } -func postProposalHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { +func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq err := buildReq(w, r, cdc, &req) @@ -79,12 +80,11 @@ func postProposalHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.Handle return } - // sign - signAndBuild(w, ctx, req.BaseReq, msg, cdc) + signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) } } -func depositHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { +func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -93,6 +93,7 @@ func depositHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) + return } @@ -100,6 +101,7 @@ func depositHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc if err != nil { err := errors.Errorf("proposalID [%d] is not positive", proposalID) w.Write([]byte(err.Error())) + return } @@ -120,12 +122,11 @@ func depositHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc return } - // sign - signAndBuild(w, ctx, req.BaseReq, msg, cdc) + signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) } } -func voteHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { +func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -134,6 +135,7 @@ func voteHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) + return } @@ -161,8 +163,7 @@ func voteHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc { return } - // sign - signAndBuild(w, ctx, req.BaseReq, msg, cdc) + signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) } } @@ -175,6 +176,7 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) + return } @@ -182,26 +184,31 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { if err != nil { err := errors.Errorf("proposalID [%d] is not positive", proposalID) w.Write([]byte(err.Error())) + return } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) w.Write([]byte(err.Error())) + return } var proposal gov.Proposal cdc.MustUnmarshalBinary(res, &proposal) + output, err := wire.MarshalJSONIndent(cdc, proposal) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) + return } + w.Write(output) } } @@ -216,6 +223,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) + return } @@ -224,6 +232,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("proposalID [%d] is not positive", proposalID) w.Write([]byte(err.Error())) + return } @@ -231,6 +240,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.New("depositer address required but not specified") w.Write([]byte(err.Error())) + return } @@ -239,34 +249,41 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) w.Write([]byte(err.Error())) + return } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + res, err := cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) if err != nil || len(res) == 0 { - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { w.WriteHeader(http.StatusNotFound) err := errors.Errorf("proposalID [%d] does not exist", proposalID) w.Write([]byte(err.Error())) + return } + w.WriteHeader(http.StatusNotFound) err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID) w.Write([]byte(err.Error())) + return } var deposit gov.Deposit cdc.MustUnmarshalBinary(res, &deposit) + output, err := wire.MarshalJSONIndent(cdc, deposit) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) + return } + w.Write(output) } } @@ -289,6 +306,7 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("proposalID [%s] is not positive", proposalID) w.Write([]byte(err.Error())) + return } @@ -304,35 +322,41 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) w.Write([]byte(err.Error())) + return } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + res, err := cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) if err != nil || len(res) == 0 { - - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { w.WriteHeader(http.StatusNotFound) err := errors.Errorf("proposalID [%d] does not exist", proposalID) w.Write([]byte(err.Error())) + return } + w.WriteHeader(http.StatusNotFound) err = errors.Errorf("voter [%s] did not vote on proposalID [%d]", bechVoterAddr, proposalID) w.Write([]byte(err.Error())) + return } var vote gov.Vote cdc.MustUnmarshalBinary(res, &vote) + output, err := wire.MarshalJSONIndent(cdc, vote) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) + return } + w.Write(output) } } @@ -348,6 +372,7 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) + return } @@ -356,15 +381,17 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("proposalID [%s] is not positive", proposalID) w.Write([]byte(err.Error())) + return } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) w.Write([]byte(err.Error())) + return } @@ -374,10 +401,11 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { if proposal.GetStatus() != gov.StatusVotingPeriod { err := errors.Errorf("proposal is not in Voting Period", proposalID) w.Write([]byte(err.Error())) + return } - res2, err := ctx.QuerySubspace(cdc, gov.KeyVotesSubspace(proposalID), storeName) + res2, err := cliCtx.QuerySubspace(gov.KeyVotesSubspace(proposalID), storeName) if err != nil { err = errors.New("ProposalID doesn't exist") w.Write([]byte(err.Error())) @@ -396,8 +424,10 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) + return } + w.Write(output) } } @@ -431,6 +461,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) w.Write([]byte(err.Error())) + return } } @@ -441,18 +472,21 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { w.WriteHeader(http.StatusBadRequest) err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus) w.Write([]byte(err.Error())) + return } } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := ctx.QueryStore(gov.KeyNextProposalID, storeName) + res, err := cliCtx.QueryStore(gov.KeyNextProposalID, storeName) if err != nil { err = errors.New("no proposals exist yet and proposalID has not been set") w.Write([]byte(err.Error())) + return } + var maxProposalID int64 cdc.MustUnmarshalBinary(res, &maxProposalID) @@ -460,20 +494,20 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { for proposalID := int64(0); proposalID < maxProposalID; proposalID++ { if voterAddr != nil { - res, err = ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + res, err = cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) if err != nil || len(res) == 0 { continue } } if depositerAddr != nil { - res, err = ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + res, err = cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) if err != nil || len(res) == 0 { continue } } - res, err = ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + res, err = cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName) if err != nil || len(res) == 0 { continue } @@ -494,8 +528,10 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) + return } + w.Write(output) } } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index 341f0b0577..58d96b591f 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -7,6 +7,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" + "github.com/pkg/errors" ) @@ -67,23 +69,24 @@ func writeErr(w *http.ResponseWriter, status int, msg string) { (*w).Write([]byte(err.Error())) } -// TODO: Build this function out into a more generic base-request (probably should live in client/lcd) -func signAndBuild(w http.ResponseWriter, ctx context.CoreContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { - ctx = ctx.WithAccountNumber(baseReq.AccountNumber) - ctx = ctx.WithSequence(baseReq.Sequence) - ctx = ctx.WithChainID(baseReq.ChainID) +// TODO: Build this function out into a more generic base-request +// (probably should live in client/lcd). +func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { + txCtx := authctx.TxContext{ + Codec: cdc, + AccountNumber: baseReq.AccountNumber, + Sequence: baseReq.Sequence, + ChainID: baseReq.ChainID, + Gas: baseReq.Gas, + } - // add gas to context - ctx = ctx.WithGas(baseReq.Gas) - - txBytes, err := ctx.SignAndBuild(baseReq.Name, baseReq.Password, []sdk.Msg{msg}, cdc) + txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { writeErr(&w, http.StatusUnauthorized, err.Error()) return } - // send - res, err := ctx.BroadcastTx(txBytes) + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { writeErr(&w, http.StatusInternalServerError, err.Error()) return diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index f5505d9a47..f44c736d89 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -2,18 +2,19 @@ package cli import ( "encoding/hex" - - "github.com/spf13/cobra" - "github.com/spf13/viper" + "os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - + "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" - authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/ibc" + + "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( @@ -22,37 +23,35 @@ const ( flagChain = "chain" ) -// IBC transfer command +// IBCTransferCmd implements the IBC transfer command. func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "transfer", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - // get the from address - from, err := ctx.GetFromAddress() + from, err := cliCtx.GetFromAddress() if err != nil { return err } - // build the message msg, err := buildMsg(from) if err != nil { return err } - // get password - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().String(flagTo, "", "Address to send coins") cmd.Flags().String(flagAmount, "", "Amount of coins to send") cmd.Flags().String(flagChain, "", "Destination chain to send coins") + return cmd } diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index 58d8f272b1..92b03a66fc 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -4,17 +4,19 @@ import ( "os" "time" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/tendermint/tendermint/libs/log" - "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/ibc" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/tendermint/libs/log" ) // flags @@ -36,7 +38,7 @@ type relayCommander struct { logger log.Logger } -// IBC relay command +// IBCRelayCmd implements the IBC relay command. func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { cmdr := relayCommander{ cdc: cdc, @@ -77,10 +79,12 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { fromChainNode := viper.GetString(FlagFromChainNode) toChainID := viper.GetString(FlagToChainID) toChainNode := viper.GetString(FlagToChainNode) - address, err := context.NewCoreContextFromViper().GetFromAddress() + + address, err := context.NewCLIContext().GetFromAddress() if err != nil { panic(err) } + c.address = address c.loop(fromChainID, fromChainNode, toChainID, toChainNode) @@ -88,12 +92,10 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { // This is nolinted as someone is in the process of refactoring this to remove the goto // nolint: gocyclo -func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, - toChainNode string) { +func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { + cliCtx := context.NewCLIContext() - ctx := context.NewCoreContextFromViper() - // get password - passphrase, err := ctx.GetPassphraseFromStdin(ctx.FromAddressName) + passphrase, err := keys.ReadPassphraseFromStdin(cliCtx.FromAddressName) if err != nil { panic(err) } @@ -122,12 +124,14 @@ OUTER: c.logger.Error("error querying outgoing packet list length", "err", err) continue OUTER //TODO replace with continue (I think it should just to the correct place where OUTER is now) } + var egressLength int64 if egressLengthbz == nil { egressLength = 0 } else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil { panic(err) } + if egressLength > processed { c.logger.Info("Detected IBC packet", "number", egressLength-1) } @@ -142,7 +146,9 @@ OUTER: } err = c.broadcastTx(seq, toChainNode, c.refine(egressbz, i, passphrase)) + seq++ + if err != nil { c.logger.Error("error broadcasting ingress packet", "err", err) continue OUTER // TODO replace to break, will break first loop then send back to the beginning (aka OUTER) @@ -154,11 +160,11 @@ OUTER: } func query(node string, key []byte, storeName string) (res []byte, err error) { - return context.NewCoreContextFromViper().WithNodeURI(node).QueryStore(key, storeName) + return context.NewCLIContext().WithNodeURI(node).QueryStore(key, storeName) } func (c relayCommander) broadcastTx(seq int64, node string, tx []byte) error { - _, err := context.NewCoreContextFromViper().WithNodeURI(node).WithSequence(seq + 1).BroadcastTx(tx) + _, err := context.NewCLIContext().WithNodeURI(node).BroadcastTx(tx) return err } @@ -167,6 +173,7 @@ func (c relayCommander) getSequence(node string) int64 { if err != nil { panic(err) } + if nil != res { account, err := c.decoder(res) if err != nil { @@ -191,10 +198,13 @@ func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []b Sequence: sequence, } - ctx := context.NewCoreContextFromViper().WithSequence(sequence) - res, err := ctx.SignAndBuild(ctx.FromAddressName, passphrase, []sdk.Msg{msg}, c.cdc) + txCtx := authctx.NewTxContextFromCLI().WithSequence(sequence).WithCodec(c.cdc) + cliCtx := context.NewCLIContext() + + res, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, []sdk.Msg{msg}) if err != nil { panic(err) } + return res } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index bee9f955f4..4470f556e0 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -4,18 +4,19 @@ import ( "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/ibc" + + "github.com/gorilla/mux" ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandlerFn(cdc, kb, ctx)).Methods("POST") +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } type transferBody struct { @@ -31,9 +32,8 @@ type transferBody struct { // TransferRequestHandler - http request handler to transfer coins to a address // on a different chain via IBC -func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // collect data vars := mux.Vars(r) destChainID := vars["destchain"] bech32addr := vars["address"] @@ -52,6 +52,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core w.Write([]byte(err.Error())) return } + err = cdc.UnmarshalJSON(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -70,21 +71,22 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) msg := ibc.IBCTransferMsg{packet} - // add gas to context - ctx = ctx.WithGas(m.Gas) + txCtx := authctx.TxContext{ + Codec: cdc, + ChainID: m.SrcChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + Gas: m.Gas, + } - // sign - ctx = ctx.WithAccountNumber(m.AccountNumber) - ctx = ctx.WithSequence(m.Sequence) - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) return } - // send - res, err := ctx.BroadcastTx(txBytes) + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 0360eb315a..0901eef970 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -13,24 +13,26 @@ import ( "github.com/cosmos/cosmos-sdk/x/slashing" ) -// get the command to query signing info +// GetCmdQuerySigningInfo implements the command to query signing info. func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "signing-info [validator-pubkey]", Short: "Query a validator's signing information", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - pk, err := sdk.GetValPubKeyBech32(args[0]) if err != nil { return err } + key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } + signingInfo := new(slashing.ValidatorSigningInfo) cdc.MustUnmarshalBinary(res, signingInfo) diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index ab8911c119..5085f5aacc 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -1,38 +1,42 @@ package cli import ( - "github.com/spf13/cobra" + "os" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/slashing" + + "github.com/spf13/cobra" ) -// create unrevoke command +// GetCmdUnrevoke implements the create unrevoke validator command. func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unrevoke", Args: cobra.ExactArgs(0), Short: "unrevoke validator previously revoked for downtime", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - validatorAddr, err := ctx.GetFromAddress() + validatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } msg := slashing.NewMsgUnrevoke(validatorAddr) - // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } + return cmd } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 535a642d42..5509ed1a8d 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -4,26 +4,23 @@ import ( "fmt" "net/http" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/gorilla/mux" ) -func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { r.HandleFunc( "/slashing/signing_info/{validator}", - signingInfoHandlerFn(ctx, "slashing", cdc), + signingInfoHandlerFn(cliCtx, "slashing", cdc), ).Methods("GET") } // http request handler to query signing info -func signingInfoHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - // read parameters vars := mux.Vars(r) pk, err := sdk.GetValPubKeyBech32(vars["validator"]) @@ -34,7 +31,8 @@ func signingInfoHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.C } key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) - res, err := ctx.QueryStore(key, storeName) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query signing info. Error: %s", err.Error()))) @@ -42,6 +40,7 @@ func signingInfoHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.C } var signingInfo slashing.ValidatorSigningInfo + err = cdc.UnmarshalBinary(res, &signingInfo) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/x/slashing/client/rest/rest.go b/x/slashing/client/rest/rest.go index 156d400334..7c2fdf9052 100644 --- a/x/slashing/client/rest/rest.go +++ b/x/slashing/client/rest/rest.go @@ -1,15 +1,15 @@ package rest import ( - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/wire" + + "github.com/gorilla/mux" ) // RegisterRoutes registers staking-related REST handlers to a router -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - registerQueryRoutes(ctx, r, cdc) - registerTxRoutes(ctx, r, cdc, kb) +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + registerQueryRoutes(cliCtx, r, cdc) + registerTxRoutes(cliCtx, r, cdc, kb) } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index f358075447..2ecec51ea8 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -7,19 +7,20 @@ import ( "io/ioutil" "net/http" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/slashing" + + "github.com/gorilla/mux" ) -func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { r.HandleFunc( "/slashing/unrevoke", - unrevokeRequestHandlerFn(cdc, kb, ctx), + unrevokeRequestHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } @@ -34,7 +35,7 @@ type UnrevokeBody struct { ValidatorAddr string `json:"validator_addr"` } -func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m UnrevokeBody body, err := ioutil.ReadAll(r.Body) @@ -70,21 +71,24 @@ func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core return } - ctx = ctx.WithGas(m.Gas) - ctx = ctx.WithChainID(m.ChainID) - ctx = ctx.WithAccountNumber(m.AccountNumber) - ctx = ctx.WithSequence(m.Sequence) + txCtx := authctx.TxContext{ + Codec: cdc, + ChainID: m.ChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + Gas: m.Gas, + } msg := slashing.NewMsgUnrevoke(validatorAddr) - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) return } - res, err := ctx.BroadcastTx(txBytes) + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index c2d85d4b1a..5ac303f9fe 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -14,26 +14,28 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// get the command to query a validator +// GetCmdQueryValidator implements the validator query command. func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "validator [owner-addr]", Short: "Query a validator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } + key := stake.GetValidatorKey(addr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } else if len(res) == 0 { return fmt.Errorf("No validator found with address %s", args[0]) } + validator := types.MustUnmarshalValidator(cdc, addr, res) switch viper.Get(cli.OutputFlag) { @@ -50,9 +52,11 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } + fmt.Println(string(output)) } - // TODO output with proofs / machine parseable etc. + + // TODO: output with proofs / machine parseable etc. return nil }, } @@ -60,16 +64,16 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query a validator +// GetCmdQueryValidators implements the query all validators command. func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "validators", Short: "Query for all validators", RunE: func(cmd *cobra.Command, args []string) error { - key := stake.ValidatorsKey - ctx := context.NewCoreContextFromViper() - resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, err := cliCtx.QuerySubspace(key, storeName) if err != nil { return err } @@ -89,6 +93,7 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } + fmt.Println(resp) } case "json": @@ -96,24 +101,25 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } + fmt.Println(string(output)) return nil } - return nil - // TODO output with proofs / machine parseable etc. + // TODO: output with proofs / machine parseable etc. + return nil }, } + return cmd } -// get the command to query a single delegation +// GetCmdQueryDelegation the query delegation command. func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegation", Short: "Query a delegation based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -125,8 +131,9 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { } key := stake.GetDelegationKey(delAddr, valAddr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } @@ -140,39 +147,45 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } + fmt.Println(resp) case "json": output, err := wire.MarshalJSONIndent(cdc, delegation) if err != nil { return err } + fmt.Println(string(output)) return nil } + return nil }, } cmd.Flags().AddFlagSet(fsValidator) cmd.Flags().AddFlagSet(fsDelegator) + return cmd } -// get the command to query all the delegations made from one delegator +// GetCmdQueryDelegations implements the command to query all the delegations +// made from one delegator. func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", Short: "Query all delegations made from one delegator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } + key := stake.GetDelegationsKey(delegatorAddr) - ctx := context.NewCoreContextFromViper() - resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, err := cliCtx.QuerySubspace(key, storeName) if err != nil { return err } @@ -188,22 +201,24 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } - fmt.Println(string(output)) - return nil - // TODO output with proofs / machine parseable etc. + fmt.Println(string(output)) + + // TODO: output with proofs / machine parseable etc. + return nil }, } + return cmd } -// get the command to query a single unbonding-delegation record +// GetCmdQueryUnbondingDelegation implements the command to query a single +// unbonding-delegation record. func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegation", Short: "Query an unbonding-delegation record based on delegator and validator address", RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -215,8 +230,9 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co } key := stake.GetUBDKey(delAddr, valAddr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } @@ -230,39 +246,45 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co if err != nil { return err } + fmt.Println(resp) case "json": output, err := wire.MarshalJSONIndent(cdc, ubd) if err != nil { return err } + fmt.Println(string(output)) return nil } + return nil }, } cmd.Flags().AddFlagSet(fsValidator) cmd.Flags().AddFlagSet(fsDelegator) + return cmd } -// get the command to query all the unbonding-delegation records for a delegator +// GetCmdQueryUnbondingDelegations implements the command to query all the +// unbonding-delegation records for a delegator. func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegations [delegator-addr]", Short: "Query all unbonding-delegations records for one delegator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } + key := stake.GetUBDsKey(delegatorAddr) - ctx := context.NewCoreContextFromViper() - resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, err := cliCtx.QuerySubspace(key, storeName) if err != nil { return err } @@ -278,38 +300,43 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C if err != nil { return err } - fmt.Println(string(output)) - return nil - // TODO output with proofs / machine parseable etc. + fmt.Println(string(output)) + + // TODO: output with proofs / machine parseable etc. + return nil }, } + return cmd } -// get the command to query a single unbonding-delegation record +// GetCmdQueryRedelegation implements the command to query a single +// unbonding-delegation record. func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegation", Short: "Query an unbonding-delegation record based on delegator and validator address", RunE: func(cmd *cobra.Command, args []string) error { - valSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) if err != nil { return err } + valDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) if err != nil { return err } + delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) if err != nil { return err } key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } @@ -323,39 +350,45 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } + fmt.Println(resp) case "json": output, err := wire.MarshalJSONIndent(cdc, red) if err != nil { return err } + fmt.Println(string(output)) return nil } + return nil }, } cmd.Flags().AddFlagSet(fsRedelegation) cmd.Flags().AddFlagSet(fsDelegator) + return cmd } -// get the command to query all the unbonding-delegation records for a delegator +// GetCmdQueryRedelegations implements the command to query all the +// unbonding-delegation records for a delegator. func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbonding-delegations [delegator-addr]", Short: "Query all unbonding-delegations records for one delegator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } + key := stake.GetREDsKey(delegatorAddr) - ctx := context.NewCoreContextFromViper() - resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, err := cliCtx.QuerySubspace(key, storeName) if err != nil { return err } @@ -371,11 +404,13 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command if err != nil { return err } - fmt.Println(string(output)) - return nil - // TODO output with proofs / machine parseable etc. + fmt.Println(string(output)) + + // TODO: output with proofs / machine parseable etc. + return nil }, } + return cmd } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 218c1f5567..4c86777f34 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -2,27 +2,34 @@ package cli import ( "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// create create validator command +// GetCmdCreateValidator implements the create validator command handler. func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "create-validator", Short: "create new validator initialized with a self-delegation to it", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) amounstStr := viper.GetString(FlagAmount) if amounstStr == "" { @@ -32,7 +39,8 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - validatorAddr, err := ctx.GetFromAddress() + + validatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } @@ -41,13 +49,16 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { if len(pkStr) == 0 { return fmt.Errorf("must use --pubkey flag") } + pk, err := sdk.GetValPubKeyBech32(pkStr) if err != nil { return err } + if viper.GetString(FlagMoniker) == "" { return fmt.Errorf("please enter a moniker for the validator using --moniker") } + description := stake.Description{ Moniker: viper.GetString(FlagMoniker), Identity: viper.GetString(FlagIdentity), @@ -61,17 +72,14 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { if err != nil { return err } + msg = stake.NewMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, amount, description) } else { msg = stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) } // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } @@ -79,21 +87,27 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDescriptionCreate) cmd.Flags().AddFlagSet(fsDelegator) + return cmd } -// create edit validator command +// GetCmdEditValidator implements the create edit validator command. func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "edit-validator", Short: "edit and existing validator account", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - validatorAddr, err := ctx.GetFromAddress() + validatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + description := stake.Description{ Moniker: viper.GetString(FlagMoniker), Identity: viper.GetString(FlagIdentity), @@ -103,36 +117,37 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgEditValidator(validatorAddr, description) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsDescriptionEdit) + return cmd } -// delegate command +// GetCmdDelegate implements the delegate command. func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", Short: "delegate liquid tokens to an validator", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err } - delegatorAddr, err := ctx.GetFromAddress() + delegatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -141,51 +156,55 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(delegatorAddr, validatorAddr, amount) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsValidator) + return cmd } -// create edit validator command +// GetCmdRedelegate implements the redelegate validator command. func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "redelegate", Short: "redelegate illiquid tokens from one validator to another", } + cmd.AddCommand( client.PostCommands( GetCmdBeginRedelegate(storeName, cdc), GetCmdCompleteRedelegate(cdc), )...) + return cmd } -// redelegate command +// GetCmdBeginRedelegate the begin redelegation command. func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "begin", Short: "begin redelegation", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) var err error - delegatorAddr, err := ctx.GetFromAddress() + delegatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + validatorSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) if err != nil { return err } + validatorDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) if err != nil { return err @@ -194,8 +213,10 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { // get the shares amount sharesAmountStr := viper.GetString(FlagSharesAmount) sharesPercentStr := viper.GetString(FlagSharesPercent) - sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, - delegatorAddr, validatorSrcAddr) + sharesAmount, err := getShares( + storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorSrcAddr, + ) if err != nil { return err } @@ -203,25 +224,22 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr, sharesAmount) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsShares) cmd.Flags().AddFlagSet(fsRedelegation) + return cmd } // nolint: gocyclo // TODO: Make this pass gocyclo linting -func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercentStr string, - delegatorAddr, validatorAddr sdk.AccAddress) (sharesAmount sdk.Rat, err error) { - +func getShares( + storeName string, cdc *wire.Codec, sharesAmountStr, + sharesPercentStr string, delegatorAddr, validatorAddr sdk.AccAddress, +) (sharesAmount sdk.Rat, err error) { switch { case sharesAmountStr != "" && sharesPercentStr != "": return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") @@ -247,33 +265,44 @@ func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercent // make a query to get the existing delegation shares key := stake.GetDelegationKey(delegatorAddr, validatorAddr) - ctx := context.NewCoreContextFromViper() - resQuery, err := ctx.QueryStore(key, storeName) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + resQuery, err := cliCtx.QueryStore(key, storeName) if err != nil { return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) } + delegation := types.MustUnmarshalDelegation(cdc, key, resQuery) sharesAmount = sharesPercent.Mul(delegation.Shares) } + return } -// redelegate command +// GetCmdCompleteRedelegate implements the complete redelegation command. func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "complete", Short: "complete redelegation", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - delegatorAddr, err := ctx.GetFromAddress() + delegatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + validatorSrcAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) if err != nil { return err } + validatorDstAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) if err != nil { return err @@ -282,44 +311,48 @@ func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd } -// create edit validator command +// GetCmdUnbond implements the unbond validator command. func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbond", Short: "begin or complete unbonding shares from a validator", } + cmd.AddCommand( client.PostCommands( GetCmdBeginUnbonding(storeName, cdc), GetCmdCompleteUnbonding(cdc), )...) + return cmd } -// create edit validator command +// GetCmdBeginUnbonding implements the begin unbonding validator command. func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "begin", Short: "begin unbonding", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - delegatorAddr, err := ctx.GetFromAddress() + delegatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -328,8 +361,10 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { // get the shares amount sharesAmountStr := viper.GetString(FlagSharesAmount) sharesPercentStr := viper.GetString(FlagSharesPercent) - sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, - delegatorAddr, validatorAddr) + sharesAmount, err := getShares( + storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorAddr, + ) if err != nil { return err } @@ -337,32 +372,33 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sharesAmount) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsShares) cmd.Flags().AddFlagSet(fsValidator) + return cmd } -// create edit validator command +// GetCmdCompleteUnbonding implements the complete unbonding validator command. func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "complete", Short: "complete unbonding", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithLogger(os.Stdout). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - delegatorAddr, err := ctx.GetFromAddress() + delegatorAddr, err := cliCtx.GetFromAddress() if err != nil { return err } + validatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -371,14 +407,11 @@ func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) // build and sign the transaction, then broadcast to Tendermint - err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - - return nil + return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } + cmd.Flags().AddFlagSet(fsValidator) + return cmd } diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 9a4c3755dd..0d69c0a2e7 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -4,46 +4,42 @@ import ( "fmt" "net/http" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/types" + + "github.com/gorilla/mux" ) const storeName = "stake" -func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { - +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { r.HandleFunc( "/stake/{delegator}/delegation/{validator}", - delegationHandlerFn(ctx, cdc), + delegationHandlerFn(cliCtx, cdc), ).Methods("GET") r.HandleFunc( "/stake/{delegator}/ubd/{validator}", - ubdHandlerFn(ctx, cdc), + ubdHandlerFn(cliCtx, cdc), ).Methods("GET") r.HandleFunc( "/stake/{delegator}/red/{validator_src}/{validator_dst}", - redHandlerFn(ctx, cdc), + redHandlerFn(cliCtx, cdc), ).Methods("GET") r.HandleFunc( "/stake/validators", - validatorsHandlerFn(ctx, cdc), + validatorsHandlerFn(cliCtx, cdc), ).Methods("GET") } // http request handler to query a delegation -func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func delegationHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - // read parameters vars := mux.Vars(r) bech32delegator := vars["delegator"] bech32validator := vars["validator"] @@ -64,7 +60,7 @@ func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF key := stake.GetDelegationKey(delegatorAddr, validatorAddr) - res, err := ctx.QueryStore(key, storeName) + res, err := cliCtx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) @@ -96,10 +92,8 @@ func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF } // http request handler to query an unbonding-delegation -func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func ubdHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - // read parameters vars := mux.Vars(r) bech32delegator := vars["delegator"] bech32validator := vars["validator"] @@ -120,7 +114,7 @@ func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { key := stake.GetUBDKey(delegatorAddr, validatorAddr) - res, err := ctx.QueryStore(key, storeName) + res, err := cliCtx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) @@ -152,9 +146,8 @@ func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { } // http request handler to query an redelegation -func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func redHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // read parameters vars := mux.Vars(r) bech32delegator := vars["delegator"] @@ -184,7 +177,7 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) - res, err := ctx.QueryStore(key, storeName) + res, err := cliCtx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) @@ -217,9 +210,9 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { // TODO bech32 // http request handler to query list of validators -func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func validatorsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - kvs, err := ctx.QuerySubspace(cdc, stake.ValidatorsKey, storeName) + kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 3528d45e4c..7c2fdf9052 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -1,15 +1,15 @@ package rest import ( - "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/wire" + + "github.com/gorilla/mux" ) // RegisterRoutes registers staking-related REST handlers to a router -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - registerQueryRoutes(ctx, r, cdc) - registerTxRoutes(ctx, r, cdc, kb) +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + registerQueryRoutes(cliCtx, r, cdc) + registerTxRoutes(cliCtx, r, cdc, kb) } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index a0f0416549..0ab52aa631 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -6,21 +6,23 @@ import ( "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/gorilla/mux" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/types" + + "github.com/gorilla/mux" + + ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { r.HandleFunc( "/stake/delegations", - editDelegationsRequestHandlerFn(cdc, kb, ctx), + editDelegationsRequestHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } @@ -67,15 +69,17 @@ type EditDelegationsBody struct { // nolint: gocyclo // TODO: Split this up into several smaller functions, and remove the above nolint -func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m EditDelegationsBody + body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } + err = cdc.UnmarshalJSON(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -105,22 +109,26 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } + messages[i] = stake.MsgDelegate{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, Delegation: msg.Delegation, } + i++ } @@ -131,35 +139,41 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount, types.MaxBondDenominatorPrecision) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) return } + messages[i] = stake.MsgBeginRedelegate{ DelegatorAddr: delegatorAddr, ValidatorSrcAddr: validatorSrcAddr, ValidatorDstAddr: validatorDstAddr, SharesAmount: shares, } + i++ } @@ -170,28 +184,33 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } + messages[i] = stake.MsgCompleteRedelegate{ DelegatorAddr: delegatorAddr, ValidatorSrcAddr: validatorSrcAddr, ValidatorDstAddr: validatorDstAddr, } + i++ } @@ -202,28 +221,33 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount, types.MaxBondDenominatorPrecision) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) return } + messages[i] = stake.MsgBeginUnbonding{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, SharesAmount: shares, } + i++ } @@ -234,36 +258,44 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } + messages[i] = stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, } + i++ } - // add gas to context - ctx = ctx.WithGas(m.Gas) + txCtx := authctx.TxContext{ + Codec: cdc, + ChainID: m.ChainID, + Gas: m.Gas, + } // sign messages signedTxs := make([][]byte, len(messages[:])) for i, msg := range messages { // increment sequence for each message - ctx = ctx.WithAccountNumber(m.AccountNumber) - ctx = ctx.WithSequence(m.Sequence) + txCtx = txCtx.WithAccountNumber(m.AccountNumber) + txCtx = txCtx.WithSequence(m.Sequence) + m.Sequence++ - txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, []sdk.Msg{msg}, cdc) + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -278,12 +310,13 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte // should we have a sdk.MultiMsg type to make sending atomic? results := make([]*ctypes.ResultBroadcastTxCommit, len(signedTxs[:])) for i, txBytes := range signedTxs { - res, err := ctx.BroadcastTx(txBytes) + res, err := cliCtx.BroadcastTx(txBytes) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } + results[i] = res }