Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST tx endpoint backwards compatibility #6801

Merged
merged 15 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions client/tx/legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 sdk.Tx) (types.StdTx, error) {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
if stdTx, ok := tx.(types.StdTx); ok {
return stdTx, nil
}

sigFeeMemoTx, ok := tx.(signing.SigFeeMemoTx)
if !ok {
return types.StdTx{}, fmt.Errorf("cannot convert %+v to StdTx", tx)
}

aminoTxConfig := types.StdTxConfig{Cdc: codec}
builder := aminoTxConfig.NewTxBuilder()

err := CopyTx(sigFeeMemoTx, 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 {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
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
}
102 changes: 102 additions & 0 deletions client/tx/legacy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package tx_test

import (
"testing"

"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)
}

func TestCopyTx(t *testing.T) {
encCfg := simapp.MakeEncodingConfig()
protoCfg := tx.NewTxConfig(codec.NewProtoCodec(encCfg.InterfaceRegistry), std.DefaultPublicKeyCodec{}, tx.DefaultSignModeHandler())
aminoCfg := types3.StdTxConfig{Cdc: encCfg.Amino}

// proto -> amino -> proto
protoBuilder := protoCfg.NewTxBuilder()
buildTestTx(t, protoBuilder)
aminoBuilder := aminoCfg.NewTxBuilder()
err := tx2.CopyTx(protoBuilder.GetTx(), aminoBuilder)
require.NoError(t, err)
protoBuilder2 := protoCfg.NewTxBuilder()
err = tx2.CopyTx(aminoBuilder.GetTx(), protoBuilder2)
require.NoError(t, err)
bz, err := protoCfg.TxEncoder()(protoBuilder.GetTx())
require.NoError(t, err)
bz2, err := protoCfg.TxEncoder()(protoBuilder2.GetTx())
require.NoError(t, err)
require.Equal(t, bz, bz2)

// amino -> proto -> amino
aminoBuilder = aminoCfg.NewTxBuilder()
buildTestTx(t, aminoBuilder)
protoBuilder = protoCfg.NewTxBuilder()
err = tx2.CopyTx(aminoBuilder.GetTx(), protoBuilder)
require.NoError(t, err)
aminoBuilder2 := aminoCfg.NewTxBuilder()
err = tx2.CopyTx(protoBuilder.GetTx(), aminoBuilder2)
require.NoError(t, err)
bz, err = aminoCfg.TxEncoder()(aminoBuilder.GetTx())
require.NoError(t, err)
bz2, err = aminoCfg.TxEncoder()(aminoBuilder2.GetTx())
require.NoError(t, err)
require.Equal(t, bz, bz2)
}

func TestConvertTxToStdTx(t *testing.T) {
encCfg := simapp.MakeEncodingConfig()
protoCfg := tx.NewTxConfig(codec.NewProtoCodec(encCfg.InterfaceRegistry), std.DefaultPublicKeyCodec{}, tx.DefaultSignModeHandler())

protoBuilder := protoCfg.NewTxBuilder()
buildTestTx(t, protoBuilder)
stdTx, err := tx2.ConvertTxToStdTx(encCfg.Amino, protoBuilder.GetTx())
require.NoError(t, err)
require.Equal(t, memo, stdTx.Memo)
require.Equal(t, gas, stdTx.Fee.Gas)
require.Equal(t, fee, stdTx.Fee.Amount)
require.Equal(t, msg, stdTx.Msgs[0])
require.Equal(t, sig.PubKey.Bytes(), stdTx.Signatures[0].PubKey)
require.Equal(t, sig.Data.(*signing2.SingleSignatureData).Signature, stdTx.Signatures[0].Signature)
}
11 changes: 9 additions & 2 deletions client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,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 @@ -176,7 +178,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 @@ -186,7 +188,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))
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
if rest.CheckInternalServerError(w, err) {
return
}
Expand Down
7 changes: 5 additions & 2 deletions x/auth/client/rest/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,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 := convertAndEncodeStdTx(clientCtx.TxConfig, req.Tx)
if rest.CheckInternalServerError(w, err) {
return
}
Expand All @@ -43,6 +44,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)
}
}
15 changes: 12 additions & 3 deletions x/auth/client/rest/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"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 +33,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 +44,20 @@ 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
}

stdTx, err := clienttx.ConvertTxToStdTx(clientCtx.Codec, tx)
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)
}
}
33 changes: 30 additions & 3 deletions x/auth/client/rest/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"io/ioutil"
"net/http"

sdk "github.com/cosmos/cosmos-sdk/types"

"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,13 +31,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 := convertAndEncodeStdTx(clientCtx.TxConfig, req)
if rest.CheckInternalServerError(w, err) {
return
}
Expand All @@ -42,6 +47,28 @@ 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)
}
}

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 {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
err := tx.CopyTx(stdTx, builder)
if err != nil {
return nil, err
}

theTx = builder.GetTx()
}

return txConfig.TxEncoder()(theTx)
}
Loading