feat(x/tx): add basic handler types + sign mode direct (#14787)

Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
This commit is contained in:
Aaron Craelius 2023-02-09 10:01:27 -05:00 committed by GitHub
parent 4f13b5b319
commit 8bd9288051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 345 additions and 79 deletions

View File

@ -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{}

View File

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

View File

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

View File

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

View File

@ -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
}

View File

@ -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
}

23
x/tx/signing/tx_data.go Normal file
View File

@ -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
}

View File

@ -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}
}

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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) {

View File

@ -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) {

View File

@ -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++ {

View File

@ -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)

View File

@ -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()))

View File

@ -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,
}

View File

@ -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{

View File

@ -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{}

View File

@ -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 {