Merge PR #2391: LCD Cleanup and DRY Refactor

This commit is contained in:
Alexander Bezobchuk 2018-09-26 13:29:39 +00:00 committed by Christopher Goes
parent 145e06b85c
commit 91cac96fea
22 changed files with 596 additions and 689 deletions

158
client/context/broadcast.go Normal file
View File

@ -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
}

View File

@ -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>/<storeName>/<subpath>
// 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
}

View File

@ -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)

View File

@ -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()))

View File

@ -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)
}

View File

@ -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
}

View File

@ -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})
},
}
}

View File

@ -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})
},
}
}

View File

@ -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})
},
}

View File

@ -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
}

View File

@ -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})
},
}

View File

@ -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)
}
}

View File

@ -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})

View File

@ -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})
},
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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})
},
}

View File

@ -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)
}
}

View File

@ -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})
},
}

View File

@ -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)
}
}

View File

@ -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})
},
}

View File

@ -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