REST tx endpoint backwards compatibility (#6801)

* update rest endpoints

* Add conversion logic for auth encode/decode/broadcast

* Cleanup

* Add tx conversion to legacy REST tx endpoints.

* Cleanup

* Add tests

* Add tests for ConvertAndEncodeStdTx

* Fix for StdSignature

* Test coverage improvements

* Add integration test for REST broadcast

Co-authored-by: Aaron Craelius <aaronc@users.noreply.github.com>
Co-authored-by: Aaron Craelius <aaron@regen.network>
This commit is contained in:
SaReN 2020-07-30 20:15:18 +05:30 committed by GitHub
parent 8283165600
commit c7ad21d162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 402 additions and 68 deletions

79
client/tx/legacy.go Normal file
View File

@ -0,0 +1,79 @@
package tx
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)
// ConvertTxToStdTx converts a transaction to the legacy StdTx format
func ConvertTxToStdTx(codec *codec.Codec, tx signing.SigFeeMemoTx) (types.StdTx, error) {
if stdTx, ok := tx.(types.StdTx); ok {
return stdTx, nil
}
aminoTxConfig := types.StdTxConfig{Cdc: codec}
builder := aminoTxConfig.NewTxBuilder()
err := CopyTx(tx, builder)
if err != nil {
return types.StdTx{}, err
}
stdTx, ok := builder.GetTx().(types.StdTx)
if !ok {
return types.StdTx{}, fmt.Errorf("expected %T, got %+v", types.StdTx{}, builder.GetTx())
}
return stdTx, nil
}
// CopyTx copies a SigFeeMemoTx to a new TxBuilder, allowing conversion between
// different transaction formats.
func CopyTx(tx signing.SigFeeMemoTx, builder client.TxBuilder) error {
err := builder.SetMsgs(tx.GetMsgs()...)
if err != nil {
return err
}
sigs, err := tx.GetSignaturesV2()
if err != nil {
return err
}
err = builder.SetSignatures(sigs...)
if err != nil {
return err
}
builder.SetMemo(tx.GetMemo())
builder.SetFeeAmount(tx.GetFee())
builder.SetGasLimit(tx.GetGas())
return nil
}
func ConvertAndEncodeStdTx(txConfig client.TxConfig, stdTx types.StdTx) ([]byte, error) {
builder := txConfig.NewTxBuilder()
var theTx sdk.Tx
// check if we need a StdTx anyway, in that case don't copy
if _, ok := builder.GetTx().(types.StdTx); ok {
theTx = stdTx
} else {
err := CopyTx(stdTx, builder)
if err != nil {
return nil, err
}
theTx = builder.GetTx()
}
return txConfig.TxEncoder()(theTx)
}

145
client/tx/legacy_test.go Normal file
View File

@ -0,0 +1,145 @@
package tx_test
import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/stretchr/testify/require"
tx2 "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/std"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
types3 "github.com/cosmos/cosmos-sdk/x/auth/types"
signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
types2 "github.com/cosmos/cosmos-sdk/x/bank/types"
)
const (
memo = "waboom"
gas = uint64(10000)
)
var (
fee = types.NewCoins(types.NewInt64Coin("bam", 100))
_, pub1, addr1 = testdata.KeyTestPubAddr()
_, _, addr2 = testdata.KeyTestPubAddr()
msg = types2.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 10000)))
sig = signing2.SignatureV2{
PubKey: pub1,
Data: &signing2.SingleSignatureData{
SignMode: signing2.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
Signature: []byte("dummy"),
},
}
)
func buildTestTx(t *testing.T, builder client.TxBuilder) {
builder.SetMemo(memo)
builder.SetGasLimit(gas)
builder.SetFeeAmount(fee)
err := builder.SetMsgs(msg)
require.NoError(t, err)
err = builder.SetSignatures(sig)
require.NoError(t, err)
}
type TestSuite struct {
suite.Suite
encCfg params.EncodingConfig
protoCfg client.TxConfig
aminoCfg client.TxConfig
}
func (s *TestSuite) SetupSuite() {
encCfg := simapp.MakeEncodingConfig()
s.encCfg = encCfg
s.protoCfg = tx.NewTxConfig(codec.NewProtoCodec(encCfg.InterfaceRegistry), std.DefaultPublicKeyCodec{}, tx.DefaultSignModeHandler())
s.aminoCfg = types3.StdTxConfig{Cdc: encCfg.Amino}
}
func (s *TestSuite) TestCopyTx() {
// proto -> amino -> proto
protoBuilder := s.protoCfg.NewTxBuilder()
buildTestTx(s.T(), protoBuilder)
aminoBuilder := s.aminoCfg.NewTxBuilder()
err := tx2.CopyTx(protoBuilder.GetTx(), aminoBuilder)
s.Require().NoError(err)
protoBuilder2 := s.protoCfg.NewTxBuilder()
err = tx2.CopyTx(aminoBuilder.GetTx(), protoBuilder2)
s.Require().NoError(err)
bz, err := s.protoCfg.TxEncoder()(protoBuilder.GetTx())
s.Require().NoError(err)
bz2, err := s.protoCfg.TxEncoder()(protoBuilder2.GetTx())
s.Require().NoError(err)
s.Require().Equal(bz, bz2)
// amino -> proto -> amino
aminoBuilder = s.aminoCfg.NewTxBuilder()
buildTestTx(s.T(), aminoBuilder)
protoBuilder = s.protoCfg.NewTxBuilder()
err = tx2.CopyTx(aminoBuilder.GetTx(), protoBuilder)
s.Require().NoError(err)
aminoBuilder2 := s.aminoCfg.NewTxBuilder()
err = tx2.CopyTx(protoBuilder.GetTx(), aminoBuilder2)
s.Require().NoError(err)
bz, err = s.aminoCfg.TxEncoder()(aminoBuilder.GetTx())
s.Require().NoError(err)
bz2, err = s.aminoCfg.TxEncoder()(aminoBuilder2.GetTx())
s.Require().NoError(err)
s.Require().Equal(bz, bz2)
}
func (s *TestSuite) TestConvertTxToStdTx() {
// proto tx
protoBuilder := s.protoCfg.NewTxBuilder()
buildTestTx(s.T(), protoBuilder)
stdTx, err := tx2.ConvertTxToStdTx(s.encCfg.Amino, protoBuilder.GetTx())
s.Require().NoError(err)
s.Require().Equal(memo, stdTx.Memo)
s.Require().Equal(gas, stdTx.Fee.Gas)
s.Require().Equal(fee, stdTx.Fee.Amount)
s.Require().Equal(msg, stdTx.Msgs[0])
s.Require().Equal(sig.PubKey, stdTx.Signatures[0].PubKey)
s.Require().Equal(sig.Data.(*signing2.SingleSignatureData).Signature, stdTx.Signatures[0].Signature)
// std tx
aminoBuilder := s.aminoCfg.NewTxBuilder()
buildTestTx(s.T(), aminoBuilder)
stdTx = aminoBuilder.GetTx().(types3.StdTx)
stdTx2, err := tx2.ConvertTxToStdTx(s.encCfg.Amino, stdTx)
s.Require().NoError(err)
s.Require().Equal(stdTx, stdTx2)
}
func (s *TestSuite) TestConvertAndEncodeStdTx() {
// convert amino -> proto -> amino
aminoBuilder := s.aminoCfg.NewTxBuilder()
buildTestTx(s.T(), aminoBuilder)
stdTx := aminoBuilder.GetTx().(types3.StdTx)
txBz, err := tx2.ConvertAndEncodeStdTx(s.protoCfg, stdTx)
s.Require().NoError(err)
decodedTx, err := s.protoCfg.TxDecoder()(txBz)
s.Require().NoError(err)
aminoBuilder2 := s.aminoCfg.NewTxBuilder()
s.Require().NoError(tx2.CopyTx(decodedTx.(signing.SigFeeMemoTx), aminoBuilder2))
s.Require().Equal(stdTx, aminoBuilder2.GetTx())
// just use amino everywhere
txBz, err = tx2.ConvertAndEncodeStdTx(s.aminoCfg, stdTx)
s.Require().NoError(err)
decodedTx, err = s.aminoCfg.TxDecoder()(txBz)
s.Require().NoError(err)
s.Require().Equal(stdTx, decodedTx)
}

View File

@ -138,6 +138,8 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
// WriteGeneratedTxResponse writes a generated unsigned transaction to the
// provided http.ResponseWriter. It will simulate gas costs if requested by the
// BaseReq. Upon any error, the error will be written to the http.ResponseWriter.
// Note that this function returns the legacy StdTx Amino JSON format for compatibility
// with legacy clients.
func WriteGeneratedTxResponse(
ctx client.Context, w http.ResponseWriter, br rest.BaseReq, msgs ...sdk.Msg,
) {
@ -175,7 +177,7 @@ func WriteGeneratedTxResponse(
txf = txf.WithGas(adjusted)
if br.Simulate {
rest.WriteSimulationResponse(w, ctx.JSONMarshaler, txf.Gas())
rest.WriteSimulationResponse(w, ctx.Codec, txf.Gas())
return
}
}
@ -185,7 +187,12 @@ func WriteGeneratedTxResponse(
return
}
output, err := ctx.JSONMarshaler.MarshalJSON(tx.GetTx())
stdTx, err := ConvertTxToStdTx(ctx.Codec, tx.GetTx())
if rest.CheckInternalServerError(w, err) {
return
}
output, err := ctx.Codec.MarshalJSON(stdTx)
if rest.CheckInternalServerError(w, err) {
return
}

View File

@ -62,6 +62,7 @@ func NewSimApp(val Validator) servertypes.Application {
// in-process local testing network.
type Config struct {
Codec codec.Marshaler
LegacyAmino *codec.Codec
TxConfig client.TxConfig
AccountRetriever client.AccountRetriever
AppConstructor AppConstructor // the ABCI application constructor
@ -88,6 +89,7 @@ func DefaultConfig() Config {
return Config{
Codec: encCfg.Marshaler,
TxConfig: encCfg.TxConfig,
LegacyAmino: encCfg.Amino,
AccountRetriever: authtypes.NewAccountRetriever(encCfg.Marshaler),
AppConstructor: NewSimApp,
GenesisState: simapp.ModuleBasics.DefaultGenesis(encCfg.Marshaler),
@ -312,6 +314,7 @@ func New(t *testing.T, cfg Config) *Network {
WithHomeDir(tmCfg.RootDir).
WithChainID(cfg.ChainID).
WithJSONMarshaler(cfg.Codec).
WithCodec(cfg.LegacyAmino).
WithTxConfig(cfg.TxConfig).
WithAccountRetriever(cfg.AccountRetriever)

View File

@ -51,7 +51,8 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, clientCtx client.Context,
return
}
output, err := clientCtx.JSONMarshaler.MarshalJSON(types.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
// NOTE: amino is used intentionally here, don't migrate it
output, err := clientCtx.Codec.MarshalJSON(types.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
if rest.CheckInternalServerError(w, err) {
return
}

View File

@ -4,6 +4,8 @@ import (
"io/ioutil"
"net/http"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth/types"
@ -27,11 +29,12 @@ func BroadcastTxRequest(clientCtx client.Context) http.HandlerFunc {
return
}
if err := clientCtx.JSONMarshaler.UnmarshalJSON(body, &req); rest.CheckBadRequestError(w, err) {
// NOTE: amino is used intentionally here, don't migrate it!
if err := clientCtx.Codec.UnmarshalJSON(body, &req); rest.CheckBadRequestError(w, err) {
return
}
txBytes, err := clientCtx.Codec.MarshalBinaryBare(req.Tx)
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req.Tx)
if rest.CheckInternalServerError(w, err) {
return
}
@ -43,6 +46,8 @@ func BroadcastTxRequest(clientCtx client.Context) http.HandlerFunc {
return
}
// NOTE: amino is set intentionally here, don't migrate it!
clientCtx = clientCtx.WithJSONMarshaler(clientCtx.Codec)
rest.PostProcessResponseBare(w, clientCtx, res)
}
}

View File

@ -2,10 +2,14 @@ package rest
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/client"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/types/rest"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
@ -32,7 +36,8 @@ func DecodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
return
}
err = clientCtx.JSONMarshaler.UnmarshalJSON(body, &req)
// NOTE: amino is used intentionally here, don't migrate it
err = clientCtx.Codec.UnmarshalJSON(body, &req)
if rest.CheckBadRequestError(w, err) {
return
}
@ -42,13 +47,26 @@ func DecodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
return
}
var stdTx authtypes.StdTx
err = clientCtx.Codec.UnmarshalBinaryBare(txBytes, &stdTx)
tx, err := clientCtx.TxConfig.TxDecoder()(txBytes)
if rest.CheckBadRequestError(w, err) {
return
}
sigFeeMemoTx, ok := tx.(signing.SigFeeMemoTx)
if !ok {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("%+v is not backwards compatible with %T", tx, authtypes.StdTx{}))
return
}
stdTx, err := clienttx.ConvertTxToStdTx(clientCtx.Codec, sigFeeMemoTx)
if rest.CheckBadRequestError(w, err) {
return
}
response := DecodeResp(stdTx)
// NOTE: amino is set intentionally here, don't migrate it
clientCtx = clientCtx.WithJSONMarshaler(clientCtx.Codec)
rest.PostProcessResponse(w, clientCtx, response)
}
}

View File

@ -5,6 +5,8 @@ import (
"io/ioutil"
"net/http"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth/types"
@ -27,13 +29,14 @@ func EncodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
return
}
err = clientCtx.JSONMarshaler.UnmarshalJSON(body, &req)
// NOTE: amino is used intentionally here, don't migrate it
err = clientCtx.Codec.UnmarshalJSON(body, &req)
if rest.CheckBadRequestError(w, err) {
return
}
// re-encode it via the Amino wire protocol
txBytes, err := clientCtx.Codec.MarshalBinaryBare(req)
// re-encode it in the chain's native binary format
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req)
if rest.CheckInternalServerError(w, err) {
return
}
@ -42,6 +45,8 @@ func EncodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
response := EncodeResp{Tx: txBytesBase64}
// NOTE: amino is set intentionally here, don't migrate it
rest.PostProcessResponseBare(w, clientCtx, response)
}
}

View File

@ -0,0 +1,124 @@
package rest_test
import (
"fmt"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
rest2 "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
"github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/types/rest"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/testutil/network"
)
type IntegrationTestSuite struct {
suite.Suite
cfg network.Config
network *network.Network
}
func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
cfg := network.DefaultConfig()
cfg.NumValidators = 1
s.cfg = cfg
s.network = network.New(s.T(), cfg)
_, err := s.network.WaitForHeight(1)
s.Require().NoError(err)
}
func (s *IntegrationTestSuite) TearDownSuite() {
s.T().Log("tearing down integration test suite")
s.network.Cleanup()
}
func (s *IntegrationTestSuite) TestEncodeDecode() {
val := s.network.Validators[0]
// NOTE: this uses StdTx explicitly, don't migrate it!
stdTx := authtypes.StdTx{
Msgs: []sdk.Msg{&types.MsgSend{}},
Fee: authtypes.StdFee{
Amount: sdk.Coins{sdk.NewInt64Coin("foo", 10)},
Gas: 10000,
},
Memo: "FOOBAR",
}
// NOTE: this uses amino explicitly, don't migrate it!
cdc := val.ClientCtx.Codec
bz, err := cdc.MarshalJSON(stdTx)
s.Require().NoError(err)
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/encode", val.APIAddress), "application/json", bz)
s.Require().NoError(err)
var encodeResp rest2.EncodeResp
err = cdc.UnmarshalJSON(res, &encodeResp)
s.Require().NoError(err)
bz, err = cdc.MarshalJSON(rest2.DecodeReq{Tx: encodeResp.Tx})
s.Require().NoError(err)
res, err = rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
s.Require().NoError(err)
var respWithHeight rest.ResponseWithHeight
err = cdc.UnmarshalJSON(res, &respWithHeight)
s.Require().NoError(err)
var decodeResp rest2.DecodeResp
err = cdc.UnmarshalJSON(respWithHeight.Result, &decodeResp)
s.Require().NoError(err)
s.Require().Equal(stdTx, authtypes.StdTx(decodeResp))
}
func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
// NOTE: this uses StdTx explicitly, don't migrate it!
stdTx := authtypes.StdTx{
Msgs: []sdk.Msg{&types.MsgSend{}},
Fee: authtypes.StdFee{
Amount: sdk.Coins{sdk.NewInt64Coin("foo", 10)},
Gas: 10000,
},
Memo: "FOOBAR",
}
// we just test with async mode because this tx will fail - all we care about is that it got encoded and broadcast correctly
res, err := s.broadcastReq(stdTx, "async")
s.Require().NoError(err)
var txRes sdk.TxResponse
// NOTE: this uses amino explicitly, don't migrate it!
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
// we just check for a non-empty TxHash here, the actual hash will depend on the underlying tx configuration
s.Require().NotEmpty(txRes.TxHash)
}
func (s *IntegrationTestSuite) broadcastReq(stdTx authtypes.StdTx, mode string) ([]byte, error) {
val := s.network.Validators[0]
// NOTE: this uses amino explicitly, don't migrate it!
cdc := val.ClientCtx.Codec
req := rest2.BroadcastReq{
Tx: stdTx,
Mode: mode,
}
bz, err := cdc.MarshalJSON(req)
s.Require().NoError(err)
return rest.PostRequest(fmt.Sprintf("%s/txs", val.APIAddress), "application/json", bz)
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -14,17 +14,3 @@ func RegisterHandlers(clientCtx client.Context, r *mux.Router) {
r.HandleFunc("/bank/total", totalSupplyHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc("/bank/total/{denom}", supplyOfHandlerFn(clientCtx)).Methods("GET")
}
// ---------------------------------------------------------------------------
// Deprecated
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ---------------------------------------------------------------------------
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(clientCtx client.Context, r *mux.Router) {
r.HandleFunc("/bank/accounts/{address}/transfers", SendRequestHandlerFn(clientCtx)).Methods("POST")
r.HandleFunc("/bank/balances/{address}", QueryBalancesRequestHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc("/bank/total", totalSupplyHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc("/bank/total/{denom}", supplyOfHandlerFn(clientCtx)).Methods("GET")
}

View File

@ -9,7 +9,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
@ -50,43 +49,3 @@ func NewSendRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
}
}
// ---------------------------------------------------------------------------
// Deprecated
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ---------------------------------------------------------------------------
// SendRequestHandlerFn - http request handler to send coins to a address.
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ref: https://github.com/cosmos/cosmos-sdk/issues/5864
func SendRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bech32Addr := vars["address"]
toAddr, err := sdk.AccAddressFromBech32(bech32Addr)
if rest.CheckBadRequestError(w, err) {
return
}
var req SendReq
if !rest.ReadRESTReq(w, r, clientCtx.Codec, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
if rest.CheckBadRequestError(w, err) {
return
}
msg := types.NewMsgSend(fromAddr, toAddr, req.Amount)
authclient.WriteGenerateStdTxResponse(w, clientCtx, req.BaseReq, []sdk.Msg{msg})
}
}

View File

@ -44,7 +44,8 @@ func (s *IntegrationTestSuite) TestCoinSend() {
func submitSendReq(val *network.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)
// NOTE: this uses amino explicitly, don't migrate it!
bz, err := val.ClientCtx.Codec.MarshalJSON(req)
if err != nil {
return authtypes.StdTx{}, errors.Wrap(err, "error encoding SendReq to json")
}
@ -55,7 +56,8 @@ func submitSendReq(val *network.Validator, req bankrest.SendReq) (authtypes.StdT
}
var tx authtypes.StdTx
err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(res, &tx)
// NOTE: this uses amino explicitly, don't migrate it!
err = val.ClientCtx.Codec.UnmarshalJSON(res, &tx)
if err != nil {
return authtypes.StdTx{}, errors.Wrap(err, "error unmarshaling to StdTx SendReq response")
}