Skip to content

Commit

Permalink
REST tx endpoint backwards compatibility (cosmos#6801)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Aaron Craelius <[email protected]>
  • Loading branch information
3 people authored Jul 30, 2020
1 parent 8283165 commit c7ad21d
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 68 deletions.
79 changes: 79 additions & 0 deletions client/tx/legacy.go
Original file line number Diff line number Diff line change
@@ -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 changes: 145 additions & 0 deletions client/tx/legacy_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 9 additions & 2 deletions client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions testutil/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand Down Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion x/auth/client/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
9 changes: 7 additions & 2 deletions x/auth/client/rest/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -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)
}
}
24 changes: 21 additions & 3 deletions x/auth/client/rest/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
}
Expand All @@ -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)
}
}
Loading

0 comments on commit c7ad21d

Please sign in to comment.