From 8f96ec0585a6c868c01a8183f7d27d3c5c1f32af Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 2 Jul 2020 05:16:39 +0200 Subject: [PATCH] REST bank transfers fail due to encoding. (#6536) Bank module REST endpoint bank/accounts/{addr}/transfers was returning invalid StdTx format. --- CHANGELOG.md | 2 + client/tx/tx.go | 2 +- types/rest/rest.go | 21 +++++++ x/bank/client/rest/tx_test.go | 106 ++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 x/bank/client/rest/tx_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb05f3128..f429a8c875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,6 +177,8 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa ### Bug Fixes +* (x/bank) [\#6536](https://github.com/cosmos/cosmos-sdk/pull/6536) Fix bug in `WriteGeneratedTxResponse` function used by multiple +REST endpoints. Now it writes a Tx in StdTx format. * (x/staking) [\#6529](https://github.com/cosmos/cosmos-sdk/pull/6529) Export validator addresses (previously was empty). * (export) [\#6510](https://github.com/cosmos/cosmos-sdk/pull/6510/) Field TimeIotaMs now is included in genesis file while exporting. * (client) [\#6402](https://github.com/cosmos/cosmos-sdk/issues/6402) Fix `keys add` `--algo` flag which only worked for Tendermint's `secp256k1` default key signing algorithm. diff --git a/client/tx/tx.go b/client/tx/tx.go index 69b6bc3809..2093ccea9e 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -188,7 +188,7 @@ func WriteGeneratedTxResponse( return } - output, err := ctx.JSONMarshaler.MarshalJSON(tx) + output, err := ctx.JSONMarshaler.MarshalJSON(tx.GetTx()) if rest.CheckInternalServerError(w, err) { return } diff --git a/types/rest/rest.go b/types/rest/rest.go index 24b6ad9dd5..e9e58dfa6f 100644 --- a/types/rest/rest.go +++ b/types/rest/rest.go @@ -3,6 +3,7 @@ package rest import ( + "bytes" "encoding/json" "errors" "fmt" @@ -442,3 +443,23 @@ func GetRequest(url string) ([]byte, error) { return body, nil } + +// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data. +// An error is returned if the request or reading the body fails. +func PostRequest(url string, contentType string, data []byte) ([]byte, error) { + res, err := http.Post(url, contentType, bytes.NewBuffer(data)) // nolint:gosec + if err != nil { + return nil, fmt.Errorf("error while sending post request: %w", err) + } + + bz, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + + if err = res.Body.Close(); err != nil { + return nil, err + } + + return bz, nil +} diff --git a/x/bank/client/rest/tx_test.go b/x/bank/client/rest/tx_test.go new file mode 100644 index 0000000000..17b394bec0 --- /dev/null +++ b/x/bank/client/rest/tx_test.go @@ -0,0 +1,106 @@ +package rest_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/rest" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func (s *IntegrationTestSuite) TestCoinSend() { + encodingConfig := simapp.MakeEncodingConfig() + authclient.Codec = encodingConfig.Marshaler + + val := s.network.Validators[0] + + account, err := getAccountInfo(val) + s.Require().NoError(err) + + sendReq := generateSendReq( + account, + types.Coins{types.NewCoin(s.cfg.BondDenom, types.TokensFromConsensusPower(1))}, + ) + + stdTx, err := submitSendReq(val, sendReq) + s.Require().NoError(err) + + s.Require().Nil(stdTx.Signatures) + s.Require().Equal([]types.Msg{ + &banktypes.MsgSend{ + FromAddress: account.GetAddress(), + ToAddress: account.GetAddress(), + Amount: sendReq.Amount, + }, + }, stdTx.GetMsgs()) +} + +func submitSendReq(val *testutil.Validator, req bankrest.SendReq) (authtypes.StdTx, error) { + url := fmt.Sprintf("%s/bank/accounts/%s/transfers", val.APIAddress, val.Address) + + bz, err := val.ClientCtx.JSONMarshaler.MarshalJSON(req) + if err != nil { + return authtypes.StdTx{}, errors.Wrap(err, "error encoding SendReq to json") + } + + res, err := rest.PostRequest(url, "application/json", bz) + if err != nil { + return authtypes.StdTx{}, err + } + + var tx authtypes.StdTx + err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(res, &tx) + if err != nil { + return authtypes.StdTx{}, errors.Wrap(err, "error unmarshaling to StdTx SendReq response") + } + + return tx, nil +} + +func generateSendReq(from authtypes.AccountI, amount types.Coins) bankrest.SendReq { + baseReq := rest.NewBaseReq( + from.GetAddress().String(), + "someMemo", + "some-id", + "10000", + fmt.Sprintf("%f", 1.0), + from.GetAccountNumber(), + from.GetSequence(), + types.NewCoins(), + nil, + false, + ) + + return bankrest.SendReq{ + BaseReq: baseReq, + Amount: amount, + } +} + +func getAccountInfo(val *testutil.Validator) (authtypes.AccountI, error) { + url := fmt.Sprintf("%s/auth/accounts/%s", val.APIAddress, val.Address) + + resp, err := rest.GetRequest(url) + if err != nil { + return nil, err + } + + bz, err := rest.ParseResponseWithHeight(val.ClientCtx.JSONMarshaler, resp) + if err != nil { + return nil, err + } + + var acc authtypes.AccountI + err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(bz, &acc) + if err != nil { + return nil, err + } + + return acc, nil +}