From 91cac96feac96bbdd4e6d91449151b5f1efa90d3 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Wed, 26 Sep 2018 13:29:39 +0000 Subject: [PATCH] Merge PR #2391: LCD Cleanup and DRY Refactor --- client/context/broadcast.go | 158 ++++++++++++++ client/context/query.go | 161 ++------------- client/lcd/lcd_test.go | 80 ++++---- client/tx/broadcast.go | 2 +- client/utils/rest.go | 194 +++++++++++++++++- client/utils/utils.go | 24 ++- examples/democoin/x/cool/client/cli/tx.go | 4 +- examples/democoin/x/pow/client/cli/tx.go | 2 +- .../x/simplestake/client/cli/commands.go | 4 +- x/bank/client/cli/broadcast.go | 5 +- x/bank/client/cli/sendtx.go | 4 +- x/bank/client/rest/sendtx.go | 107 ++-------- x/bank/client/util.go | 4 +- x/gov/client/cli/tx.go | 6 +- x/gov/client/rest/rest.go | 45 ++-- x/gov/client/rest/util.go | 141 ------------- x/ibc/client/cli/ibctx.go | 2 +- x/ibc/client/rest/transfer.go | 99 ++------- x/slashing/client/cli/tx.go | 2 +- x/slashing/client/rest/tx.go | 93 ++------- x/stake/client/cli/tx.go | 14 +- x/stake/client/rest/tx.go | 134 ++++++------ 22 files changed, 596 insertions(+), 689 deletions(-) create mode 100644 client/context/broadcast.go delete mode 100644 x/gov/client/rest/util.go diff --git a/client/context/broadcast.go b/client/context/broadcast.go new file mode 100644 index 0000000000..4e295777c0 --- /dev/null +++ b/client/context/broadcast.go @@ -0,0 +1,158 @@ +package context + +import ( + "fmt" + "io" + + "github.com/pkg/errors" + + abci "github.com/tendermint/tendermint/abci/types" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// TODO: This should get deleted eventually, and perhaps +// ctypes.ResultBroadcastTx be stripped of unused fields, and +// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync. +// +// The motivation is that we want a unified type to return, and the better +// option is the one that can hold CheckTx/DeliverTx responses optionally. +func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit { + return &ctypes.ResultBroadcastTxCommit{ + Hash: res.Hash, + // NOTE: other fields are unused for async. + } +} + +// BroadcastTx 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) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + if ctx.Async { + res, err := ctx.broadcastTxAsync(txBytes) + if err != nil { + return nil, err + } + + resCommit := resultBroadcastTxToCommit(res) + return resCommit, err + } + + return ctx.broadcastTxCommit(txBytes) +} + +// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node +// and waits for a commit. +func (ctx CLIContext) BroadcastTxAndAwaitCommit(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 +} + +func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastTx, error) { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return res, err + } + + if ctx.Logger != nil { + if ctx.JSON { + type toJSON struct { + TxHash string + } + + resJSON := toJSON{res.Hash.String()} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } else { + io.WriteString(ctx.Logger, fmt.Sprintf("async tx sent (tx hash: %s)\n", res.Hash)) + } + } + + return res, nil +} + +func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return res, 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 abci.ResponseDeliverTx + } + + if ctx.Logger != nil { + resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } + + return res, 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 res, nil +} diff --git a/client/context/query.go b/client/context/query.go index 35402fbfbe..d9bef3f5d1 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -2,7 +2,6 @@ package context import ( "fmt" - "io" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -19,7 +18,6 @@ import ( tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" 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 @@ -114,49 +112,6 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) { 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 { @@ -193,92 +148,6 @@ func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { 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 abci.ResponseDeliverTx - } - - if ctx.Logger != nil { - resJSON := toJSON{res.Height, res.Hash.String(), 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 cmn.HexBytes) (res []byte, err error) { @@ -302,7 +171,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } - // Data from trusted node or subspace query doesn't need verification. + // data from trusted node or subspace query doesn't need verification if ctx.TrustNode || !isQueryStoreWithProof(path) { return resp.Value, nil } @@ -315,26 +184,26 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return resp.Value, nil } -// Certify verifies the consensus proof at given height +// Certify verifies the consensus proof at given height. func (ctx CLIContext) Certify(height int64) (lite.Commit, error) { check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Certifier) - if tmliteErr.IsCommitNotFoundErr(err) { + switch { + case tmliteErr.IsCommitNotFoundErr(err): return lite.Commit{}, ErrVerifyCommit(height) - } else if err != nil { + case err != nil: return lite.Commit{}, err } + return check, nil } -// verifyProof perform response proof verification -// nolint: unparam -func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { - +// verifyProof perform response proof verification. +func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { if ctx.Certifier == nil { - return fmt.Errorf("missing valid certifier to verify data from untrusted node") + return fmt.Errorf("missing valid certifier to verify data from distrusted node") } - // AppHash for height H is in header H+1 + // the AppHash for height H is in header H+1 commit, err := ctx.Certify(resp.Height + 1) if err != nil { return err @@ -342,14 +211,16 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { var multiStoreProof store.MultiStoreProof cdc := codec.New() + err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) if err != nil { return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } - // Verify the substore commit hash against trusted appHash + // verify the substore commit hash against trusted appHash substoreCommitHash, err := store.VerifyMultiStoreCommitInfo( - multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash) + multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash, + ) if err != nil { return errors.Wrap(err, "failed in verifying the proof against appHash") } @@ -370,11 +241,12 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ } // isQueryStoreWithProof expects a format like /// -// queryType can be app or store +// queryType can be app or store. func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } + paths := strings.SplitN(path[1:], "/", 3) if len(paths) != 3 { return false @@ -383,5 +255,6 @@ func isQueryStoreWithProof(path string) bool { if store.RequireProof("/" + paths[2]) { return true } + return false } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 30fa314c53..1da8edcc80 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -838,14 +838,16 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc `, gasAdjustment) } jsonStr := []byte(fmt.Sprintf(`{ - %v%v - "name":"%s", - "password":"%s", - "account_number":"%d", - "sequence":"%d", "amount":[%s], - "chain_id":"%s" - }`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID)) + "base_req": { + %v%v + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, coinbz, gasStr, gasAdjustmentStr, name, password, chainID, accnum, sequence)) res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return @@ -877,18 +879,20 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc // send jsonStr := []byte(fmt.Sprintf(`{ - "name":"%s", - "password": "%s", - "account_number":"%d", - "sequence": "%d", - "src_chain_id": "%s", "amount":[ { "denom": "%s", "amount": "1" } - ] - }`, name, password, accnum, sequence, chainID, "steak")) + ], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, "steak", name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -997,11 +1001,6 @@ func doDelegate(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [ { "delegator_addr": "%s", @@ -1012,8 +1011,15 @@ func doDelegate(t *testing.T, port, seed, name, password string, "begin_unbondings": [], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, "steak", amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, "steak", amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1034,11 +1040,6 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [ { @@ -1049,8 +1050,15 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, ], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1072,11 +1080,6 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [], "complete_unbondings": [], @@ -1088,8 +1091,15 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, "shares": "30" } ], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valSrcAddr, valDstAddr)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valSrcAddr, valDstAddr, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 89ad48f43f..a494318255 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -25,7 +25,7 @@ func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - res, err := cliCtx.BroadcastTx([]byte(m.TxBytes)) + res, err := cliCtx.BroadcastTxAndAwaitCommit([]byte(m.TxBytes)) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/utils/rest.go b/client/utils/rest.go index e0fae753db..5e3deb71df 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -2,10 +2,15 @@ package utils import ( "fmt" + "io/ioutil" "net/http" "net/url" "strconv" + "strings" + client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -16,6 +21,9 @@ const ( queryArgGenerateOnly = "generate_only" ) +//---------------------------------------- +// Basic HTTP utilities + // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. func WriteErrorResponse(w http.ResponseWriter, status int, msg string) { @@ -30,24 +38,45 @@ func WriteSimulationResponse(w http.ResponseWriter, gas int64) { w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) } -// HasDryRunArg returns true if the request's URL query contains -// the dry run argument and its value is set to "true". -func HasDryRunArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgDryRun) } +// HasDryRunArg returns true if the request's URL query contains the dry run +// argument and its value is set to "true". +func HasDryRunArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgDryRun) +} -// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter is set to "true". -func HasGenerateOnlyArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgGenerateOnly) } +// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter +// is set to "true". +func HasGenerateOnlyArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgGenerateOnly) +} -// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default -// value if the string is empty. Write +// ParseInt64OrReturnBadRequest converts s to a int64 value. +func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) { + var err error + + n, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid int64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + +// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a +// default value, defaultIfEmpty, if the string is empty. func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { if len(s) == 0 { return defaultIfEmpty, true } + n, err := strconv.ParseFloat(s, 64) if err != nil { WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return n, false } + return n, true } @@ -58,13 +87,164 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, txBldr authtxb.TxBuilder, WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } + output, err := txBldr.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) if err != nil { WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + w.Write(output) return } func urlQueryHasArg(url *url.URL, arg string) bool { return url.Query().Get(arg) == "true" } + +//---------------------------------------- +// Building / Sending utilities + +// BaseReq defines a structure that can be embedded in other request structures +// that all share common "base" fields. +type BaseReq struct { + Name string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas string `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` +} + +// Sanitize performs basic sanitization on a BaseReq object. +func (br BaseReq) Sanitize() BaseReq { + return BaseReq{ + Name: strings.TrimSpace(br.Name), + Password: strings.TrimSpace(br.Password), + ChainID: strings.TrimSpace(br.ChainID), + Gas: strings.TrimSpace(br.Gas), + GasAdjustment: strings.TrimSpace(br.GasAdjustment), + AccountNumber: br.AccountNumber, + Sequence: br.Sequence, + } +} + +/* +ReadRESTReq is a simple convenience wrapper that reads the body and +unmarshals to the req interface. + + Usage: + type SomeReq struct { + BaseReq `json:"base_req"` + CustomField string `json:"custom_field"` + } + + req := new(SomeReq) + err := ReadRESTReq(w, r, cdc, req) +*/ +func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + err = cdc.UnmarshalJSON(body, req) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + return nil +} + +// ValidateBasic performs basic validation of a BaseReq. If custom validation +// logic is needed, the implementing request handler should perform those +// checks manually. +func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { + switch { + case len(br.Name) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") + return false + + case len(br.Password) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") + return false + + case len(br.ChainID) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "chainID required but not specified") + return false + } + + return true +} + +// CompleteAndBroadcastTxREST implements a utility function that facilitates +// sending a series of messages in a signed transaction given a TxBuilder 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. +// +// NOTE: Also see CompleteAndBroadcastTxCli. +// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn. +func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec) { + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + adjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: baseReq.ChainID, + AccountNumber: baseReq.AccountNumber, + Sequence: baseReq.Sequence, + } + + if HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if HasDryRunArg(r) { + WriteSimulationResponse(w, newBldr.Gas) + return + } + + txBldr = newBldr + } + + if HasGenerateOnlyArg(r) { + WriteGenerateStdTxResponse(w, txBldr, msgs) + return + } + + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } + + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + output, err := codec.MarshalJSONIndent(cdc, res) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Write(output) +} diff --git a/client/utils/utils.go b/client/utils/utils.go index 76ee91585e..9e72c552d1 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -14,13 +14,16 @@ import ( "github.com/tendermint/tendermint/libs/common" ) -// SendTx implements a auxiliary handler that facilitates sending a series of -// messages in a signed transaction given a TxBuilder 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(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { - txBldr, err := prepareTxContext(txBldr, cliCtx) +// CompleteAndBroadcastTxCli implements a utility function that +// facilitates sending a series of messages in a signed +// transaction given a TxBuilder 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. +// NOTE: Also see CompleteAndBroadcastTxREST. +func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + txBldr, err := prepareTxBuilder(txBldr, cliCtx) if err != nil { return err } @@ -52,7 +55,8 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return err } // broadcast to a Tendermint node - return cliCtx.EnsureBroadcastTx(txBytes) + _, err = cliCtx.BroadcastTx(txBytes) + return err } // EnrichCtxWithGas calculates the gas estimate that would be consumed by the @@ -161,7 +165,7 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { return simulationResult.GasUsed, nil } -func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { +func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { if err := cliCtx.EnsureAccountExists(); err != nil { return txBldr, err } @@ -196,7 +200,7 @@ func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // buildUnsignedStdTx builds a StdTx as per the parameters passed in the // contexts. Gas is automatically estimated if gas wanted is set to 0. func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - txBldr, err = prepareTxContext(txBldr, cliCtx) + txBldr, err = prepareTxBuilder(txBldr, cliCtx) if err != nil { return } diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index 2f78bba460..db6394e88a 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -34,7 +34,7 @@ func QuizTxCmd(cdc *codec.Codec) *cobra.Command { msg := cool.NewMsgQuiz(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } @@ -59,7 +59,7 @@ func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command { msg := cool.NewMsgSetTrend(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, 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 f1d708c897..4f5dacb1a6 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -53,7 +53,7 @@ func MineCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, 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 d09bf64f37..5d2790e70d 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -68,7 +68,7 @@ func BondTxCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -98,7 +98,7 @@ func UnbondTxCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/cli/broadcast.go b/x/bank/client/cli/broadcast.go index cef88246c4..bf3d529433 100644 --- a/x/bank/client/cli/broadcast.go +++ b/x/bank/client/cli/broadcast.go @@ -29,9 +29,12 @@ in place of an input filename, the command reads from standard input.`, if err != nil { return } - return cliCtx.EnsureBroadcastTx(txBytes) + + _, err = cliCtx.BroadcastTx(txBytes) + return err }, } + return cmd } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 31fe182991..befe6bf00c 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -67,12 +67,12 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - msg := client.BuildMsg(from, to, coins) + msg := client.CreateMsg(from, to, coins) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 2a1e51f0de..e186db773a 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -1,16 +1,13 @@ package rest import ( - "io/ioutil" "net/http" - cliclient "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/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank/client" @@ -23,17 +20,9 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, r.HandleFunc("/tx/broadcast", BroadcastTxRequestHandlerFn(cdc, cliCtx)).Methods("POST") } -type sendBody struct { - // fees is not used currently - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type sendReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } var msgCdc = codec.New() @@ -42,100 +31,36 @@ func init() { bank.RegisterCodec(msgCdc) } -// SendRequestHandlerFn - http request handler to send coins to a address +// SendRequestHandlerFn - http request handler to send coins to a address. func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // collect data vars := mux.Vars(r) - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m sendBody - body, err := ioutil.ReadAll(r.Body) + var req sendReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = msgCdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) - if err != nil { // XXX rechecking same error ? - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - simulateGas, gas, err := cliclient.ReadGasFlag(m.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, newBldr.Gas) - return - } - txBldr = newBldr - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := codec.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + msg := client.CreateMsg(sdk.AccAddress(info.GetPubKey().Address()), to, req.Amount) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/bank/client/util.go b/x/bank/client/util.go index c733517a84..b647f00d3f 100644 --- a/x/bank/client/util.go +++ b/x/bank/client/util.go @@ -5,8 +5,8 @@ import ( bank "github.com/cosmos/cosmos-sdk/x/bank" ) -// build the sendTx msg -func BuildMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { +// create the sendTx msg +func CreateMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewMsgSend([]bank.Input{input}, []bank.Output{output}) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 1bd01b58ab..cb897f43af 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -111,7 +111,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome // 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(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -191,7 +191,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -242,7 +242,7 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 3f62691c74..f66331ad5a 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -41,7 +41,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) } type postProposalReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType gov.ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -50,13 +50,13 @@ type postProposalReq struct { } type depositReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } type voteReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter Option gov.VoteOption `json:"option"` // option from OptionSet chosen by the voter } @@ -64,12 +64,13 @@ type voteReq struct { func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -81,7 +82,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -96,17 +97,19 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req depositReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -118,7 +121,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -133,17 +136,19 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req voteReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -155,7 +160,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -170,7 +175,7 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -209,7 +214,7 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -276,7 +281,7 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -346,7 +351,7 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -412,7 +417,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { params.ProposalStatus = proposalStatus } if len(strNumLatest) != 0 { - numLatest, ok := parseInt64OrReturnBadRequest(strNumLatest, w) + numLatest, ok := utils.ParseInt64OrReturnBadRequest(w, strNumLatest) if !ok { return } @@ -451,7 +456,7 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go deleted file mode 100644 index 0bc1a8804e..0000000000 --- a/x/gov/client/rest/util.go +++ /dev/null @@ -1,141 +0,0 @@ -package rest - -import ( - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "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/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -type baseReq struct { - Name string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` -} - -func buildReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - err = cdc.UnmarshalJSON(body, req) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - return nil -} - -func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { - if len(req.Name) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Name required but not specified") - return false - } - - if len(req.Password) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Password required but not specified") - return false - } - - if len(req.ChainID) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "ChainID required but not specified") - return false - } - - if req.AccountNumber < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Account Number required but not specified") - return false - } - - if req.Sequence < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Sequence required but not specified") - return false - } - return true -} - -// TODO: Build this function out into a more generic base-request -// (probably should live in client/lcd). -func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *codec.Codec) { - simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: baseReq.ChainID, - AccountNumber: baseReq.AccountNumber, - Sequence: baseReq.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, newBldr.Gas) - return - } - txBldr = newBldr - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := codec.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) -} - -func parseInt64OrReturnBadRequest(s string, w http.ResponseWriter) (n int64, ok bool) { - var err error - n, err = strconv.ParseInt(s, 10, 64) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - err := fmt.Errorf("'%s' is not a valid int64", s) - w.Write([]byte(err.Error())) - return 0, false - } - return n, true -} diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index d34d6687b8..f58b178793 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -47,7 +47,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index d151f8aa58..19c9219711 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -1,16 +1,13 @@ package rest import ( - "io/ioutil" "net/http" - "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/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/gorilla/mux" @@ -21,110 +18,48 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } -type transferBody struct { - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - SrcChainID string `json:"src_chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type transferReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } // TransferRequestHandler - http request handler to transfer coins to a address -// on a different chain via IBC +// on a different chain via IBC. func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) destChainID := vars["destchain"] - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m transferBody - body, err := ioutil.ReadAll(r.Body) + var req transferReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } - info, err := kb.Get(m.LocalAccountName) + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) - msg := ibc.IBCTransferMsg{packet} + packet := ibc.NewIBCPacket( + sdk.AccAddress(info.GetPubKey().Address()), to, + req.Amount, baseReq.ChainID, destChainID, + ) + msg := ibc.IBCTransferMsg{IBCPacket: packet} - simulateGas, gas, err := client.ReadGasFlag(m.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: m.SrcChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := cdc.MarshalJSON(res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 9d7e46578c..ad3646ea17 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -36,7 +36,7 @@ func GetCmdUnjail(cdc *codec.Codec) *cobra.Command { if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 94537c2eb3..972d4351fa 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -2,18 +2,14 @@ package rest import ( "bytes" - "encoding/json" "fmt" - "io/ioutil" "net/http" - "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/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/gorilla/mux" @@ -27,98 +23,45 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec } // Unjail TX body -type UnjailBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - ValidatorAddr string `json:"validator_addr"` +type UnjailReq struct { + BaseReq utils.BaseReq `json:"base_req"` + ValidatorAddr string `json:"validator_addr"` } func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m UnjailBody - body, err := ioutil.ReadAll(r.Body) + var req UnjailReq + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = json.Unmarshal(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - valAddr, err := sdk.ValAddressFromBech32(m.ValidatorAddr) + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse( + w, http.StatusInternalServerError, + fmt.Sprintf("failed to decode validator; error: %s", err.Error()), + ) return } if !bytes.Equal(info.GetPubKey().Address(), valAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - Gas: m.Gas, - GasAdjustment: adjustment, - } - msg := slashing.NewMsgUnjail(valAddr) - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := json.MarshalIndent(res, "", " ") - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 74f4c6d6e7..c68c12a5d4 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -94,7 +94,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -150,7 +150,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -193,7 +193,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -265,7 +265,7 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -308,7 +308,7 @@ func GetCmdCompleteRedelegate(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -372,7 +372,7 @@ func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -410,7 +410,7 @@ func GetCmdCompleteUnbonding(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 324e0c914f..ee24322281 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -27,53 +27,55 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec ).Methods("POST") } -type msgDelegationsInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` -} -type msgBeginRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 -} -type msgBeginUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 -} +type ( + msgDelegationsInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Delegation sdk.Coin `json:"delegation"` + } -// the request body for edit delegations -type EditDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - Delegations []msgDelegationsInput `json:"delegations"` - BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` - CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` - BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` - CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` -} + msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + } + + msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + } + + // the request body for edit delegations + EditDelegationsReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` + } +) // TODO: Split this up into several smaller functions, and remove the above nolint // TODO: use sdk.ValAddress instead of sdk.AccAddress for validators in messages +// TODO: Seriously consider how to refactor...do we need to make it multiple txs? +// If not, we can just use CompleteAndBroadcastTxREST. func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m EditDelegationsBody + var req EditDelegationsReq body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -82,14 +84,19 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte return } - err = cdc.UnmarshalJSON(body, &m) + err = cdc.UnmarshalJSON(body, &req) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -97,14 +104,14 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte } // build messages - messages := make([]sdk.Msg, len(m.Delegations)+ - len(m.BeginRedelegates)+ - len(m.CompleteRedelegates)+ - len(m.BeginUnbondings)+ - len(m.CompleteUnbondings)) + messages := make([]sdk.Msg, len(req.Delegations)+ + len(req.BeginRedelegates)+ + len(req.CompleteRedelegates)+ + len(req.BeginUnbondings)+ + len(req.CompleteUnbondings)) i := 0 - for _, msg := range m.Delegations { + for _, msg := range req.Delegations { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -131,7 +138,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.BeginRedelegates { + for _, msg := range req.BeginRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) @@ -170,7 +177,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.CompleteRedelegates { + for _, msg := range req.CompleteRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -203,7 +210,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.BeginUnbondings { + for _, msg := range req.BeginUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -236,7 +243,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.CompleteUnbondings { + for _, msg := range req.CompleteUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -262,41 +269,46 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - simulateGas, gas, err := client.ReadGasFlag(m.Gas) + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) + + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } + txBldr := authtxb.TxBuilder{ Codec: cdc, Gas: gas, GasAdjustment: adjustment, SimulateGas: simulateGas, - ChainID: m.ChainID, + ChainID: baseReq.ChainID, } // sign messages signedTxs := make([][]byte, len(messages[:])) for i, msg := range messages { // increment sequence for each message - txBldr = txBldr.WithAccountNumber(m.AccountNumber) - txBldr = txBldr.WithSequence(m.Sequence) - m.Sequence++ + txBldr = txBldr.WithAccountNumber(baseReq.AccountNumber) + txBldr = txBldr.WithSequence(baseReq.Sequence) + + baseReq.Sequence++ if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { utils.WriteSimulationResponse(w, newBldr.Gas) return } + txBldr = newBldr } @@ -305,7 +317,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte return } - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return