Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST: Cleanup Tx Broadcasting and Encoding #3696

Merged
merged 15 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
* `password` and `generate_only` have been removed from the `base_req` object
* All txs that used to sign or use the Keybase now only generate the tx
* `keys` routes completely removed
* [\#3692] Update tx encoding and broadcasting endpoints:
* Remove duplicate broadcasting endpoints in favor of POST @ `/txs`
* The `Tx` field now accepts a `StdTx` and not raw tx bytes
* Move encoding endpoint to `/txs/encode`

### Gaia CLI

Expand Down
10 changes: 4 additions & 6 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/tests"
Expand Down Expand Up @@ -292,12 +293,9 @@ func TestEncodeTx(t *testing.T) {
var tx auth.StdTx
cdc.UnmarshalJSON([]byte(body), &tx)

// build the request
encodeReq := struct {
Tx auth.StdTx `json:"tx"`
}{Tx: tx}
encodedJSON, _ := cdc.MarshalJSON(encodeReq)
res, body = Request(t, port, "POST", "/tx/encode", encodedJSON)
req := clienttx.EncodeReq{Tx: tx}
encodedJSON, _ := cdc.MarshalJSON(req)
res, body = Request(t, port, "POST", "/txs/encode", encodedJSON)

// Make sure it came back ok, and that we can decode it back to the transaction
// 200 response.
Expand Down
53 changes: 12 additions & 41 deletions client/lcd/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,66 +241,37 @@ paths:
post:
tags:
- ICS0
summary: broadcast Tx
description: broadcast tx with tendermint rpc
summary: Broadcast a signed tx
description: Broadcast a signed tx to a full node
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: txBroadcast
description: Build a StdTx transaction and serilize it to a byte array with amino, then the `"tx"` field in the post body will be the base64 encoding of the byte array. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away).
description: The tx must be a signed StdTx. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away).
required: true
schema:
type: object
properties:
tx:
type: string
$ref: "#/definitions/StdTx"
return:
type: string
example: block
responses:
200:
description: Broadcast tx result
description: Tx broadcasting result
schema:
$ref: "#/definitions/BroadcastTxCommitResult"
500:
description: Internal Server Error
/tx/broadcast:
/txs/encode:
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
post:
tags:
- ICS20
summary: Send a signed Tx
description: Send a signed Tx to a Gaiad full node
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: txBroadcast
description: broadcast tx
required: true
schema:
type: object
properties:
tx:
$ref: "#/definitions/StdTx"
responses:
202:
description: Tx was send and will probably be added to the next block
schema:
$ref: "#/definitions/BroadcastTxCommitResult"
400:
description: The Tx was malformated
500:
description: Server internal error
/tx/encode:
post:
tags:
- ICS20
summary: Encode a transaction to wire format
- ICS0
summary: Encode a transaction to the Amino wire format
description: Encode a transaction (signed or not) from JSON to base64-encoded Amino serialized bytes
consumes:
- application/json
Expand All @@ -309,7 +280,7 @@ paths:
parameters:
- in: body
name: tx
description: The transaction to encode
description: The tx to encode
required: true
schema:
type: object
Expand All @@ -318,15 +289,15 @@ paths:
$ref: "#/definitions/StdTx"
responses:
200:
description: Transaction was successfully decoded and re-encoded
description: The tx was successfully decoded and re-encoded
schema:
type: object
properties:
tx:
type: string
example: The base64-encoded Amino-serialized bytes for the transaction
example: The base64-encoded Amino-serialized bytes for the tx
400:
description: The Tx was malformated
description: The tx was malformated
500:
description: Server internal error
/bank/balances/{address}:
Expand Down
5 changes: 3 additions & 2 deletions client/lcd/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec"
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
Expand Down Expand Up @@ -649,12 +650,12 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {

// POST /tx/broadcast Send a signed Tx
func doBroadcast(t *testing.T, port string, tx auth.StdTx) (*http.Response, string) {
txReq := authrest.BroadcastReq{Tx: tx, Return: "block"}
txReq := clienttx.BroadcastReq{Tx: tx, Return: "block"}

req, err := cdc.MarshalJSON(txReq)
require.Nil(t, err)

return Request(t, port, "POST", "/tx/broadcast", req)
return Request(t, port, "POST", "/txs", req)
}

// doTransfer performs a balance transfer with auto gas calculation. It also signs
Expand Down
80 changes: 68 additions & 12 deletions client/tx/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ package tx

import (
"net/http"
"strings"

"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth"

"io/ioutil"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
)

Expand All @@ -20,44 +27,93 @@ const (
flagBlock = "block"
)

// BroadcastBody Tx Broadcast Body
type BroadcastBody struct {
TxBytes []byte `json:"tx"`
Return string `json:"return"`
// BroadcastReq defines a tx broadcasting request.
type BroadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
}

// BroadcastTxRequest REST Handler
// nolint: gocyclo
// BroadcastTxRequest implements a tx broadcasting handler that is responsible
// for broadcasting a valid and signed tx to a full node. The tx can be
// broadcasted via a sync|async|block mechanism.
func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var m BroadcastBody
var req BroadcastReq

body, err := ioutil.ReadAll(r.Body)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
err = cdc.UnmarshalJSON(body, &m)

err = cdc.UnmarshalJSON(body, &req)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

txBytes, err := cdc.MarshalBinaryLengthPrefixed(req.Tx)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

var res interface{}
switch m.Return {
switch req.Return {
case flagBlock:
res, err = cliCtx.BroadcastTx(m.TxBytes)
res, err = cliCtx.BroadcastTx(txBytes)

case flagSync:
res, err = cliCtx.BroadcastTxSync(m.TxBytes)
res, err = cliCtx.BroadcastTxSync(txBytes)

case flagAsync:
res, err = cliCtx.BroadcastTxAsync(m.TxBytes)
res, err = cliCtx.BroadcastTxAsync(txBytes)

default:
rest.WriteErrorResponse(w, http.StatusInternalServerError,
"unsupported return type. supported types: block, sync, async")
return
}

if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}

// GetBroadcastCommand returns the tx broadcast command.
func GetBroadcastCommand(codec *amino.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "broadcast [file_path]",
Short: "Broadcast transactions generated offline",
Long: strings.TrimSpace(`Broadcast transactions created with the --generate-only
flag and signed with the sign command. Read a transaction from [file_path] and
broadcast it to a node. If you supply a dash (-) argument in place of an input
filename, the command reads from standard input.

$ gaiacli tx broadcast ./mytxn.json
`),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
cliCtx := context.NewCLIContext().WithCodec(codec)
stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0])
if err != nil {
return
}

txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx)
if err != nil {
return
}

res, err := cliCtx.BroadcastTx(txBytes)
cliCtx.PrintOutput(res)
return err
},
}

return client.PostCommands(cmd)[0]
}
107 changes: 107 additions & 0 deletions client/tx/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package tx

import (
"encoding/base64"
"io/ioutil"
"net/http"

"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"

"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/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth"
)

type (
// EncodeReq defines a tx encoding request.
EncodeReq struct {
Tx auth.StdTx `json:"tx"`
}

// EncodeResp defines a tx encoding response.
EncodeResp struct {
Tx string `json:"tx"`
}
)

// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular,
// it takes a json-formatted transaction, encodes it to the Amino wire protocol,
// and responds with base64-encoded bytes.
func EncodeTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req EncodeReq

body, err := ioutil.ReadAll(r.Body)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

err = cdc.UnmarshalJSON(body, &req)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

// re-encode it via the Amino wire protocol
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(req.Tx)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

// base64 encode the encoded tx bytes
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)

response := EncodeResp{Tx: txBytesBase64}
rest.PostProcessResponse(w, cdc, response, cliCtx.Indent)
}
}

// txEncodeRespStr implements a simple Stringer wrapper for a encoded tx.
type txEncodeRespStr string

func (txr txEncodeRespStr) String() string {
return string(txr)
}

// GetEncodeCommand returns the encode command to take a JSONified transaction and turn it into
// Amino-serialized bytes
func GetEncodeCommand(codec *amino.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "encode [file]",
Short: "Encode transactions generated offline",
Long: `Encode transactions created with the --generate-only flag and signed with the sign command.
Read a transaction from <file>, serialize it to the Amino wire protocol, and output it as base64.
If you supply a dash (-) argument in place of an input filename, the command reads from standard input.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
cliCtx := context.NewCLIContext().WithCodec(codec)

stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0])
if err != nil {
return
}

// re-encode it via the Amino wire protocol
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx)
if err != nil {
return err
}

// base64 encode the encoded tx bytes
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)

response := txEncodeRespStr(txBytesBase64)
cliCtx.PrintOutput(response)

return nil
},
}

return client.PostCommands(cmd)[0]
}
Loading