From 8bd9288051d464a93d1101b189d7cb00c53beef1 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 9 Feb 2023 10:01:27 -0500 Subject: [PATCH] feat(x/tx): add basic handler types + sign mode direct (#14787) Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com> --- x/tx/signing/direct/direct.go | 31 ++++++++++ x/tx/signing/direct/direct_test.go | 99 ++++++++++++++++++++++++++++++ x/tx/signing/handler_map.go | 45 ++++++++++++++ x/tx/signing/sign_mode_handler.go | 38 +++--------- x/tx/signing/signer_data.go | 34 ++++++++++ x/tx/signing/std/handler_map.go | 28 +++++++++ x/tx/signing/tx_data.go | 23 +++++++ x/tx/textual/any.go | 4 +- x/tx/textual/any_test.go | 7 ++- x/tx/textual/bytes_test.go | 5 +- x/tx/textual/coin_test.go | 7 ++- x/tx/textual/coins_test.go | 5 +- x/tx/textual/dec_test.go | 5 +- x/tx/textual/e2e_test.go | 11 +++- x/tx/textual/enum_test.go | 7 ++- x/tx/textual/int_test.go | 3 +- x/tx/textual/message.go | 4 +- x/tx/textual/message_test.go | 8 ++- x/tx/textual/repeated_test.go | 9 +-- x/tx/textual/tx.go | 5 +- x/tx/textual/tx_test.go | 5 +- x/tx/textual/valuerenderer.go | 39 +++++++----- x/tx/textual/valuerenderer_test.go | 2 +- 23 files changed, 345 insertions(+), 79 deletions(-) create mode 100644 x/tx/signing/direct/direct.go create mode 100644 x/tx/signing/direct/direct_test.go create mode 100644 x/tx/signing/handler_map.go create mode 100644 x/tx/signing/signer_data.go create mode 100644 x/tx/signing/std/handler_map.go create mode 100644 x/tx/signing/tx_data.go diff --git a/x/tx/signing/direct/direct.go b/x/tx/signing/direct/direct.go new file mode 100644 index 0000000000..450f0af4e3 --- /dev/null +++ b/x/tx/signing/direct/direct.go @@ -0,0 +1,31 @@ +package direct + +import ( + "context" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + "google.golang.org/protobuf/proto" + + "cosmossdk.io/x/tx/signing" +) + +// SignModeHandler is the SIGN_MODE_DIRECT implementation of signing.SignModeHandler. +type SignModeHandler struct{} + +// Mode implements signing.SignModeHandler.Mode. +func (h SignModeHandler) Mode() signingv1beta1.SignMode { + return signingv1beta1.SignMode_SIGN_MODE_DIRECT +} + +// GetSignBytes implements signing.SignModeHandler.GetSignBytes. +func (SignModeHandler) GetSignBytes(_ context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) { + return proto.Marshal(&txv1beta1.SignDoc{ + BodyBytes: txData.BodyBytes, + AuthInfoBytes: txData.AuthInfoBytes, + ChainId: signerData.ChainId, + AccountNumber: signerData.AccountNumber, + }) +} + +var _ signing.SignModeHandler = SignModeHandler{} diff --git a/x/tx/signing/direct/direct_test.go b/x/tx/signing/direct/direct_test.go new file mode 100644 index 0000000000..154b33f012 --- /dev/null +++ b/x/tx/signing/direct/direct_test.go @@ -0,0 +1,99 @@ +package direct_test + +import ( + "context" + "testing" + + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/api/cosmos/crypto/secp256k1" + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + "github.com/cosmos/cosmos-proto/any" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/direct" +) + +func TestDirectModeHandler(t *testing.T) { + memo := "sometestmemo" + + msg, err := any.New(&bankv1beta1.MsgSend{}) + require.NoError(t, err) + + pk, err := any.New(&secp256k1.PubKey{ + Key: make([]byte, 256), + }) + require.NoError(t, err) + + accSeq := uint64(2) // Arbitrary account sequence + + signerInfo := []*txv1beta1.SignerInfo{ + { + PublicKey: pk, + ModeInfo: &txv1beta1.ModeInfo{ + Sum: &txv1beta1.ModeInfo_Single_{ + Single: &txv1beta1.ModeInfo_Single{ + Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT, + }, + }, + }, + Sequence: accSeq, + }, + } + + fee := &txv1beta1.Fee{Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}}, GasLimit: 20000} + txBody := &txv1beta1.TxBody{ + Messages: []*anypb.Any{msg}, + Memo: memo, + } + + authInfo := &txv1beta1.AuthInfo{ + Fee: fee, + SignerInfos: signerInfo, + } + + directHandler := direct.SignModeHandler{} + + chainId := "test-chain" + accNum := uint64(1) + + signingData := signing.SignerData{ + Address: "", + ChainId: chainId, + AccountNumber: accNum, + PubKey: pk, + } + + bodyBz, err := proto.Marshal(txBody) + require.NoError(t, err) + + authInfoBz, err := proto.Marshal(authInfo) + require.NoError(t, err) + + txData := signing.TxData{ + Body: txBody, + AuthInfo: authInfo, + BodyBytes: bodyBz, + AuthInfoBytes: authInfoBz, + BodyHasUnknownNonCriticals: false, + } + + signBytes, err := directHandler.GetSignBytes(context.Background(), signingData, txData) + require.NoError(t, err) + require.NotNil(t, signBytes) + + signBytes2, err := proto.Marshal(&txv1beta1.SignDoc{ + BodyBytes: txData.BodyBytes, + AuthInfoBytes: txData.AuthInfoBytes, + ChainId: chainId, + AccountNumber: accNum, + }) + require.NoError(t, err) + require.NotNil(t, signBytes2) + + require.Equal(t, signBytes2, signBytes) +} diff --git a/x/tx/signing/handler_map.go b/x/tx/signing/handler_map.go new file mode 100644 index 0000000000..076a216689 --- /dev/null +++ b/x/tx/signing/handler_map.go @@ -0,0 +1,45 @@ +package signing + +import ( + "context" + "fmt" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" +) + +// HandlerMap aggregates several sign mode handlers together for convenient generation of sign bytes +// based on sign mode. +type HandlerMap struct { + signModeHandlers map[signingv1beta1.SignMode]SignModeHandler + modes []signingv1beta1.SignMode +} + +// NewHandlerMap constructs a new sign mode handler map. +func NewHandlerMap(handlers ...SignModeHandler) *HandlerMap { + res := &HandlerMap{ + signModeHandlers: map[signingv1beta1.SignMode]SignModeHandler{}, + } + + for _, handler := range handlers { + mode := handler.Mode() + res.signModeHandlers[mode] = handler + res.modes = append(res.modes, mode) + } + + return res +} + +// SupportedModes lists the modes supported by this handler map. +func (h *HandlerMap) SupportedModes() []signingv1beta1.SignMode { + return h.modes +} + +// GetSignBytes returns the sign bytes for the transaction for the requested mode. +func (h *HandlerMap) GetSignBytes(ctx context.Context, signMode signingv1beta1.SignMode, signerData SignerData, txData TxData) ([]byte, error) { + handler, ok := h.signModeHandlers[signMode] + if !ok { + return nil, fmt.Errorf("unsuppored sign mode %s", signMode) + } + + return handler.GetSignBytes(ctx, signerData, txData) +} diff --git a/x/tx/signing/sign_mode_handler.go b/x/tx/signing/sign_mode_handler.go index a4ac86eb7d..682dbc1941 100644 --- a/x/tx/signing/sign_mode_handler.go +++ b/x/tx/signing/sign_mode_handler.go @@ -1,34 +1,16 @@ package signing -import "google.golang.org/protobuf/types/known/anypb" +import ( + "context" -// SignerData is the specific information needed to sign a transaction that generally -// isn't included in the transaction body itself -type SignerData struct { - // The address of the signer. - // - // In case of multisigs, this should be the multisig's address. - Address string + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" +) - // ChainId is the chain that this transaction is targeted - ChainId string +// SignModeHandler is the interface that handlers for each sign mode should implement to generate sign bytes. +type SignModeHandler interface { + // Mode is the sign mode supported by this handler + Mode() signingv1beta1.SignMode - // AccountNumber is the account number of the signer. - // - // In case of multisigs, this should be the multisig account number. - AccountNumber uint64 - - // Sequence is the account sequence number of the signer that is used - // for replay protection. This field is only useful for Legacy Amino signing, - // since in SIGN_MODE_DIRECT the account sequence is already in the signer - // info. - // - // In case of multisigs, this should be the multisig sequence. - Sequence uint64 - - // PubKey is the public key of the signer. - // - // In case of multisigs, this should be the pubkey of the member of the - // multisig that is signing the current sign doc. - PubKey *anypb.Any + // GetSignBytes returns the sign bytes for the provided SignerData and TxData, or an error. + GetSignBytes(ctx context.Context, signerData SignerData, txData TxData) ([]byte, error) } diff --git a/x/tx/signing/signer_data.go b/x/tx/signing/signer_data.go new file mode 100644 index 0000000000..a4ac86eb7d --- /dev/null +++ b/x/tx/signing/signer_data.go @@ -0,0 +1,34 @@ +package signing + +import "google.golang.org/protobuf/types/known/anypb" + +// SignerData is the specific information needed to sign a transaction that generally +// isn't included in the transaction body itself +type SignerData struct { + // The address of the signer. + // + // In case of multisigs, this should be the multisig's address. + Address string + + // ChainId is the chain that this transaction is targeted + ChainId string + + // AccountNumber is the account number of the signer. + // + // In case of multisigs, this should be the multisig account number. + AccountNumber uint64 + + // Sequence is the account sequence number of the signer that is used + // for replay protection. This field is only useful for Legacy Amino signing, + // since in SIGN_MODE_DIRECT the account sequence is already in the signer + // info. + // + // In case of multisigs, this should be the multisig sequence. + Sequence uint64 + + // PubKey is the public key of the signer. + // + // In case of multisigs, this should be the pubkey of the member of the + // multisig that is signing the current sign doc. + PubKey *anypb.Any +} diff --git a/x/tx/signing/std/handler_map.go b/x/tx/signing/std/handler_map.go new file mode 100644 index 0000000000..c598051c90 --- /dev/null +++ b/x/tx/signing/std/handler_map.go @@ -0,0 +1,28 @@ +package std + +import ( + "fmt" + + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/direct" + "cosmossdk.io/x/tx/textual" +) + +// SignModeOptions are options for configuring the standard sign mode handler map. +type SignModeOptions struct { + // CoinMetadataQueryFn is the CoinMetadataQueryFn required for SIGN_MODE_TEXTUAL. + CoinMetadataQueryFn textual.CoinMetadataQueryFn +} + +// HandlerMap returns a sign mode handler map that Cosmos SDK apps can use out +// of the box to support all "standard" sign modes. +func (s SignModeOptions) HandlerMap() (*signing.HandlerMap, error) { + if s.CoinMetadataQueryFn == nil { + return nil, fmt.Errorf("missing %T needed for SIGN_MODE_TEXTUAL", s.CoinMetadataQueryFn) + } + + return signing.NewHandlerMap( + direct.SignModeHandler{}, + textual.NewSignModeHandler(s.CoinMetadataQueryFn), + ), nil +} diff --git a/x/tx/signing/tx_data.go b/x/tx/signing/tx_data.go new file mode 100644 index 0000000000..c0d00057dc --- /dev/null +++ b/x/tx/signing/tx_data.go @@ -0,0 +1,23 @@ +package signing + +import txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + +// TxData is the data about a transaction that is necessary to generate sign bytes. +type TxData struct { + // Body is the TxBody that will be part of the transaction. + Body *txv1beta1.TxBody + + // AuthInfo is the AuthInfo that will be part of the transaction. + AuthInfo *txv1beta1.AuthInfo + + // BodyBytes is the marshaled body bytes that will be part of TxRaw. + BodyBytes []byte + + // AuthInfoBytes is the marshaled AuthInfo bytes that will be part of TxRaw. + AuthInfoBytes []byte + + // BodyHasUnknownNonCriticals should be set to true if the transaction has been + // decoded and found to have unknown non-critical fields. This is only needed + // for amino JSON signing. + BodyHasUnknownNonCriticals bool +} diff --git a/x/tx/textual/any.go b/x/tx/textual/any.go index aab2502b0d..f2be86c655 100644 --- a/x/tx/textual/any.go +++ b/x/tx/textual/any.go @@ -15,11 +15,11 @@ import ( // anyValueRenderer is a ValueRenderer for google.protobuf.Any messages. type anyValueRenderer struct { - tr *Textual + tr *SignModeHandler } // NewAnyValueRenderer returns a ValueRenderer for google.protobuf.Any messages. -func NewAnyValueRenderer(t *Textual) ValueRenderer { +func NewAnyValueRenderer(t *SignModeHandler) ValueRenderer { return anyValueRenderer{tr: t} } diff --git a/x/tx/textual/any_test.go b/x/tx/textual/any_test.go index a8a8e2f556..a577d5835a 100644 --- a/x/tx/textual/any_test.go +++ b/x/tx/textual/any_test.go @@ -7,10 +7,11 @@ import ( "os" "testing" - "cosmossdk.io/x/tx/textual" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" + "cosmossdk.io/x/tx/textual" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/testing/protocmp" @@ -30,7 +31,7 @@ func TestAny(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - tr := textual.NewTextual(EmptyCoinMetadataQuerier) + tr := textual.NewSignModeHandler(EmptyCoinMetadataQuerier) for i, tc := range testcases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { anyMsg := anypb.Any{} @@ -38,7 +39,7 @@ func TestAny(t *testing.T) { require.NoError(t, err) // Format into screens and check vs expected - rend := textual.NewAnyValueRenderer((&tr)) + rend := textual.NewAnyValueRenderer((tr)) screens, err := rend.Format(context.Background(), protoreflect.ValueOfMessage(anyMsg.ProtoReflect())) require.NoError(t, err) require.Equal(t, tc.Screens, screens) diff --git a/x/tx/textual/bytes_test.go b/x/tx/textual/bytes_test.go index 7685e9909f..18ae4ae94c 100644 --- a/x/tx/textual/bytes_test.go +++ b/x/tx/textual/bytes_test.go @@ -6,9 +6,10 @@ import ( "os" "testing" - "cosmossdk.io/x/tx/textual" "github.com/stretchr/testify/require" "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/x/tx/textual" ) func TestBytesJsonTestCases(t *testing.T) { @@ -20,7 +21,7 @@ func TestBytesJsonTestCases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - textual := textual.NewTextual(nil) + textual := textual.NewSignModeHandler(nil) for _, tc := range testcases { t.Run(tc.hex, func(t *testing.T) { diff --git a/x/tx/textual/coin_test.go b/x/tx/textual/coin_test.go index cad066994e..2b26151efa 100644 --- a/x/tx/textual/coin_test.go +++ b/x/tx/textual/coin_test.go @@ -12,6 +12,7 @@ import ( bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/x/tx/textual" ) @@ -47,7 +48,7 @@ func addMetadataToContext(ctx context.Context, metadata *bankv1beta1.Metadata) c func TestMetadataQuerier(t *testing.T) { // Errors on nil metadata querier - txt := textual.NewTextual(nil) + txt := textual.NewSignModeHandler(nil) vr, err := txt.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) require.NoError(t, err) _, err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect())) @@ -55,7 +56,7 @@ func TestMetadataQuerier(t *testing.T) { // Errors if metadata querier returns an error expErr := fmt.Errorf("mock error") - txt = textual.NewTextual(func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) { + txt = textual.NewSignModeHandler(func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) { return nil, expErr }) vr, err = txt.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) @@ -73,7 +74,7 @@ func TestCoinJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - textual := textual.NewTextual(mockCoinMetadataQuerier) + textual := textual.NewSignModeHandler(mockCoinMetadataQuerier) vr, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) require.NoError(t, err) diff --git a/x/tx/textual/coins_test.go b/x/tx/textual/coins_test.go index 04655b5817..a4febb3946 100644 --- a/x/tx/textual/coins_test.go +++ b/x/tx/textual/coins_test.go @@ -9,9 +9,10 @@ import ( bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" "cosmossdk.io/math" - "cosmossdk.io/x/tx/textual" "github.com/stretchr/testify/require" "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/x/tx/textual" ) func TestCoinsJsonTestcases(t *testing.T) { @@ -21,7 +22,7 @@ func TestCoinsJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - txt := textual.NewTextual(mockCoinMetadataQuerier) + txt := textual.NewSignModeHandler(mockCoinMetadataQuerier) vr, err := txt.GetFieldValueRenderer(fieldDescriptorFromName("COINS")) vrr := vr.(textual.RepeatedValueRenderer) require.NoError(t, err) diff --git a/x/tx/textual/dec_test.go b/x/tx/textual/dec_test.go index bc1952a36f..1bf3657520 100644 --- a/x/tx/textual/dec_test.go +++ b/x/tx/textual/dec_test.go @@ -5,9 +5,10 @@ import ( "os" "testing" - "cosmossdk.io/x/tx/textual" "github.com/stretchr/testify/require" "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/x/tx/textual" ) func TestDecJsonTestcases(t *testing.T) { @@ -18,7 +19,7 @@ func TestDecJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - textual := textual.NewTextual(nil) + textual := textual.NewSignModeHandler(nil) for _, tc := range testcases { tc := tc diff --git a/x/tx/textual/e2e_test.go b/x/tx/textual/e2e_test.go index cfccff0f2e..620ca90c63 100644 --- a/x/tx/textual/e2e_test.go +++ b/x/tx/textual/e2e_test.go @@ -16,6 +16,8 @@ import ( _ "cosmossdk.io/api/cosmos/crypto/multisig" _ "cosmossdk.io/api/cosmos/crypto/secp256k1" _ "cosmossdk.io/api/cosmos/gov/v1" + + "cosmossdk.io/x/tx/signing" "cosmossdk.io/x/tx/textual" "cosmossdk.io/x/tx/textual/internal/textualpb" ) @@ -37,8 +39,8 @@ func TestE2EJsonTestcases(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { _, bodyBz, _, authInfoBz, signerData := createTextualData(t, tc.Proto, tc.SignerData) - tr := textual.NewTextual(mockCoinMetadataQuerier) - rend := textual.NewTxValueRenderer(&tr) + tr := textual.NewSignModeHandler(mockCoinMetadataQuerier) + rend := textual.NewTxValueRenderer(tr) ctx := addMetadataToContext(context.Background(), tc.Metadata) data := &textualpb.TextualData{ @@ -64,7 +66,10 @@ func TestE2EJsonTestcases(t *testing.T) { require.Equal(t, tc.Screens, screens) // Make sure CBOR match. - signDoc, err := tr.GetSignBytes(ctx, bodyBz, authInfoBz, signerData) + signDoc, err := tr.GetSignBytes(ctx, signerData, signing.TxData{ + BodyBytes: bodyBz, + AuthInfoBytes: authInfoBz, + }) require.NoError(t, err) require.Equal(t, tc.Cbor, hex.EncodeToString(signDoc)) diff --git a/x/tx/textual/enum_test.go b/x/tx/textual/enum_test.go index bc65a73a53..8d9ed76226 100644 --- a/x/tx/textual/enum_test.go +++ b/x/tx/textual/enum_test.go @@ -7,13 +7,14 @@ import ( "strings" "testing" - "cosmossdk.io/x/tx/textual" - "cosmossdk.io/x/tx/textual/internal/testpb" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/testing/protocmp" + + "cosmossdk.io/x/tx/textual" + "cosmossdk.io/x/tx/textual/internal/testpb" ) type enumTest struct { @@ -28,7 +29,7 @@ func TestEnumJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - textual := textual.NewTextual(nil) + textual := textual.NewSignModeHandler(nil) for _, tc := range testcases { t.Run(tc.Text, func(t *testing.T) { diff --git a/x/tx/textual/int_test.go b/x/tx/textual/int_test.go index bfcba75507..7b305221d7 100644 --- a/x/tx/textual/int_test.go +++ b/x/tx/textual/int_test.go @@ -11,6 +11,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" "cosmossdk.io/math" + "cosmossdk.io/x/tx/textual" ) @@ -22,7 +23,7 @@ func TestIntJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - textual := textual.NewTextual(nil) + textual := textual.NewSignModeHandler(nil) for _, tc := range testcases { t.Run(tc[0], func(t *testing.T) { diff --git a/x/tx/textual/message.go b/x/tx/textual/message.go index 0aaaeec361..f6e5ed1cd2 100644 --- a/x/tx/textual/message.go +++ b/x/tx/textual/message.go @@ -14,12 +14,12 @@ import ( ) type messageValueRenderer struct { - tr *Textual + tr *SignModeHandler msgDesc protoreflect.MessageDescriptor fds []protoreflect.FieldDescriptor } -func NewMessageValueRenderer(t *Textual, msgDesc protoreflect.MessageDescriptor) ValueRenderer { +func NewMessageValueRenderer(t *SignModeHandler, msgDesc protoreflect.MessageDescriptor) ValueRenderer { fields := msgDesc.Fields() fds := make([]protoreflect.FieldDescriptor, 0, fields.Len()) for i := 0; i < fields.Len(); i++ { diff --git a/x/tx/textual/message_test.go b/x/tx/textual/message_test.go index 51bb85c711..66ee556c04 100644 --- a/x/tx/textual/message_test.go +++ b/x/tx/textual/message_test.go @@ -11,9 +11,11 @@ import ( "github.com/stretchr/testify/require" bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" + "google.golang.org/protobuf/reflect/protoreflect" + "cosmossdk.io/x/tx/textual" "cosmossdk.io/x/tx/textual/internal/testpb" - "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/testing/protocmp" ) @@ -34,10 +36,10 @@ func TestMessageJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - tr := textual.NewTextual(EmptyCoinMetadataQuerier) + tr := textual.NewSignModeHandler(EmptyCoinMetadataQuerier) for i, tc := range testcases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - rend := textual.NewMessageValueRenderer(&tr, (&testpb.Foo{}).ProtoReflect().Descriptor()) + rend := textual.NewMessageValueRenderer(tr, (&testpb.Foo{}).ProtoReflect().Descriptor()) screens, err := rend.Format(context.Background(), protoreflect.ValueOf(tc.Proto.ProtoReflect())) require.NoError(t, err) diff --git a/x/tx/textual/repeated_test.go b/x/tx/textual/repeated_test.go index 07174ab69e..a23533dbe8 100644 --- a/x/tx/textual/repeated_test.go +++ b/x/tx/textual/repeated_test.go @@ -9,10 +9,11 @@ import ( "github.com/stretchr/testify/require" - "cosmossdk.io/x/tx/textual" - "cosmossdk.io/x/tx/textual/internal/testpb" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/x/tx/textual" + "cosmossdk.io/x/tx/textual/internal/testpb" ) type repeatedJsonTest struct { @@ -28,13 +29,13 @@ func TestRepeatedJsonTestcases(t *testing.T) { err = json.Unmarshal(raw, &testcases) require.NoError(t, err) - tr := textual.NewTextual(mockCoinMetadataQuerier) + tr := textual.NewSignModeHandler(mockCoinMetadataQuerier) for i, tc := range testcases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { // Create a context.Context containing all coins metadata, to simulate // that they are in state. ctx := context.Background() - rend := textual.NewMessageValueRenderer(&tr, (&testpb.Qux{}).ProtoReflect().Descriptor()) + rend := textual.NewMessageValueRenderer(tr, (&testpb.Qux{}).ProtoReflect().Descriptor()) require.NoError(t, err) screens, err := rend.Format(ctx, protoreflect.ValueOf(tc.Proto.ProtoReflect())) diff --git a/x/tx/textual/tx.go b/x/tx/textual/tx.go index 726aff9ce2..764ef13958 100644 --- a/x/tx/textual/tx.go +++ b/x/tx/textual/tx.go @@ -16,6 +16,7 @@ import ( msg "cosmossdk.io/api/cosmos/msg/v1" signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/x/tx/textual/internal/textualpb" ) @@ -28,7 +29,7 @@ var ( ) type txValueRenderer struct { - tr *Textual + tr *SignModeHandler } // NewTxValueRenderer returns a ValueRenderer for the protobuf @@ -36,7 +37,7 @@ type txValueRenderer struct { // The reason we create a renderer for TextualData (and not directly Tx) // is that TextualData is a single place that contains all data needed // to create the `[]Screen` SignDoc. -func NewTxValueRenderer(tr *Textual) ValueRenderer { +func NewTxValueRenderer(tr *SignModeHandler) ValueRenderer { return txValueRenderer{ tr: tr, } diff --git a/x/tx/textual/tx_test.go b/x/tx/textual/tx_test.go index a36358bd37..933429a785 100644 --- a/x/tx/textual/tx_test.go +++ b/x/tx/textual/tx_test.go @@ -20,6 +20,7 @@ import ( _ "cosmossdk.io/api/cosmos/crypto/secp256k1" _ "cosmossdk.io/api/cosmos/gov/v1" txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/x/tx/signing" "cosmossdk.io/x/tx/textual" "cosmossdk.io/x/tx/textual/internal/textualpb" @@ -55,8 +56,8 @@ func TestTxJsonTestcases(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { txBody, bodyBz, txAuthInfo, authInfoBz, signerData := createTextualData(t, tc.Proto, tc.SignerData) - tr := textual.NewTextual(mockCoinMetadataQuerier) - rend := textual.NewTxValueRenderer(&tr) + tr := textual.NewSignModeHandler(mockCoinMetadataQuerier) + rend := textual.NewTxValueRenderer(tr) ctx := addMetadataToContext(context.Background(), tc.Metadata) data := &textualpb.TextualData{ diff --git a/x/tx/textual/valuerenderer.go b/x/tx/textual/valuerenderer.go index 851370a3b8..9f9a254a36 100644 --- a/x/tx/textual/valuerenderer.go +++ b/x/tx/textual/valuerenderer.go @@ -5,6 +5,7 @@ import ( "context" "fmt" + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/anypb" @@ -13,21 +14,22 @@ import ( bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" + cosmos_proto "github.com/cosmos/cosmos-proto" + "cosmossdk.io/x/tx/signing" "cosmossdk.io/x/tx/textual/internal/textualpb" - cosmos_proto "github.com/cosmos/cosmos-proto" ) // CoinMetadataQueryFn defines a function that queries state for the coin denom -// metadata. It is meant to be passed as an argument into `NewTextual`. +// metadata. It is meant to be passed as an argument into `NewSignModeHandler`. type CoinMetadataQueryFn func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) // ValueRendererCreator is a function returning a textual. type ValueRendererCreator func(protoreflect.FieldDescriptor) ValueRenderer -// Textual holds the configuration for dispatching +// SignModeHandler holds the configuration for dispatching // to specific value renderers for SIGN_MODE_TEXTUAL. -type Textual struct { +type SignModeHandler struct { // coinMetadataQuerier defines a function to query the coin metadata from // state. It should use bank module's `DenomsMetadata` gRPC query to fetch // each denom's associated metadata, either using the bank keeper (for @@ -44,16 +46,15 @@ type Textual struct { messages map[protoreflect.FullName]ValueRenderer } -// NewTextual returns a new Textual which provides -// value renderers. -func NewTextual(q CoinMetadataQueryFn) Textual { - t := Textual{coinMetadataQuerier: q} +// NewSignModeHandler returns a new SignModeHandler which generates sign bytes and provides value renderers. +func NewSignModeHandler(q CoinMetadataQueryFn) *SignModeHandler { + t := &SignModeHandler{coinMetadataQuerier: q} t.init() return t } // GetFieldValueRenderer returns the value renderer for the given FieldDescriptor. -func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueRenderer, error) { +func (r *SignModeHandler) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueRenderer, error) { switch { // Scalars, such as sdk.Int and sdk.Dec encoded as strings. case fd.Kind() == protoreflect.StringKind: @@ -106,7 +107,7 @@ func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueR // GetMessageValueRenderer is a specialization of GetValueRenderer for messages. // It is useful when the message type is discovered outside the context of a field, // e.g. when handling a google.protobuf.Any. -func (r *Textual) GetMessageValueRenderer(md protoreflect.MessageDescriptor) (ValueRenderer, error) { +func (r *SignModeHandler) GetMessageValueRenderer(md protoreflect.MessageDescriptor) (ValueRenderer, error) { fullName := md.FullName() vr, found := r.messages[fullName] if found { @@ -119,7 +120,7 @@ func (r *Textual) GetMessageValueRenderer(md protoreflect.MessageDescriptor) (Va // custom scalar and message renderers. // // It is an idempotent method. -func (r *Textual) init() { +func (r *SignModeHandler) init() { if r.scalars == nil { r.scalars = map[string]ValueRendererCreator{} r.scalars["cosmos.Int"] = func(fd protoreflect.FieldDescriptor) ValueRenderer { return NewIntValueRenderer(fd) } @@ -136,22 +137,22 @@ func (r *Textual) init() { } // DefineScalar adds a value renderer to the given Cosmos scalar. -func (r *Textual) DefineScalar(scalar string, vr ValueRendererCreator) { +func (r *SignModeHandler) DefineScalar(scalar string, vr ValueRendererCreator) { r.init() r.scalars[scalar] = vr } // DefineMessageRenderer adds a new custom message renderer. -func (r *Textual) DefineMessageRenderer(name protoreflect.FullName, vr ValueRenderer) { +func (r *SignModeHandler) DefineMessageRenderer(name protoreflect.FullName, vr ValueRenderer) { r.init() r.messages[name] = vr } // GetSignBytes returns the transaction sign bytes. -func (r *Textual) GetSignBytes(ctx context.Context, bodyBz, authInfoBz []byte, signerData signing.SignerData) ([]byte, error) { +func (r *SignModeHandler) GetSignBytes(ctx context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) { data := &textualpb.TextualData{ - BodyBytes: bodyBz, - AuthInfoBytes: authInfoBz, + BodyBytes: txData.BodyBytes, + AuthInfoBytes: txData.AuthInfoBytes, SignerData: &textualpb.SignerData{ Address: signerData.Address, ChainId: signerData.ChainId, @@ -179,3 +180,9 @@ func (r *Textual) GetSignBytes(ctx context.Context, bodyBz, authInfoBz []byte, s return buf.Bytes(), nil } + +func (r *SignModeHandler) Mode() signingv1beta1.SignMode { + return signingv1beta1.SignMode_SIGN_MODE_TEXTUAL +} + +var _ signing.SignModeHandler = &SignModeHandler{} diff --git a/x/tx/textual/valuerenderer_test.go b/x/tx/textual/valuerenderer_test.go index dc37f38554..3c49bacdff 100644 --- a/x/tx/textual/valuerenderer_test.go +++ b/x/tx/textual/valuerenderer_test.go @@ -34,7 +34,7 @@ func TestDispatcher(t *testing.T) { for _, tc := range testcases { tc := tc t.Run(tc.name, func(t *testing.T) { - textual := textual.NewTextual(nil) + textual := textual.NewSignModeHandler(nil) rend, err := textual.GetFieldValueRenderer(fieldDescriptorFromName(tc.name)) if tc.expErr {