Skip to content

Commit

Permalink
Add broadcast command
Browse files Browse the repository at this point in the history
Implement broadcast command/REST endpoint to submit transactions
generated offline with --generated-only and the sign command.

Closes: #1954
  • Loading branch information
Alessio Treglia committed Sep 8, 2018
1 parent 88a2dde commit 4c8cd38
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 21 deletions.
10 changes: 6 additions & 4 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ BREAKING CHANGES
FEATURES

* Gaia REST API (`gaiacli advanced rest-server`)
* [lcd] Endpoints to query staking pool and params
* [lcd] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
* [lcd] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions
* [lcd] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`.
* [gaia-lite] Endpoints to query staking pool and params
* [gaia-lite] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
* [gaia-lite] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions
* [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`.
* [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint.

* Gaia CLI (`gaiacli`)
* [cli] Cmds to query staking pool and params
Expand All @@ -65,6 +66,7 @@ FEATURES
* [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line:
* [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT.
* [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag.
* [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command.

* Gaia
* [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address`
Expand Down
24 changes: 21 additions & 3 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,12 @@ func TestIBCTransfer(t *testing.T) {
// TODO: query ibc egress packet state
}

func TestCoinSendGenerateAndSign(t *testing.T) {
func TestCoinSendGenerateSignAndBroadcast(t *testing.T) {
name, password := "test", "1234567890"
addr, seed := CreateAddr(t, "test", password, GetKeyBase(t))
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
defer cleanup()
acc := getAccount(t, port, addr)

// generate TX
res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true")
Expand All @@ -329,10 +330,10 @@ func TestCoinSendGenerateAndSign(t *testing.T) {
require.Equal(t, msg.Msgs[0].Type(), "bank")
require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr})
require.Equal(t, 0, len(msg.Signatures))
gasEstimate := msg.Fee.Gas

// sign tx
var signedMsg auth.StdTx
acc := getAccount(t, port, addr)
accnum := acc.GetAccountNumber()
sequence := acc.GetSequence()

Expand All @@ -346,13 +347,30 @@ func TestCoinSendGenerateAndSign(t *testing.T) {
}
json, err := cdc.MarshalJSON(payload)
require.Nil(t, err)
res, body = Request(t, port, "POST", "/sign", json)
res, body = Request(t, port, "POST", "/tx/sign", json)
require.Equal(t, http.StatusOK, res.StatusCode, body)
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &signedMsg))
require.Equal(t, len(msg.Msgs), len(signedMsg.Msgs))
require.Equal(t, msg.Msgs[0].Type(), signedMsg.Msgs[0].Type())
require.Equal(t, msg.Msgs[0].GetSigners(), signedMsg.Msgs[0].GetSigners())
require.Equal(t, 1, len(signedMsg.Signatures))

// broadcast tx
broadcastPayload := struct {
Tx auth.StdTx `json:"tx"`
}{Tx: signedMsg}
json, err = cdc.MarshalJSON(broadcastPayload)
require.Nil(t, err)
res, body = Request(t, port, "POST", "/tx/broadcast", json)
require.Equal(t, http.StatusOK, res.StatusCode, body)

// check if tx was committed
var resultTx ctypes.ResultBroadcastTxCommit
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx))
require.Equal(t, uint32(0), resultTx.CheckTx.Code)
require.Equal(t, uint32(0), resultTx.DeliverTx.Code)
require.Equal(t, gasEstimate, resultTx.DeliverTx.GasWanted)
require.Equal(t, gasEstimate, resultTx.DeliverTx.GasUsed)
}

func TestTxs(t *testing.T) {
Expand Down
33 changes: 23 additions & 10 deletions cmd/gaia/cli_test/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
require.Equal(t, " 2 - Apples", proposalsQuery)
}

func TestGaiaCLISendGenerateAndSign(t *testing.T) {
func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
chainID, servAddr, port := initializeFixtures(t)
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)

Expand All @@ -368,26 +368,26 @@ func TestGaiaCLISendGenerateAndSign(t *testing.T) {
require.Equal(t, len(msg.Msgs), 1)
require.Equal(t, 0, len(msg.GetSignatures()))

// Test generate sendTx, estimate gas
// Test generate sendTx with --gas=$amount
success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf(
"gaiacli send %v --amount=10steak --to=%s --from=foo --gas=0 --generate-only",
"gaiacli send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only",
flags, barAddr), []string{}...)
require.True(t, success)
require.NotEmpty(t, stderr)
require.Empty(t, stderr)
msg = unmarshalStdTx(t, stdout)
require.NotZero(t, msg.Fee.Gas)
require.Equal(t, msg.Fee.Gas, int64(100))
require.Equal(t, len(msg.Msgs), 1)
require.Equal(t, 0, len(msg.GetSignatures()))

// Test generate sendTx with --gas=$amount
// Test generate sendTx, estimate gas
success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf(
"gaiacli send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only",
"gaiacli send %v --amount=10steak --to=%s --from=foo --gas=0 --generate-only",
flags, barAddr), []string{}...)
require.True(t, success)
require.Empty(t, stderr)
require.NotEmpty(t, stderr)
msg = unmarshalStdTx(t, stdout)
require.Equal(t, msg.Fee.Gas, int64(100))
require.True(t, msg.Fee.Gas > 0)
require.Equal(t, len(msg.Msgs), 1)
require.Equal(t, 0, len(msg.GetSignatures()))

// Write the output to disk
unsignedTxFile := writeToNewTempFile(t, stdout)
Expand Down Expand Up @@ -417,6 +417,19 @@ func TestGaiaCLISendGenerateAndSign(t *testing.T) {
"gaiacli sign %v --print-sigs %v", flags, signedTxFile.Name()))
require.True(t, success)
require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout)

// Test broadcast
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())

success = executeWrite(t, fmt.Sprintf("gaiacli broadcast %v %v", flags, signedTxFile.Name()))
require.True(t, success)
tests.WaitForNextNBlocksTM(2, port)

barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags))
require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64())
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
}

//___________________________________________________________________________________
Expand Down
1 change: 1 addition & 0 deletions cmd/gaia/cmd/gaiacli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func main() {
rootCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
bankcmd.GetBroadcastCommand(cdc),
)...)

// add proxy, version and key info
Expand Down
44 changes: 41 additions & 3 deletions docs/light/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,11 @@ Returns on success:
"sequence": 7
}
}
}
```

### POST /auth/accounts/sign
### POST /auth/tx/sign

- **URL**: `/auth/sign`
- **URL**: `/auth/tx/sign`
- **Functionality**: Sign a transaction without broadcasting it.
- Returns on success:

Expand Down Expand Up @@ -298,6 +297,45 @@ Returns on success:
}
```

### POST /auth/tx/broadcast

- **URL**: `/auth/broadcast`
- **Functionality**: Broadcast a transaction.
- Returns on success:

```json
{
"rest api": "1.0",
"code": 200,
"error": "",
"result":
{
"check_tx": {
"log": "Msg 0: ",
"gasWanted": "2742",
"gasUsed": "1002"
},
"deliver_tx": {
"log": "Msg 0: ",
"gasWanted": "2742",
"gasUsed": "2742",
"tags": [
{
"key": "c2VuZGVy",
"value": "Y29zbW9zMXdjNTl6ZXU3MmNjdnp5ZWR6ZGE1N3pzcXh2eXZ2Y3poaHBhdDI4"
},
{
"key": "cmVjaXBpZW50",
"value": "Y29zbW9zMTJ4OTNmY3V2azg3M3o1ejZnejRlNTl2dnlxcXp1eDdzdDcwNWd5"
}
]
},
"hash": "784314784503582AC885BD6FB0D2A5B79FF703A7",
"height": "5"
}
}
```

## ICS20 - TokenAPI

The TokenAPI exposes all functionality needed to query account balances and send transactions.
Expand Down
6 changes: 6 additions & 0 deletions docs/sdk/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ gaiacli sign \
unsignedSendTx.json > signedSendTx.json
```

You can broadcast the signed transaction to a node by providing the JSON file to the following command:

```
gaiacli broadcast --node=<node> signedSendTx.json
```

### Staking

#### Set up a Validator
Expand Down
2 changes: 1 addition & 1 deletion x/auth/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, s
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
).Methods("GET")
r.HandleFunc(
"/sign",
"/tx/sign",
SignTxRequestHandlerFn(cdc, cliCtx),
).Methods("POST")
}
Expand Down
46 changes: 46 additions & 0 deletions x/bank/client/cli/broadcast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cli

import (
"io/ioutil"
"os"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"
)

// GetSignCommand returns the sign command
func GetBroadcastCommand(codec *amino.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "broadcast <file>",
Short: "Broadcast transactions generated offline",
Long: `Broadcast transactions created with the --generate-only flag and signed with the sign command.
Read a transaction from <file> and broadcast it to a node.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
cliCtx := context.NewCLIContext().WithCodec(codec).WithLogger(os.Stdout)
stdTx, err := readAndUnmarshalStdTx(cliCtx.Codec, args[0])
if err != nil {
return
}
txBytes, err := cliCtx.Codec.MarshalBinary(stdTx)
if err != nil {
return
}
return cliCtx.EnsureBroadcastTx(txBytes)
},
}
return cmd
}

func readAndUnmarshalStdTx(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) {
var bytes []byte
if bytes, err = ioutil.ReadFile(filename); err != nil {
return
}
if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil {
return
}
return
}
57 changes: 57 additions & 0 deletions x/bank/client/rest/broadcast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package rest

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

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
)

type broadcastBody struct {
Tx auth.StdTx `json:"tx"`
}

// BroadcastTxRequestHandlerFn returns the broadcast tx REST handler
func BroadcastTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var m broadcastBody
if ok := unmarshalBodyOrReturnBadRequest(cliCtx, w, r, &m); !ok {
return
}

txBytes, err := cliCtx.Codec.MarshalBinary(m.Tx)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
res, err := cliCtx.BroadcastTx(txBytes)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

output, err := wire.MarshalJSONIndent(cdc, res)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Write(output)
}
}

func unmarshalBodyOrReturnBadRequest(cliCtx context.CLIContext, w http.ResponseWriter, r *http.Request, m *broadcastBody) bool {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return false
}
err = cliCtx.Codec.UnmarshalJSON(body, m)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return false
}
return true
}
1 change: 1 addition & 0 deletions x/bank/client/rest/sendtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST")
r.HandleFunc("/tx/broadcast", BroadcastTxRequestHandlerFn(cdc, cliCtx)).Methods("POST")
}

type sendBody struct {
Expand Down

0 comments on commit 4c8cd38

Please sign in to comment.