feat(x/tx): extract signers using cosmos.msg.v1.signer (#15205)
Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
This commit is contained in:
parent
5f47935747
commit
1f40d9d47d
2
x/tx/Makefile
Normal file
2
x/tx/Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
codegen:
|
||||
@(cd internal/testpb; buf generate)
|
||||
@ -7,7 +7,7 @@ import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "cosmossdk.io/x/tx/textual/internal/testpb";
|
||||
option go_package = "cosmossdk.io/x/tx/internal/testpb";
|
||||
|
||||
// A is used for testing value renderers.
|
||||
message A {
|
||||
@ -6725,11 +6725,11 @@ var file__1_proto_rawDesc = []byte{
|
||||
0x02, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49,
|
||||
0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x41, 0x4c, 0x4c, 0x4f,
|
||||
0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x57, 0x49, 0x54, 0x48,
|
||||
0x5f, 0x56, 0x45, 0x54, 0x4f, 0x10, 0x04, 0x42, 0x33, 0x42, 0x06, 0x31, 0x50, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x50, 0x01, 0x5a, 0x27, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69,
|
||||
0x6f, 0x2f, 0x74, 0x78, 0x2f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x5f, 0x56, 0x45, 0x54, 0x4f, 0x10, 0x04, 0x42, 0x35, 0x42, 0x06, 0x31, 0x50, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x50, 0x01, 0x5a, 0x29, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69,
|
||||
0x6f, 0x2f, 0x78, 0x2f, 0x74, 0x78, 0x2f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x2f, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
63
x/tx/internal/testpb/signers.proto
Normal file
63
x/tx/internal/testpb/signers.proto
Normal file
@ -0,0 +1,63 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "cosmos/msg/v1/msg.proto";
|
||||
|
||||
option go_package = "cosmossdk.io/x/tx/internal/testpb";
|
||||
|
||||
message SimpleSigner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
string signer = 1;
|
||||
}
|
||||
|
||||
message RepeatedSigner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
repeated string signer = 1;
|
||||
}
|
||||
|
||||
message NestedSigner {
|
||||
option (cosmos.msg.v1.signer) = "inner";
|
||||
Inner inner = 1;
|
||||
|
||||
message Inner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
string signer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message RepeatedNestedSigner {
|
||||
option (cosmos.msg.v1.signer) = "inner";
|
||||
repeated Inner inner = 1;
|
||||
|
||||
message Inner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
string signer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message NestedRepeatedSigner {
|
||||
option (cosmos.msg.v1.signer) = "inner";
|
||||
Inner inner = 1;
|
||||
|
||||
message Inner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
repeated string signer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message RepeatedNestedRepeatedSigner {
|
||||
option (cosmos.msg.v1.signer) = "inner";
|
||||
repeated Inner inner = 1;
|
||||
|
||||
message Inner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
repeated string signer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message BadSigner {
|
||||
option (cosmos.msg.v1.signer) = "signer";
|
||||
bytes signer = 1;
|
||||
}
|
||||
message NoSignerOption {
|
||||
bytes signer = 1;
|
||||
}
|
||||
6117
x/tx/internal/testpb/signers.pulsar.go
Normal file
6117
x/tx/internal/testpb/signers.pulsar.go
Normal file
File diff suppressed because it is too large
Load Diff
177
x/tx/signing/get_signers.go
Normal file
177
x/tx/signing/get_signers.go
Normal file
@ -0,0 +1,177 @@
|
||||
package signing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
)
|
||||
|
||||
// GetSignersContext is a context for retrieving the list of signers from a
|
||||
// message where signers are specified by the cosmos.msg.v1.signer protobuf
|
||||
// option.
|
||||
type GetSignersContext struct {
|
||||
protoFiles *protoregistry.Files
|
||||
getSignersFuncs map[protoreflect.FullName]getSignersFunc
|
||||
}
|
||||
|
||||
// GetSignersOptions are options for creating GetSignersContext.
|
||||
type GetSignersOptions struct {
|
||||
// ProtoFiles are the protobuf files to use for resolving message descriptors.
|
||||
// If it is nil, the global protobuf registry will be used.
|
||||
ProtoFiles *protoregistry.Files
|
||||
}
|
||||
|
||||
// NewGetSignersContext creates a new GetSignersContext using the provided options.
|
||||
func NewGetSignersContext(options GetSignersOptions) *GetSignersContext {
|
||||
protoFiles := options.ProtoFiles
|
||||
if protoFiles == nil {
|
||||
protoFiles = protoregistry.GlobalFiles
|
||||
}
|
||||
|
||||
return &GetSignersContext{
|
||||
protoFiles: protoFiles,
|
||||
getSignersFuncs: map[protoreflect.FullName]getSignersFunc{},
|
||||
}
|
||||
}
|
||||
|
||||
type getSignersFunc func(proto.Message) []string
|
||||
|
||||
func getSignersFieldNames(descriptor protoreflect.MessageDescriptor) ([]string, error) {
|
||||
signersFields := proto.GetExtension(descriptor.Options(), msgv1.E_Signer).([]string)
|
||||
if signersFields == nil || len(signersFields) == 0 {
|
||||
return nil, fmt.Errorf("no cosmos.msg.v1.signer option found for message %s", descriptor.FullName())
|
||||
}
|
||||
|
||||
return signersFields, nil
|
||||
}
|
||||
|
||||
func (*GetSignersContext) makeGetSignersFunc(descriptor protoreflect.MessageDescriptor) (getSignersFunc, error) {
|
||||
signersFields, err := getSignersFieldNames(descriptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fieldGetters := make([]func(proto.Message, []string) []string, len(signersFields))
|
||||
for i, fieldName := range signersFields {
|
||||
field := descriptor.Fields().ByName(protoreflect.Name(fieldName))
|
||||
if field == nil {
|
||||
return nil, fmt.Errorf("field %s not found in message %s", fieldName, descriptor.FullName())
|
||||
}
|
||||
|
||||
if field.IsMap() || field.HasOptionalKeyword() {
|
||||
return nil, fmt.Errorf("cosmos.msg.v1.signer field %s in message %s must not be a map or optional", fieldName, descriptor.FullName())
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case protoreflect.StringKind:
|
||||
if field.IsList() {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
signers := msg.ProtoReflect().Get(field).List()
|
||||
n := signers.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
arr = append(arr, signers.Get(i).String())
|
||||
}
|
||||
return arr
|
||||
}
|
||||
} else {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
return append(arr, msg.ProtoReflect().Get(field).String())
|
||||
}
|
||||
}
|
||||
case protoreflect.MessageKind:
|
||||
isList := field.IsList()
|
||||
nestedMessage := field.Message()
|
||||
nestedSignersFields, err := getSignersFieldNames(nestedMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(nestedSignersFields) != 1 {
|
||||
return nil, fmt.Errorf("nested cosmos.msg.v1.signer option in message %s must contain only one value", nestedMessage.FullName())
|
||||
}
|
||||
|
||||
nestedFieldName := nestedSignersFields[0]
|
||||
nestedField := nestedMessage.Fields().ByName(protoreflect.Name(nestedFieldName))
|
||||
nestedIsList := nestedField.IsList()
|
||||
if nestedField == nil {
|
||||
return nil, fmt.Errorf("field %s not found in message %s", nestedFieldName, nestedMessage.FullName())
|
||||
}
|
||||
|
||||
if nestedField.Kind() != protoreflect.StringKind || nestedField.IsMap() || nestedField.HasOptionalKeyword() {
|
||||
return nil, fmt.Errorf("nested signer field %s in message %s must be a simple string", nestedFieldName, nestedMessage.FullName())
|
||||
}
|
||||
|
||||
if isList {
|
||||
if nestedIsList {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
msgs := msg.ProtoReflect().Get(field).List()
|
||||
m := msgs.Len()
|
||||
for i := 0; i < m; i++ {
|
||||
signers := msgs.Get(i).Message().Get(nestedField).List()
|
||||
n := signers.Len()
|
||||
for j := 0; j < n; j++ {
|
||||
arr = append(arr, signers.Get(j).String())
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
} else {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
msgs := msg.ProtoReflect().Get(field).List()
|
||||
m := msgs.Len()
|
||||
for i := 0; i < m; i++ {
|
||||
arr = append(arr, msgs.Get(i).Message().Get(nestedField).String())
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if nestedIsList {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
nestedMsg := msg.ProtoReflect().Get(field).Message()
|
||||
signers := nestedMsg.Get(nestedField).List()
|
||||
n := signers.Len()
|
||||
for j := 0; j < n; j++ {
|
||||
arr = append(arr, signers.Get(j).String())
|
||||
}
|
||||
return arr
|
||||
}
|
||||
} else {
|
||||
fieldGetters[i] = func(msg proto.Message, arr []string) []string {
|
||||
return append(arr, msg.ProtoReflect().Get(field).Message().Get(nestedField).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected field type %s for field %s in message %s", field.Kind(), fieldName, descriptor.FullName())
|
||||
}
|
||||
}
|
||||
|
||||
return func(message proto.Message) []string {
|
||||
var signers []string
|
||||
for _, getter := range fieldGetters {
|
||||
signers = getter(message, signers)
|
||||
}
|
||||
return signers
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signers for a given message.
|
||||
func (c *GetSignersContext) GetSigners(msg proto.Message) ([]string, error) {
|
||||
messageDescriptor := msg.ProtoReflect().Descriptor()
|
||||
f, ok := c.getSignersFuncs[messageDescriptor.FullName()]
|
||||
if !ok {
|
||||
var err error
|
||||
f, err = c.makeGetSignersFunc(messageDescriptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.getSignersFuncs[messageDescriptor.FullName()] = f
|
||||
}
|
||||
|
||||
return f(msg), nil
|
||||
}
|
||||
113
x/tx/signing/get_signers_test.go
Normal file
113
x/tx/signing/get_signers_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
package signing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
groupv1 "cosmossdk.io/api/cosmos/group/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
)
|
||||
|
||||
func TestGetSigners(t *testing.T) {
|
||||
ctx := NewGetSignersContext(GetSignersOptions{})
|
||||
tests := []struct {
|
||||
name string
|
||||
msg proto.Message
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "MsgSend",
|
||||
msg: &bankv1beta1.MsgSend{
|
||||
FromAddress: "foo",
|
||||
},
|
||||
want: []string{"foo"},
|
||||
},
|
||||
{
|
||||
name: "MsgMultiSend",
|
||||
msg: &bankv1beta1.MsgMultiSend{
|
||||
Inputs: []*bankv1beta1.Input{
|
||||
{Address: "foo"},
|
||||
{Address: "bar"},
|
||||
},
|
||||
},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "MsgSubmitProposal",
|
||||
msg: &groupv1.MsgSubmitProposal{
|
||||
Proposers: []string{"foo", "bar"},
|
||||
},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
msg: &testpb.SimpleSigner{Signer: "foo"},
|
||||
want: []string{"foo"},
|
||||
},
|
||||
{
|
||||
name: "repeated",
|
||||
msg: &testpb.RepeatedSigner{Signer: []string{"foo", "bar"}},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "nested",
|
||||
msg: &testpb.NestedSigner{Inner: &testpb.NestedSigner_Inner{Signer: "foo"}},
|
||||
want: []string{"foo"},
|
||||
},
|
||||
{
|
||||
name: "nested repeated",
|
||||
msg: &testpb.NestedRepeatedSigner{Inner: &testpb.NestedRepeatedSigner_Inner{Signer: []string{"foo", "bar"}}},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "repeated nested",
|
||||
msg: &testpb.RepeatedNestedSigner{Inner: []*testpb.RepeatedNestedSigner_Inner{
|
||||
{Signer: "foo"},
|
||||
{Signer: "bar"},
|
||||
}},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "nested repeated",
|
||||
msg: &testpb.NestedRepeatedSigner{Inner: &testpb.NestedRepeatedSigner_Inner{
|
||||
Signer: []string{"foo", "bar"},
|
||||
}},
|
||||
want: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "repeated nested repeated",
|
||||
msg: &testpb.RepeatedNestedRepeatedSigner{Inner: []*testpb.RepeatedNestedRepeatedSigner_Inner{
|
||||
{Signer: []string{"foo", "bar"}},
|
||||
{Signer: []string{"baz", "bam"}},
|
||||
{Signer: []string{"blah"}},
|
||||
}},
|
||||
want: []string{"foo", "bar", "baz", "bam", "blah"},
|
||||
},
|
||||
{
|
||||
name: "bad",
|
||||
msg: &testpb.BadSigner{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no signer",
|
||||
msg: &testpb.NoSignerOption{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
signers, err := ctx.GetSigners(test.msg)
|
||||
if test.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.want, signers)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,8 @@ import (
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
"cosmossdk.io/x/tx/textual"
|
||||
"cosmossdk.io/x/tx/textual/internal/testpb"
|
||||
)
|
||||
|
||||
type enumTest struct {
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
codegen:
|
||||
@(buf generate)
|
||||
@ -13,10 +13,10 @@ import (
|
||||
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/testing/protocmp"
|
||||
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
"cosmossdk.io/x/tx/textual"
|
||||
)
|
||||
|
||||
func EmptyCoinMetadataQuerier(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
|
||||
|
||||
@ -12,8 +12,8 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
"cosmossdk.io/x/tx/textual"
|
||||
"cosmossdk.io/x/tx/textual/internal/testpb"
|
||||
)
|
||||
|
||||
type repeatedJsonTest struct {
|
||||
|
||||
@ -7,8 +7,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
"cosmossdk.io/x/tx/textual"
|
||||
"cosmossdk.io/x/tx/textual/internal/testpb"
|
||||
)
|
||||
|
||||
func TestDispatcher(t *testing.T) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user