feat(x/tx): unknown field filtering (#15557)
This commit is contained in:
parent
22621995b5
commit
383fed4c6d
@ -40,4 +40,4 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
## Improvements
|
||||
|
||||
* [#15302](https://github.com/cosmos/cosmos-sdk/pull/15302) Add support for a custom registry (e.g. gogo's MergedRegistry) to be plugged into SIGN_MODE_TEXTUAL.
|
||||
|
||||
* [#15557](https://github.com/cosmos/cosmos-sdk/pull/15557) Implement unknown field filtering.
|
||||
|
||||
11
x/tx/decode/errors.go
Normal file
11
x/tx/decode/errors.go
Normal file
@ -0,0 +1,11 @@
|
||||
package decode
|
||||
|
||||
import "cosmossdk.io/errors"
|
||||
|
||||
const (
|
||||
txCodespace = "tx"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownField = errors.Register(txCodespace, 2, "unknown protobuf field")
|
||||
)
|
||||
161
x/tx/decode/unknown.go
Normal file
161
x/tx/decode/unknown.go
Normal file
@ -0,0 +1,161 @@
|
||||
package decode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
const bit11NonCritical = 1 << 10
|
||||
|
||||
var anyDesc = (&anypb.Any{}).ProtoReflect().Descriptor()
|
||||
var anyFullName = anyDesc.FullName()
|
||||
|
||||
// RejectUnknownFieldsStrict operates by the same rules as RejectUnknownFields, but returns an error if any unknown
|
||||
// non-critical fields are encountered.
|
||||
func RejectUnknownFieldsStrict(bz []byte, msg protoreflect.MessageDescriptor, resolver protodesc.Resolver) error {
|
||||
var _, err = RejectUnknownFields(bz, msg, false, resolver)
|
||||
return err
|
||||
}
|
||||
|
||||
// RejectUnknownFields rejects any bytes bz with an error that has unknown fields for the provided proto.Message type with an
|
||||
// option to allow non-critical fields (specified as those fields with bit 11) to pass through. In either case, the
|
||||
// hasUnknownNonCriticals will be set to true if non-critical fields were encountered during traversal. This flag can be
|
||||
// used to treat a message with non-critical field different in different security contexts (such as transaction signing).
|
||||
// This function traverses inside of messages nested via google.protobuf.Any. It does not do any deserialization of the proto.Message.
|
||||
// An AnyResolver must be provided for traversing inside google.protobuf.Any's.
|
||||
func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUnknownNonCriticals bool, resolver protodesc.Resolver) (hasUnknownNonCriticals bool, err error) {
|
||||
if len(bz) == 0 {
|
||||
return hasUnknownNonCriticals, nil
|
||||
}
|
||||
|
||||
fields := desc.Fields()
|
||||
|
||||
for len(bz) > 0 {
|
||||
tagNum, wireType, m := protowire.ConsumeTag(bz)
|
||||
if m < 0 {
|
||||
return hasUnknownNonCriticals, errors.New("invalid length")
|
||||
}
|
||||
|
||||
fieldDesc := fields.ByNumber(tagNum)
|
||||
if fieldDesc == nil {
|
||||
isCriticalField := tagNum&bit11NonCritical == 0
|
||||
|
||||
if !isCriticalField {
|
||||
hasUnknownNonCriticals = true
|
||||
}
|
||||
|
||||
if isCriticalField || !allowUnknownNonCriticals {
|
||||
// The tag is critical, so report it.
|
||||
return hasUnknownNonCriticals, ErrUnknownField.Wrapf(
|
||||
"%s: {TagNum: %d, WireType:%q}",
|
||||
desc.FullName(), tagNum, WireTypeToString(wireType))
|
||||
}
|
||||
}
|
||||
|
||||
// Skip over the bytes that store fieldNumber and wireType bytes.
|
||||
bz = bz[m:]
|
||||
n := protowire.ConsumeFieldValue(tagNum, wireType, bz)
|
||||
if n < 0 {
|
||||
err = fmt.Errorf("could not consume field value for tagNum: %d, wireType: %q; %w",
|
||||
tagNum, WireTypeToString(wireType), protowire.ParseError(n))
|
||||
return hasUnknownNonCriticals, err
|
||||
}
|
||||
fieldBytes := bz[:n]
|
||||
bz = bz[n:]
|
||||
|
||||
// An unknown but non-critical field
|
||||
if fieldDesc == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldMessage := fieldDesc.Message()
|
||||
// not message or group kind
|
||||
if fieldMessage == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// consume length prefix of nested message
|
||||
_, o := protowire.ConsumeVarint(fieldBytes)
|
||||
fieldBytes = fieldBytes[o:]
|
||||
|
||||
var err error
|
||||
|
||||
if fieldMessage.FullName() == anyFullName {
|
||||
// Firstly typecheck types.Any to ensure nothing snuck in.
|
||||
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver)
|
||||
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
|
||||
if err != nil {
|
||||
return hasUnknownNonCriticals, err
|
||||
}
|
||||
var a anypb.Any
|
||||
if err = proto.Unmarshal(fieldBytes, &a); err != nil {
|
||||
return hasUnknownNonCriticals, err
|
||||
}
|
||||
|
||||
msgName := protoreflect.FullName(strings.TrimPrefix(a.TypeUrl, "/"))
|
||||
msgDesc, err := resolver.FindDescriptorByName(msgName)
|
||||
if err != nil {
|
||||
return hasUnknownNonCriticals, err
|
||||
}
|
||||
|
||||
fieldMessage = msgDesc.(protoreflect.MessageDescriptor)
|
||||
fieldBytes = a.Value
|
||||
}
|
||||
|
||||
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver)
|
||||
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
|
||||
if err != nil {
|
||||
return hasUnknownNonCriticals, err
|
||||
}
|
||||
}
|
||||
|
||||
return hasUnknownNonCriticals, nil
|
||||
}
|
||||
|
||||
// errUnknownField represents an error indicating that we encountered
|
||||
// a field that isn't available in the target proto.Message.
|
||||
type errUnknownField struct {
|
||||
Desc protoreflect.MessageDescriptor
|
||||
TagNum protowire.Number
|
||||
WireType protowire.Type
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (twt *errUnknownField) String() string {
|
||||
return fmt.Sprintf("errUnknownField %q: {TagNum: %d, WireType:%q}",
|
||||
twt.Desc.FullName(), twt.TagNum, WireTypeToString(twt.WireType))
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (twt *errUnknownField) Error() string {
|
||||
return twt.String()
|
||||
}
|
||||
|
||||
var _ error = (*errUnknownField)(nil)
|
||||
|
||||
// WireTypeToString returns a string representation of the given protowire.Type.
|
||||
func WireTypeToString(wt protowire.Type) string {
|
||||
switch wt {
|
||||
case 0:
|
||||
return "varint"
|
||||
case 1:
|
||||
return "fixed64"
|
||||
case 2:
|
||||
return "bytes"
|
||||
case 3:
|
||||
return "start_group"
|
||||
case 4:
|
||||
return "end_group"
|
||||
case 5:
|
||||
return "fixed32"
|
||||
default:
|
||||
return fmt.Sprintf("unknown type: %d", wt)
|
||||
}
|
||||
}
|
||||
674
x/tx/decode/unknown_test.go
Normal file
674
x/tx/decode/unknown_test.go
Normal file
@ -0,0 +1,674 @@
|
||||
package decode_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
"cosmossdk.io/x/tx/decode"
|
||||
"cosmossdk.io/x/tx/internal/testpb"
|
||||
)
|
||||
|
||||
func errUnknownField(typ string, tagNum int, wireType protowire.Type) error {
|
||||
var wt string
|
||||
if wireType >= 0 && wireType < 6 {
|
||||
wt = decode.WireTypeToString(wireType)
|
||||
}
|
||||
return decode.ErrUnknownField.Wrapf("%s: {TagNum: %d, WireType:%q}", typ, tagNum, wt)
|
||||
}
|
||||
|
||||
func errMismatchedField(typ string, wireType protowire.Type) error {
|
||||
return fmt.Errorf("invalid wire type %s for field %s", decode.WireTypeToString(wireType), typ)
|
||||
}
|
||||
|
||||
var ProtoResolver = protoregistry.GlobalFiles
|
||||
|
||||
func TestRejectUnknownFieldsRepeated(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
recv proto.Message
|
||||
wantErr error
|
||||
allowUnknownNonCriticals bool
|
||||
hasUnknownNonCriticals bool
|
||||
}{
|
||||
{
|
||||
name: "Unknown field in midst of repeated values",
|
||||
in: &testpb.TestVersion2{
|
||||
C: []*testpb.TestVersion2{
|
||||
{
|
||||
C: []*testpb.TestVersion2{
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
A: &testpb.TestVersion2{
|
||||
B: &testpb.TestVersion2{
|
||||
H: []*testpb.TestVersion1{
|
||||
{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
A: &testpb.TestVersion2{
|
||||
B: &testpb.TestVersion2{
|
||||
H: []*testpb.TestVersion1{
|
||||
{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
NewField_: 411,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion1),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion1",
|
||||
25,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, allowUnknownNonCriticals set",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testpb.TestVersion2{
|
||||
C: []*testpb.TestVersion2{
|
||||
{
|
||||
C: []*testpb.TestVersion2{
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
A: &testpb.TestVersion2{
|
||||
B: &testpb.TestVersion2{
|
||||
H: []*testpb.TestVersion1{
|
||||
{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
A: &testpb.TestVersion2{
|
||||
B: &testpb.TestVersion2{
|
||||
H: []*testpb.TestVersion1{
|
||||
{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
NewField_: 411,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion1),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion1",
|
||||
25,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, non-critical field to be rejected",
|
||||
in: &testpb.TestVersion3{
|
||||
C: []*testpb.TestVersion3{
|
||||
{
|
||||
C: []*testpb.TestVersion3{
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
A: &testpb.TestVersion3{
|
||||
B: &testpb.TestVersion3{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
A: &testpb.TestVersion3{
|
||||
B: &testpb.TestVersion3{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
NonCriticalField: "non-critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion1),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion1",
|
||||
1031,
|
||||
2),
|
||||
hasUnknownNonCriticals: true,
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, non-critical field ignored",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testpb.TestVersion3{
|
||||
C: []*testpb.TestVersion3{
|
||||
{
|
||||
C: []*testpb.TestVersion3{
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
A: &testpb.TestVersion3{
|
||||
B: &testpb.TestVersion3{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
A: &testpb.TestVersion3{
|
||||
B: &testpb.TestVersion3{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
NonCriticalField: "non-critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion1),
|
||||
wantErr: nil,
|
||||
hasUnknownNonCriticals: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
protoBlob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
desc := tt.recv.ProtoReflect().Descriptor()
|
||||
hasUnknownNonCriticals, gotErr := decode.RejectUnknownFields(
|
||||
protoBlob, desc, tt.allowUnknownNonCriticals, ProtoResolver)
|
||||
if tt.wantErr != nil {
|
||||
require.EqualError(t, gotErr, tt.wantErr.Error())
|
||||
} else {
|
||||
require.NoError(t, gotErr)
|
||||
}
|
||||
require.Equal(t, tt.hasUnknownNonCriticals, hasUnknownNonCriticals)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFields_allowUnknownNonCriticals(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
allowUnknownNonCriticals bool
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Field that's in the reserved range, should fail by default",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
1047,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "Field that's in the reserved range, toggle allowUnknownNonCriticals",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Unknown fields that are critical, but with allowUnknownNonCriticals set",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
City: testpb.Customer2_PaloAlto,
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
6,
|
||||
0),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal input: %v", err)
|
||||
}
|
||||
|
||||
c1 := new(testpb.Customer1).ProtoReflect().Descriptor()
|
||||
_, gotErr := decode.RejectUnknownFields(blob, c1, tt.allowUnknownNonCriticals, ProtoResolver)
|
||||
if tt.wantErr != nil {
|
||||
require.EqualError(t, gotErr, tt.wantErr.Error())
|
||||
} else {
|
||||
require.NoError(t, gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFieldsNested(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
recv proto.Message
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "TestVersion3 from TestVersionFD1",
|
||||
in: &testpb.TestVersion2{
|
||||
X: 5,
|
||||
Sum: &testpb.TestVersion2_E{
|
||||
E: 100,
|
||||
},
|
||||
H: []*testpb.TestVersion1{
|
||||
{X: 999},
|
||||
{X: -55},
|
||||
{
|
||||
X: 102,
|
||||
Sum: &testpb.TestVersion1_F{
|
||||
F: &testpb.TestVersion1{
|
||||
X: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
K: &testpb.Customer1{
|
||||
Id: 45,
|
||||
Name: "customer1",
|
||||
SubscriptionFee: 99,
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersionFD1),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersionFD1",
|
||||
12,
|
||||
2),
|
||||
},
|
||||
{
|
||||
name: "Alternating oneofs",
|
||||
in: &testpb.TestVersion3{
|
||||
Sum: &testpb.TestVersion3_E{
|
||||
E: 99,
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion3LoneOneOfValue),
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Alternating oneofs mismatched field",
|
||||
in: &testpb.TestVersion3{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
X: 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion3LoneOneOfValue),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion3LoneOneOfValue",
|
||||
7,
|
||||
2),
|
||||
},
|
||||
{
|
||||
name: "Discrepancy in a deeply nested one of field",
|
||||
in: &testpb.TestVersion3{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
Sum: &testpb.TestVersion3_F{
|
||||
F: &testpb.TestVersion3{
|
||||
X: 19,
|
||||
Sum: &testpb.TestVersion3_E{
|
||||
E: 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion3LoneNesting),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion3LoneNesting",
|
||||
6,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "unknown field types.Any in G",
|
||||
in: &testpb.TestVersion3{
|
||||
G: &anypb.Any{
|
||||
TypeUrl: "/testpb.TestVersion1",
|
||||
Value: mustMarshal(&testpb.TestVersion2{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
NewField_: 999,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion3),
|
||||
wantErr: errUnknownField(
|
||||
"testpb.TestVersion1",
|
||||
25, 0),
|
||||
},
|
||||
{
|
||||
name: "types.Any with extra fields",
|
||||
in: &testpb.TestVersionFD1WithExtraAny{
|
||||
G: &testpb.AnyWithExtra{
|
||||
A: &anypb.Any{
|
||||
TypeUrl: "/testpb.TestVersion1",
|
||||
Value: mustMarshal(&testpb.TestVersion2{
|
||||
Sum: &testpb.TestVersion2_F{
|
||||
F: &testpb.TestVersion2{
|
||||
NewField_: 999,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
B: 3,
|
||||
C: 2,
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion3),
|
||||
wantErr: errUnknownField(
|
||||
"google.protobuf.Any",
|
||||
3,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "mismatched types.Any in G",
|
||||
in: &testpb.TestVersion1{
|
||||
G: &anypb.Any{
|
||||
TypeUrl: "/testpb.TestVersion4LoneNesting",
|
||||
Value: mustMarshal(&testpb.TestVersion3LoneNesting_Inner1{
|
||||
Inner: &testpb.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Gotham",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion1),
|
||||
// behavior change from previous implementation: we allow mismatched wire -> proto types,
|
||||
// but this will still error on ConsumeFieldValue
|
||||
wantErr: fmt.Errorf(`could not consume field value for tagNum: 8, wireType: "unknown type: 7"; proto: cannot parse reserved wire type`),
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 0",
|
||||
in: &testpb.TestVersion3LoneNesting{
|
||||
Inner1: &testpb.TestVersion3LoneNesting_Inner1{
|
||||
Id: 10,
|
||||
Name: "foo",
|
||||
Inner: &testpb.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Palo Alto",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion4LoneNesting),
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 1",
|
||||
in: &testpb.TestVersion3LoneNesting{
|
||||
Inner2: &testpb.TestVersion3LoneNesting_Inner2{
|
||||
Id: "ID",
|
||||
Country: "Maldives",
|
||||
Inner: &testpb.TestVersion3LoneNesting_Inner2_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testpb.TestVersion4LoneNesting),
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
protoBlob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
desc := tt.recv.ProtoReflect().Descriptor()
|
||||
gotErr := decode.RejectUnknownFieldsStrict(protoBlob, desc, ProtoResolver)
|
||||
if tt.wantErr != nil {
|
||||
require.EqualError(t, gotErr, tt.wantErr.Error())
|
||||
} else {
|
||||
require.NoError(t, gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFieldsFlat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Oneof with same field number, shouldn't complain",
|
||||
in: &testpb.Customer3{
|
||||
Id: 68,
|
||||
Name: "ACME3",
|
||||
Payment: &testpb.Customer3_CreditCardNo{
|
||||
CreditCardNo: "123-XXXX-XXX881",
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Oneof with different field number, should fail",
|
||||
in: &testpb.Customer3{
|
||||
Id: 68,
|
||||
Name: "ACME3",
|
||||
Payment: &testpb.Customer3_ChequeNo{
|
||||
ChequeNo: "123XXXXXXX881",
|
||||
},
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
8, 2),
|
||||
},
|
||||
{
|
||||
name: "Any in a field, the extra field will be serialized so should fail",
|
||||
in: &testpb.Customer2{
|
||||
Miscellaneous: &anypb.Any{},
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
10,
|
||||
2),
|
||||
},
|
||||
{
|
||||
name: "With a nested struct as a field",
|
||||
in: &testpb.Customer3{
|
||||
Id: 289,
|
||||
Original: &testpb.Customer1{
|
||||
Id: 991,
|
||||
},
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
9,
|
||||
2),
|
||||
},
|
||||
{
|
||||
name: "An extra field that's non-existent in Customer1",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
Fewer: 199.9,
|
||||
},
|
||||
wantErr: errUnknownField("testpb.Customer1", 4, 5),
|
||||
},
|
||||
{
|
||||
name: "Using a field that's in the reserved range, should fail by default",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: errUnknownField(
|
||||
"testpb.Customer1",
|
||||
1047,
|
||||
0),
|
||||
},
|
||||
{
|
||||
name: "Only fields matching",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Name: "CustomerCustomerCustomerCustomerCustomer11111Customer1",
|
||||
},
|
||||
// behavior change from previous implementation: we allow mismatched wire -> proto types.
|
||||
// wantErr: errMismatchedField("testpb.Customer1", 4, 5),
|
||||
},
|
||||
{
|
||||
name: "Extra field that's non-existent in Customer1, along with Reserved set",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
Fewer: 199.9,
|
||||
Reserved: 819,
|
||||
},
|
||||
wantErr: errUnknownField("testpb.Customer1", 4, 5),
|
||||
},
|
||||
{
|
||||
name: "Using enumerated field",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
City: testpb.Customer2_PaloAlto,
|
||||
},
|
||||
wantErr: errUnknownField("testpb.Customer1", 6, 0),
|
||||
},
|
||||
{
|
||||
name: "multiple extraneous fields",
|
||||
in: &testpb.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
City: testpb.Customer2_PaloAlto,
|
||||
Fewer: 45,
|
||||
},
|
||||
wantErr: errUnknownField("testpb.Customer1", 4, 5),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal input: %v", err)
|
||||
}
|
||||
|
||||
c1 := new(testpb.Customer1)
|
||||
c1Desc := c1.ProtoReflect().Descriptor()
|
||||
//err = proto.Unmarshal(blob, c1)
|
||||
//require.NoError(t, err)
|
||||
gotErr := decode.RejectUnknownFieldsStrict(blob, c1Desc, ProtoResolver)
|
||||
if tt.wantErr != nil {
|
||||
require.EqualError(t, gotErr, tt.wantErr.Error())
|
||||
} else {
|
||||
require.NoError(t, gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Issue https://github.com/cosmos/cosmos-sdk/issues/7222, we need to ensure that repeated
|
||||
// uint64 are recognized as packed.
|
||||
func TestPackedEncoding(t *testing.T) {
|
||||
data := &testpb.TestRepeatedUints{Nums: []uint64{12, 13}}
|
||||
|
||||
marshalled, err := proto.Marshal(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
unmarshalled := data.ProtoReflect().Descriptor()
|
||||
_, err = decode.RejectUnknownFields(marshalled, unmarshalled, false, ProtoResolver)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func mustMarshal(msg proto.Message) []byte {
|
||||
blob, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return blob
|
||||
}
|
||||
@ -5,6 +5,7 @@ go 1.20
|
||||
require (
|
||||
cosmossdk.io/api v0.3.2-0.20230313131911-55bf5d4efbe7
|
||||
cosmossdk.io/core v0.6.1
|
||||
cosmossdk.io/errors v1.0.0-beta.7
|
||||
cosmossdk.io/math v1.0.0
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.3
|
||||
github.com/google/go-cmp v0.5.9
|
||||
|
||||
@ -2,6 +2,8 @@ cosmossdk.io/api v0.3.2-0.20230313131911-55bf5d4efbe7 h1:4LrWK+uGP5IxznxtHHsHD+Z
|
||||
cosmossdk.io/api v0.3.2-0.20230313131911-55bf5d4efbe7/go.mod h1:yVns7mKgcsG+hZW/3C5FdJtC6QYWdFIcRlKb9+5HV5g=
|
||||
cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s=
|
||||
cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA=
|
||||
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
|
||||
cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
|
||||
cosmossdk.io/math v1.0.0 h1:ro9w7eKx23om2tZz/VM2Pf+z2WAbGX1yDQQOJ6iGeJw=
|
||||
cosmossdk.io/math v1.0.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k=
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.3 h1:VitvZ1lPORTVxkmF2fAp3IiA61xVwArQYKXTdEcpW6o=
|
||||
|
||||
306
x/tx/internal/testpb/unknonwnproto.proto
Normal file
306
x/tx/internal/testpb/unknonwnproto.proto
Normal file
@ -0,0 +1,306 @@
|
||||
syntax = "proto3";
|
||||
package testpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
import "google/protobuf/any.proto";
|
||||
import "cosmos/tx/v1beta1/tx.proto";
|
||||
|
||||
message Customer1 {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
float subscription_fee = 3;
|
||||
|
||||
string payment = 7;
|
||||
}
|
||||
|
||||
message Customer2 {
|
||||
int32 id = 1;
|
||||
int32 industry = 2;
|
||||
string name = 3;
|
||||
float fewer = 4;
|
||||
|
||||
int64 reserved = 1047;
|
||||
|
||||
enum City {
|
||||
Laos = 0;
|
||||
LosAngeles = 1;
|
||||
PaloAlto = 2;
|
||||
Moscow = 3;
|
||||
Nairobi = 4;
|
||||
}
|
||||
|
||||
City city = 6;
|
||||
|
||||
google.protobuf.Any miscellaneous = 10;
|
||||
}
|
||||
|
||||
message Nested4A {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message Nested3A {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
repeated Nested4A a4 = 4;
|
||||
map<int64, Nested4A> index = 5;
|
||||
}
|
||||
|
||||
message Nested2A {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
Nested3A nested = 3;
|
||||
}
|
||||
|
||||
message Nested1A {
|
||||
int32 id = 1;
|
||||
Nested2A nested = 2;
|
||||
}
|
||||
|
||||
message Nested4B {
|
||||
int32 id = 1;
|
||||
int32 age = 2;
|
||||
string name = 3;
|
||||
}
|
||||
|
||||
message Nested3B {
|
||||
int32 id = 1;
|
||||
int32 age = 2;
|
||||
string name = 3;
|
||||
repeated Nested4B b4 = 4;
|
||||
}
|
||||
|
||||
message Nested2B {
|
||||
int32 id = 1;
|
||||
double fee = 2;
|
||||
Nested3B nested = 3;
|
||||
string route = 4;
|
||||
}
|
||||
|
||||
message Nested1B {
|
||||
int32 id = 1;
|
||||
Nested2B nested = 2;
|
||||
int32 age = 3;
|
||||
}
|
||||
|
||||
message Customer3 {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
float sf = 3;
|
||||
float surcharge = 4;
|
||||
string destination = 5;
|
||||
|
||||
oneof payment {
|
||||
string credit_card_no = 7;
|
||||
string cheque_no = 8;
|
||||
}
|
||||
|
||||
Customer1 original = 9;
|
||||
}
|
||||
|
||||
message TestVersion1 {
|
||||
int64 x = 1;
|
||||
TestVersion1 a = 2;
|
||||
TestVersion1 b = 3; // [(gogoproto.nullable) = false] generates invalid recursive structs;
|
||||
repeated TestVersion1 c = 4;
|
||||
repeated TestVersion1 d = 5 [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
TestVersion1 f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; // [(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
}
|
||||
message TestVersion2 {
|
||||
int64 x = 1;
|
||||
TestVersion2 a = 2;
|
||||
TestVersion2 b = 3; // [(gogoproto.nullable) = false];
|
||||
repeated TestVersion2 c = 4;
|
||||
repeated TestVersion2 d = 5; // [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
TestVersion2 f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; // [(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
uint64 new_field = 25;
|
||||
}
|
||||
message TestVersion3 {
|
||||
int64 x = 1;
|
||||
TestVersion3 a = 2;
|
||||
TestVersion3 b = 3; // [(gogoproto.nullable) = false];
|
||||
repeated TestVersion3 c = 4;
|
||||
repeated TestVersion3 d = 5; // [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
TestVersion3 f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; //[(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
string non_critical_field = 1031;
|
||||
}
|
||||
|
||||
message TestVersion3LoneOneOfValue {
|
||||
int64 x = 1;
|
||||
TestVersion3 a = 2;
|
||||
TestVersion3 b = 3; // [(gogoproto.nullable) = false];
|
||||
repeated TestVersion3 c = 4;
|
||||
repeated TestVersion3 d = 5; // [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; //[(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
string non_critical_field = 1031;
|
||||
}
|
||||
|
||||
message TestVersion3LoneNesting {
|
||||
int64 x = 1;
|
||||
TestVersion3 a = 2;
|
||||
TestVersion3 b = 3; // [(gogoproto.nullable) = false];
|
||||
repeated TestVersion3 c = 4;
|
||||
repeated TestVersion3 d = 5; // [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
TestVersion3LoneNesting f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; //[(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
string non_critical_field = 1031;
|
||||
|
||||
message Inner1 {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
message InnerInner {
|
||||
string id = 1;
|
||||
string city = 2;
|
||||
}
|
||||
InnerInner inner = 3;
|
||||
}
|
||||
|
||||
Inner1 inner1 = 14;
|
||||
|
||||
message Inner2 {
|
||||
string id = 1;
|
||||
string country = 2;
|
||||
message InnerInner {
|
||||
string id = 1;
|
||||
string city = 2;
|
||||
}
|
||||
InnerInner inner = 3;
|
||||
}
|
||||
|
||||
Inner2 inner2 = 15;
|
||||
}
|
||||
|
||||
message TestVersion4LoneNesting {
|
||||
int64 x = 1;
|
||||
TestVersion3 a = 2;
|
||||
TestVersion3 b = 3; // [(gogoproto.nullable) = false];
|
||||
repeated TestVersion3 c = 4;
|
||||
repeated TestVersion3 d = 5; // [(gogoproto.nullable) = false];
|
||||
oneof sum {
|
||||
TestVersion3LoneNesting f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; //[(gogoproto.castrepeated) = "TestVersion1"];
|
||||
// google.protobuf.Timestamp i = 10;
|
||||
// google.protobuf.Timestamp j = 11; // [(gogoproto.stdtime) = true];
|
||||
Customer1 k = 12 [(gogoproto.embed) = true];
|
||||
string non_critical_field = 1031;
|
||||
|
||||
message Inner1 {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
message InnerInner {
|
||||
int64 id = 1;
|
||||
string city = 2;
|
||||
}
|
||||
InnerInner inner = 3;
|
||||
}
|
||||
|
||||
Inner1 inner1 = 14;
|
||||
|
||||
message Inner2 {
|
||||
string id = 1;
|
||||
string country = 2;
|
||||
message InnerInner {
|
||||
string id = 1;
|
||||
int64 value = 2;
|
||||
}
|
||||
InnerInner inner = 3;
|
||||
}
|
||||
|
||||
Inner2 inner2 = 15;
|
||||
}
|
||||
|
||||
message TestVersionFD1 {
|
||||
int64 x = 1;
|
||||
TestVersion1 a = 2;
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
TestVersion1 f = 7;
|
||||
}
|
||||
google.protobuf.Any g = 8;
|
||||
repeated TestVersion1 h = 9; // [(gogoproto.castrepeated) = "TestVersion1"];
|
||||
}
|
||||
|
||||
message TestVersionFD1WithExtraAny {
|
||||
int64 x = 1;
|
||||
TestVersion1 a = 2;
|
||||
oneof sum {
|
||||
int32 e = 6;
|
||||
TestVersion1 f = 7;
|
||||
}
|
||||
AnyWithExtra g = 8;
|
||||
repeated TestVersion1 h = 9; // [(gogoproto.castrepeated) = "TestVersion1"];
|
||||
}
|
||||
|
||||
message AnyWithExtra {
|
||||
google.protobuf.Any a = 1 [(gogoproto.embed) = true];
|
||||
int64 b = 3;
|
||||
int64 c = 4;
|
||||
}
|
||||
|
||||
message TestUpdatedTxRaw {
|
||||
bytes body_bytes = 1;
|
||||
bytes auth_info_bytes = 2;
|
||||
repeated bytes signatures = 3;
|
||||
bytes new_field_5 = 5;
|
||||
bytes new_field_1024 = 1024;
|
||||
}
|
||||
|
||||
message TestUpdatedTxBody {
|
||||
repeated google.protobuf.Any messages = 1;
|
||||
string memo = 2;
|
||||
int64 timeout_height = 3;
|
||||
uint64 some_new_field = 4;
|
||||
string some_new_field_non_critical_field = 1050;
|
||||
repeated google.protobuf.Any extension_options = 1023;
|
||||
repeated google.protobuf.Any non_critical_extension_options = 2047;
|
||||
}
|
||||
|
||||
message TestUpdatedAuthInfo {
|
||||
repeated cosmos.tx.v1beta1.SignerInfo signer_infos = 1;
|
||||
cosmos.tx.v1beta1.Fee fee = 2;
|
||||
bytes new_field_3 = 3;
|
||||
bytes new_field_1024 = 1024;
|
||||
}
|
||||
|
||||
message TestRepeatedUints {
|
||||
repeated uint64 nums = 1;
|
||||
}
|
||||
28039
x/tx/internal/testpb/unknonwnproto.pulsar.go
Normal file
28039
x/tx/internal/testpb/unknonwnproto.pulsar.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user