Merge PR #3523: Add tx/encode endpoint and CLI command
This commit is contained in:
parent
d759bef4d1
commit
9348750eb4
|
@ -73,6 +73,7 @@ IMPROVEMENTS
|
||||||
* [\#3423](https://github.com/cosmos/cosmos-sdk/issues/3423) Allow simulation
|
* [\#3423](https://github.com/cosmos/cosmos-sdk/issues/3423) Allow simulation
|
||||||
(auto gas) to work with generate only.
|
(auto gas) to work with generate only.
|
||||||
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) REST server calls to keybase does not lock the underlying storage anymore.
|
* [\#3514](https://github.com/cosmos/cosmos-sdk/pull/3514) REST server calls to keybase does not lock the underlying storage anymore.
|
||||||
|
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `/tx/encode` endpoint to serialize a JSON tx to base64-encoded Amino.
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI (`gaiacli`)
|
||||||
* [\#3476](https://github.com/cosmos/cosmos-sdk/issues/3476) New `withdraw-all-rewards` command to withdraw all delegations rewards for delegators.
|
* [\#3476](https://github.com/cosmos/cosmos-sdk/issues/3476) New `withdraw-all-rewards` command to withdraw all delegations rewards for delegators.
|
||||||
|
@ -80,6 +81,7 @@ IMPROVEMENTS
|
||||||
* [\#3518](https://github.com/cosmos/cosmos-sdk/issues/3518) Fix flow in
|
* [\#3518](https://github.com/cosmos/cosmos-sdk/issues/3518) Fix flow in
|
||||||
`keys add` to show the mnemonic by default.
|
`keys add` to show the mnemonic by default.
|
||||||
* [\#3517](https://github.com/cosmos/cosmos-sdk/pull/3517) Increased test coverage
|
* [\#3517](https://github.com/cosmos/cosmos-sdk/pull/3517) Increased test coverage
|
||||||
|
* [\#3523](https://github.com/cosmos/cosmos-sdk/pull/3523) Added `tx encode` command to serialize a JSON tx to base64-encoded Amino.
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
* [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account
|
* [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package lcd
|
package lcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -423,6 +424,46 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, gasEstimate, resultTx.GasWanted)
|
require.Equal(t, gasEstimate, resultTx.GasWanted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncodeTx(t *testing.T) {
|
||||||
|
// Setup
|
||||||
|
kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, ""))
|
||||||
|
require.NoError(t, err)
|
||||||
|
addr, seed := CreateAddr(t, name1, pw, kb)
|
||||||
|
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Make a transaction to test with
|
||||||
|
res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, "2", 1, false, true, fees)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Make sure it came back ok, and that we can decode it back to the transaction
|
||||||
|
// 200 response
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
encodeResp := struct {
|
||||||
|
Tx string `json:"tx"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// No error decoding the JSON
|
||||||
|
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &encodeResp))
|
||||||
|
|
||||||
|
// Check that the base64 decodes
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(encodeResp.Tx)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Check that the transaction decodes as expected
|
||||||
|
var decodedTx auth.StdTx
|
||||||
|
require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx))
|
||||||
|
require.Equal(t, memo, decodedTx.Memo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTxs(t *testing.T) {
|
func TestTxs(t *testing.T) {
|
||||||
kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, ""))
|
kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, ""))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -334,6 +334,39 @@ paths:
|
||||||
description: The Tx was malformated
|
description: The Tx was malformated
|
||||||
500:
|
500:
|
||||||
description: Server internal error
|
description: Server internal error
|
||||||
|
/tx/encode:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- ICS20
|
||||||
|
summary: Encode a transaction to wire format
|
||||||
|
description: Encode a transaction (signed or not) from JSON to base64-encoded Amino serialized bytes
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: tx
|
||||||
|
description: The transaction to encode
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tx:
|
||||||
|
$ref: "#/definitions/StdTx"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Transaction was successfully decoded and re-encoded
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tx:
|
||||||
|
type: string
|
||||||
|
example: The base64-encoded Amino-serialized bytes for the transaction
|
||||||
|
400:
|
||||||
|
description: The Tx was malformated
|
||||||
|
500:
|
||||||
|
description: Server internal error
|
||||||
/bank/balances/{address}:
|
/bank/balances/{address}:
|
||||||
get:
|
get:
|
||||||
summary: Get the account balances
|
summary: Get the account balances
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package clitest
|
package clitest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -18,6 +20,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||||
"github.com/cosmos/cosmos-sdk/tests"
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
)
|
)
|
||||||
|
@ -587,7 +590,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
// write unsigned tx to file
|
// write unsigned tx to file
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// validate we can successfully sign
|
// validate we can successfully sign
|
||||||
|
@ -600,7 +603,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
|
require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String())
|
||||||
|
|
||||||
// write signed tx to file
|
// write signed tx to file
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// validate signatures
|
// validate signatures
|
||||||
|
@ -610,7 +613,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) {
|
||||||
// modify the transaction
|
// modify the transaction
|
||||||
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
|
stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD"
|
||||||
bz := marshalStdTx(t, stdTx)
|
bz := marshalStdTx(t, stdTx)
|
||||||
modSignedTxFile := writeToNewTempFile(t, string(bz))
|
modSignedTxFile := WriteToNewTempFile(t, string(bz))
|
||||||
defer os.Remove(modSignedTxFile.Name())
|
defer os.Remove(modSignedTxFile.Name())
|
||||||
|
|
||||||
// validate signature validation failure due to different transaction sig bytes
|
// validate signature validation failure due to different transaction sig bytes
|
||||||
|
@ -659,7 +662,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, len(msg.Msgs), 1)
|
require.Equal(t, len(msg.Msgs), 1)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Test sign --validate-signatures
|
// Test sign --validate-signatures
|
||||||
|
@ -676,7 +679,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||||
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Test sign --validate-signatures
|
// Test sign --validate-signatures
|
||||||
|
@ -732,7 +735,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -740,7 +743,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign, not enough signatures
|
// Multisign, not enough signatures
|
||||||
|
@ -748,7 +751,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
@ -760,6 +763,42 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||||
require.False(t, success)
|
require.False(t, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaiaCLIEncode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f := InitFixtures(t)
|
||||||
|
|
||||||
|
// start gaiad server
|
||||||
|
proc := f.GDStart()
|
||||||
|
defer proc.Stop(false)
|
||||||
|
|
||||||
|
cdc := app.MakeCodec()
|
||||||
|
|
||||||
|
// Build a testing transaction and write it to disk
|
||||||
|
barAddr := f.KeyAddress(keyBar)
|
||||||
|
sendTokens := staking.TokensFromTendermintPower(10)
|
||||||
|
success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only", "--memo", "deadbeef")
|
||||||
|
require.True(t, success)
|
||||||
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
|
// Write it to disk
|
||||||
|
jsonTxFile := WriteToNewTempFile(t, stdout)
|
||||||
|
defer os.Remove(jsonTxFile.Name())
|
||||||
|
|
||||||
|
// Run the encode command, and trim the extras from the stdout capture
|
||||||
|
success, base64Encoded, _ := f.TxEncode(jsonTxFile.Name())
|
||||||
|
require.True(t, success)
|
||||||
|
trimmedBase64 := strings.Trim(base64Encoded, "\"\n")
|
||||||
|
|
||||||
|
// Decode the base64
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(trimmedBase64)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Check that the transaction decodes as epxceted
|
||||||
|
var decodedTx auth.StdTx
|
||||||
|
require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx))
|
||||||
|
require.Equal(t, "deadbeef", decodedTx.Memo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
f := InitFixtures(t)
|
f := InitFixtures(t)
|
||||||
|
@ -785,7 +824,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -793,7 +832,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Sign with baz's key
|
// Sign with baz's key
|
||||||
|
@ -801,7 +840,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
bazSignatureFile := writeToNewTempFile(t, stdout)
|
bazSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(bazSignatureFile.Name())
|
defer os.Remove(bazSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign, keys in different order
|
// Multisign, keys in different order
|
||||||
|
@ -810,7 +849,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
@ -848,7 +887,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.Empty(t, stderr)
|
require.Empty(t, stderr)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
unsignedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(unsignedTxFile.Name())
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
// Sign with foo's key
|
// Sign with foo's key
|
||||||
|
@ -856,7 +895,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
fooSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(fooSignatureFile.Name())
|
defer os.Remove(fooSignatureFile.Name())
|
||||||
|
|
||||||
// Sign with bar's key
|
// Sign with bar's key
|
||||||
|
@ -864,7 +903,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
barSignatureFile := writeToNewTempFile(t, stdout)
|
barSignatureFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(barSignatureFile.Name())
|
defer os.Remove(barSignatureFile.Name())
|
||||||
|
|
||||||
// Multisign
|
// Multisign
|
||||||
|
@ -873,7 +912,7 @@ func TestGaiaCLIMultisign(t *testing.T) {
|
||||||
require.True(t, success)
|
require.True(t, success)
|
||||||
|
|
||||||
// Write the output to disk
|
// Write the output to disk
|
||||||
signedTxFile := writeToNewTempFile(t, stdout)
|
signedTxFile := WriteToNewTempFile(t, stdout)
|
||||||
defer os.Remove(signedTxFile.Name())
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
// Validate the multisignature
|
// Validate the multisignature
|
||||||
|
|
|
@ -294,12 +294,18 @@ func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, strin
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxBroadcast is gaiacli tx sign
|
// TxBroadcast is gaiacli tx broadcast
|
||||||
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
|
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
|
||||||
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
|
cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName)
|
||||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxEncode is gaiacli tx encode
|
||||||
|
func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) {
|
||||||
|
cmd := fmt.Sprintf("gaiacli tx encode %v %v", f.Flags(), fileName)
|
||||||
|
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||||
|
}
|
||||||
|
|
||||||
// TxMultisign is gaiacli tx multisign
|
// TxMultisign is gaiacli tx multisign
|
||||||
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
|
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
|
||||||
flags ...string) (bool, string, string) {
|
flags ...string) (bool, string, string) {
|
||||||
|
@ -640,7 +646,8 @@ func queryTags(tags []string) (out string) {
|
||||||
return strings.TrimSuffix(out, "&")
|
return strings.TrimSuffix(out, "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeToNewTempFile(t *testing.T, s string) *os.File {
|
// Write the given string to a new temporary file
|
||||||
|
func WriteToNewTempFile(t *testing.T, s string) *os.File {
|
||||||
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
_, err = fp.WriteString(s)
|
_, err = fp.WriteString(s)
|
||||||
|
|
|
@ -141,7 +141,8 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
authcmd.GetSignCommand(cdc),
|
authcmd.GetSignCommand(cdc),
|
||||||
authcmd.GetMultiSignCommand(cdc),
|
authcmd.GetMultiSignCommand(cdc),
|
||||||
bankcmd.GetBroadcastCommand(cdc),
|
authcmd.GetBroadcastCommand(cdc),
|
||||||
|
authcmd.GetEncodeCommand(cdc),
|
||||||
client.LineBreak,
|
client.LineBreak,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -10,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSignCommand returns the sign command
|
// GetSignCommand returns the sign command
|
||||||
|
@ -27,7 +25,7 @@ $ gaiacli tx broadcast ./mytxn.json
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
cliCtx := context.NewCLIContext().WithCodec(codec)
|
cliCtx := context.NewCLIContext().WithCodec(codec)
|
||||||
stdTx, err := readAndUnmarshalStdTx(cliCtx.Codec, args[0])
|
stdTx, err := authclient.ReadStdTxFromFile(cliCtx.Codec, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,19 +43,3 @@ $ gaiacli tx broadcast ./mytxn.json
|
||||||
|
|
||||||
return client.PostCommands(cmd)[0]
|
return client.PostCommands(cmd)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAndUnmarshalStdTx(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) {
|
|
||||||
var bytes []byte
|
|
||||||
if filename == "-" {
|
|
||||||
bytes, err = ioutil.ReadAll(os.Stdin)
|
|
||||||
} else {
|
|
||||||
bytes, err = ioutil.ReadFile(filename)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
amino "github.com/tendermint/go-amino"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrintOutput requires a Stringer, so we wrap string
|
||||||
|
type encodeResp string
|
||||||
|
|
||||||
|
func (e encodeResp) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := authclient.ReadStdTxFromFile(cliCtx.Codec, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the bytes to base64
|
||||||
|
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
||||||
|
|
||||||
|
// Write it back
|
||||||
|
response := encodeResp(txBytesBase64)
|
||||||
|
cliCtx.PrintOutput(response)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.PostCommands(cmd)[0]
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ recommended to set such parameters manually.
|
||||||
|
|
||||||
func makeMultiSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
func makeMultiSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
||||||
return func(cmd *cobra.Command, args []string) (err error) {
|
return func(cmd *cobra.Command, args []string) (err error) {
|
||||||
stdTx, err := readAndUnmarshalStdTx(cdc, args[0])
|
stdTx, err := authclient.ReadStdTxFromFile(cdc, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cli
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ be generated via the 'multisign' command.
|
||||||
|
|
||||||
func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
||||||
return func(cmd *cobra.Command, args []string) (err error) {
|
return func(cmd *cobra.Command, args []string) (err error) {
|
||||||
stdTx, err := readAndUnmarshalStdTx(cdc, args[0])
|
stdTx, err := authclient.ReadStdTxFromFile(cdc, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -222,14 +222,3 @@ func printAndValidateSigs(
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) ht
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalBodyOrReturnBadRequest(cliCtx context.CLIContext, w http.ResponseWriter, r *http.Request, m *broadcastBody) bool {
|
func unmarshalBodyOrReturnBadRequest(cliCtx context.CLIContext, w http.ResponseWriter, r *http.Request, m interface{}) bool {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
@ -0,0 +1,46 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type encodeReq struct {
|
||||||
|
Tx auth.StdTx `json:"tx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type 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 m encodeReq
|
||||||
|
// Decode the transaction from JSON
|
||||||
|
if ok := unmarshalBodyOrReturnBadRequest(cliCtx, w, r, &m); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-encode it to the wire protocol
|
||||||
|
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx)
|
||||||
|
if err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the bytes to base64
|
||||||
|
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
||||||
|
|
||||||
|
// Write it back
|
||||||
|
response := encodeResp{Tx: txBytesBase64}
|
||||||
|
rest.PostProcessResponse(w, cdc, response, cliCtx.Indent)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,14 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec,
|
||||||
"/bank/balances/{address}",
|
"/bank/balances/{address}",
|
||||||
QueryBalancesRequestHandlerFn(storeName, cdc, context.GetAccountDecoder(cdc), cliCtx),
|
QueryBalancesRequestHandlerFn(storeName, cdc, context.GetAccountDecoder(cdc), cliCtx),
|
||||||
).Methods("GET")
|
).Methods("GET")
|
||||||
|
r.HandleFunc(
|
||||||
|
"/tx/broadcast",
|
||||||
|
BroadcastTxRequestHandlerFn(cdc, cliCtx),
|
||||||
|
).Methods("POST")
|
||||||
|
r.HandleFunc(
|
||||||
|
"/tx/encode",
|
||||||
|
EncodeTxRequestHandlerFn(cdc, cliCtx),
|
||||||
|
).Methods("POST")
|
||||||
r.HandleFunc(
|
r.HandleFunc(
|
||||||
"/tx/sign",
|
"/tx/sign",
|
||||||
SignTxRequestHandlerFn(cdc, cliCtx),
|
SignTxRequestHandlerFn(cdc, cliCtx),
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin.
|
||||||
|
func ReadStdTxFromFile(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) {
|
||||||
|
var bytes []byte
|
||||||
|
if filename == "-" {
|
||||||
|
bytes, err = ioutil.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
bytes, err = ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadStdTxFromFile(t *testing.T) {
|
||||||
|
cdc := codec.New()
|
||||||
|
sdk.RegisterCodec(cdc)
|
||||||
|
|
||||||
|
// Build a test transaction
|
||||||
|
fee := auth.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)})
|
||||||
|
stdTx := auth.NewStdTx([]sdk.Msg{}, fee, []auth.StdSignature{}, "foomemo")
|
||||||
|
|
||||||
|
// Write it to the file
|
||||||
|
encodedTx, _ := cdc.MarshalJSON(stdTx)
|
||||||
|
jsonTxFile := clitest.WriteToNewTempFile(t, string(encodedTx))
|
||||||
|
defer os.Remove(jsonTxFile.Name())
|
||||||
|
|
||||||
|
// Read it back
|
||||||
|
decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name())
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, decodedTx.Memo, "foomemo")
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ import (
|
||||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) {
|
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) {
|
||||||
r.HandleFunc("/bank/accounts/{address}/transfers", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST")
|
r.HandleFunc("/bank/accounts/{address}/transfers", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST")
|
||||||
r.HandleFunc("/tx/broadcast", BroadcastTxRequestHandlerFn(cdc, cliCtx)).Methods("POST")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendReq struct {
|
type sendReq struct {
|
||||||
|
|
Loading…
Reference in New Issue