From 077abae2509c4a6ceb07d071d57511b947f303f6 Mon Sep 17 00:00:00 2001 From: Jim Larson <32469398+JimLarson@users.noreply.github.com> Date: Thu, 24 Nov 2022 00:04:55 -0800 Subject: [PATCH] feat: value renderer for google.protobuf.Any (#13671) * chore: skeleton files for any renderer * feat: first cut at Any renderer and tests * test: Any fields, more tests, rename lookup by field * docs: make Any example match the stated spec, and the implementation. * refactor: use protojson for better test case legibility * test: use protocmp for proto equality See https://developers.google.com/protocol-buffers/docs/reference/go/faq#deepequal * refactor: use json.RawMessage instead of remarshalling * refactor: consistent pointer receiver for Textual --- .../adr-050-sign-mode-textual-annex1.md | 23 +- tx/textual/internal/testdata/any.json | 76 ++++ tx/textual/internal/testpb/1.proto | 9 +- tx/textual/internal/testpb/1.pulsar.go | 423 +++++++++++++----- tx/textual/valuerenderer/any.go | 102 +++++ tx/textual/valuerenderer/any_test.go | 57 +++ tx/textual/valuerenderer/bytes_test.go | 2 +- tx/textual/valuerenderer/coin_test.go | 6 +- tx/textual/valuerenderer/coins_test.go | 2 +- tx/textual/valuerenderer/dec_test.go | 2 +- tx/textual/valuerenderer/enum_test.go | 2 +- tx/textual/valuerenderer/int_test.go | 6 +- tx/textual/valuerenderer/message.go | 12 +- tx/textual/valuerenderer/valuerenderer.go | 20 +- .../valuerenderer/valuerenderer_test.go | 3 +- 15 files changed, 592 insertions(+), 153 deletions(-) create mode 100644 tx/textual/internal/testdata/any.json create mode 100644 tx/textual/valuerenderer/any.go create mode 100644 tx/textual/valuerenderer/any_test.go diff --git a/docs/architecture/adr-050-sign-mode-textual-annex1.md b/docs/architecture/adr-050-sign-mode-textual-annex1.md index 24a790efed..1ea4369659 100644 --- a/docs/architecture/adr-050-sign-mode-textual-annex1.md +++ b/docs/architecture/adr-050-sign-mode-textual-annex1.md @@ -182,17 +182,18 @@ Object: #### Examples ``` -Object: /cosmos.gov.v1.Vote -> Proposal id: 4 -> Vote: cosmos1abc...def -> Options: 2 WeightedVoteOptions -> Options (1/2): WeightedVoteOption object ->> Option: Yes ->> Weight: 0.7 -> Options (2/2): WeightedVoteOption object ->> Option: No ->> Weight: 0.3 -> End of Options +Object: type.googleapis.com/cosmos.gov.v1.Vote +> Vote object +>> Proposal id: 4 +>> Vote: cosmos1abc...def +>> Options: 2 WeightedVoteOptions +>> Options (1/2): WeightedVoteOption object +>>> Option: Yes +>>> Weight: 0.7 +>> Options (2/2): WeightedVoteOption object +>>> Option: No +>>> Weight: 0.3 +>> End of Options ``` ### `google.protobuf.Timestamp` diff --git a/tx/textual/internal/testdata/any.json b/tx/textual/internal/testdata/any.json new file mode 100644 index 0000000000..a32415ff39 --- /dev/null +++ b/tx/textual/internal/testdata/any.json @@ -0,0 +1,76 @@ +[ + { + "proto": { + "@type": "type.googleapis.com/Foo" + }, + "screens": [ + {"text": "Object: type.googleapis.com/Foo"}, + {"text": "Foo object", "indent": 1} + ] + }, + { + "proto": { + "@type": "type.googleapis.com/Foo", + "full_name": "testing" + }, + "screens": [ + {"text": "Object: type.googleapis.com/Foo"}, + {"text": "Foo object", "indent": 1}, + {"text": "Full name: testing", "indent": 2} + ] + }, + { + "proto": { + "@type": "type.googleapis.com/google.protobuf.Timestamp", + "value": "2006-01-02T15:04:05Z" + }, + "screens": [ + {"text": "Object: type.googleapis.com/google.protobuf.Timestamp"}, + {"text": "2006-01-02T15:04:05Z", "indent": 1} + ] + }, + { + "proto": { + "@type": "type.googleapis.com/google.protobuf.Any", + "value": { + "@type": "type.googleapis.com/Foo" + } + }, + "screens": [ + {"text": "Object: type.googleapis.com/google.protobuf.Any"}, + {"text": "Object: type.googleapis.com/Foo", "indent": 1}, + {"text": "Foo object", "indent": 2} + ] + }, + { + "proto": { + "@type": "type.googleapis.com/google.protobuf.Any", + "value": { + "@type": "type.googleapis.com/Foo", + "full_name": "testing" + } + }, + "screens": [ + {"text": "Object: type.googleapis.com/google.protobuf.Any"}, + {"text": "Object: type.googleapis.com/Foo", "indent": 1}, + {"text": "Foo object", "indent": 2}, + {"text": "Full name: testing", "indent": 3} + ] + }, + { + "proto": { + "@type": "type.googleapis.com/A", + "ANY": { + "@type": "type.googleapis.com/Foo", + "full_name": "testing" + } + }, + "screens": [ + {"text": "Object: type.googleapis.com/A"}, + {"text": "A object", "indent": 1}, + {"text": "ANY: Object: type.googleapis.com/Foo", "indent": 2}, + {"text": "Foo object", "indent": 3}, + {"text": "Full name: testing", "indent": 4} + ] + } +] diff --git a/tx/textual/internal/testpb/1.proto b/tx/textual/internal/testpb/1.proto index ec7193eef5..c2ad17a4a0 100644 --- a/tx/textual/internal/testpb/1.proto +++ b/tx/textual/internal/testpb/1.proto @@ -2,6 +2,7 @@ syntax = "proto3"; option go_package = "cosmossdk.io/tx/textual/internal/testpb"; +import "google/protobuf/any.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -23,6 +24,7 @@ message A { google.protobuf.Timestamp TIMESTAMP = 10; google.protobuf.Duration DURATION = 11; ExternalEnum ENUM = 12; + google.protobuf.Any ANY = 13; // Fields that are not handled by SIGN_MODE_TEXTUAL. sint32 SINT32 = 101; @@ -48,8 +50,9 @@ message Foo { // Bar is a sample message type used for testing message rendering. message Bar { - string bar_id = 1; - bytes data = 2; + string bar_id = 1; + bytes data = 2; + google.protobuf.Any payload = 3; } enum ExternalEnum { @@ -77,4 +80,4 @@ enum BallotOption { BALLOT_OPTION_ABSTAIN = 2; BALLOT_OPTION_NO = 3; BALLOT_OPTION_NO_WITH_VETO = 4; -} \ No newline at end of file +} diff --git a/tx/textual/internal/testpb/1.pulsar.go b/tx/textual/internal/testpb/1.pulsar.go index 99e924d895..2502e6b341 100644 --- a/tx/textual/internal/testpb/1.pulsar.go +++ b/tx/textual/internal/testpb/1.pulsar.go @@ -11,6 +11,7 @@ import ( protoiface "google.golang.org/protobuf/runtime/protoiface" protoimpl "google.golang.org/protobuf/runtime/protoimpl" _ "google.golang.org/protobuf/types/descriptorpb" + anypb "google.golang.org/protobuf/types/known/anypb" durationpb "google.golang.org/protobuf/types/known/durationpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" io "io" @@ -175,6 +176,7 @@ var ( fd_A_TIMESTAMP protoreflect.FieldDescriptor fd_A_DURATION protoreflect.FieldDescriptor fd_A_ENUM protoreflect.FieldDescriptor + fd_A_ANY protoreflect.FieldDescriptor fd_A_SINT32 protoreflect.FieldDescriptor fd_A_SINT64 protoreflect.FieldDescriptor fd_A_SFIXED32 protoreflect.FieldDescriptor @@ -201,6 +203,7 @@ func init() { fd_A_TIMESTAMP = md_A.Fields().ByName("TIMESTAMP") fd_A_DURATION = md_A.Fields().ByName("DURATION") fd_A_ENUM = md_A.Fields().ByName("ENUM") + fd_A_ANY = md_A.Fields().ByName("ANY") fd_A_SINT32 = md_A.Fields().ByName("SINT32") fd_A_SINT64 = md_A.Fields().ByName("SINT64") fd_A_SFIXED32 = md_A.Fields().ByName("SFIXED32") @@ -349,6 +352,12 @@ func (x *fastReflection_A) Range(f func(protoreflect.FieldDescriptor, protorefle return } } + if x.ANY != nil { + value := protoreflect.ValueOfMessage(x.ANY.ProtoReflect()) + if !f(fd_A_ANY, value) { + return + } + } if x.SINT32 != int32(0) { value := protoreflect.ValueOfInt32(x.SINT32) if !f(fd_A_SINT32, value) { @@ -442,6 +451,8 @@ func (x *fastReflection_A) Has(fd protoreflect.FieldDescriptor) bool { return x.DURATION != nil case "A.ENUM": return x.ENUM != 0 + case "A.ANY": + return x.ANY != nil case "A.SINT32": return x.SINT32 != int32(0) case "A.SINT64": @@ -500,6 +511,8 @@ func (x *fastReflection_A) Clear(fd protoreflect.FieldDescriptor) { x.DURATION = nil case "A.ENUM": x.ENUM = 0 + case "A.ANY": + x.ANY = nil case "A.SINT32": x.SINT32 = int32(0) case "A.SINT64": @@ -573,6 +586,9 @@ func (x *fastReflection_A) Get(descriptor protoreflect.FieldDescriptor) protoref case "A.ENUM": value := x.ENUM return protoreflect.ValueOfEnum((protoreflect.EnumNumber)(value)) + case "A.ANY": + value := x.ANY + return protoreflect.ValueOfMessage(value.ProtoReflect()) case "A.SINT32": value := x.SINT32 return protoreflect.ValueOfInt32(value) @@ -649,6 +665,8 @@ func (x *fastReflection_A) Set(fd protoreflect.FieldDescriptor, value protorefle x.DURATION = value.Message().Interface().(*durationpb.Duration) case "A.ENUM": x.ENUM = (ExternalEnum)(value.Enum()) + case "A.ANY": + x.ANY = value.Message().Interface().(*anypb.Any) case "A.SINT32": x.SINT32 = int32(value.Int()) case "A.SINT64": @@ -710,6 +728,11 @@ func (x *fastReflection_A) Mutable(fd protoreflect.FieldDescriptor) protoreflect x.DURATION = new(durationpb.Duration) } return protoreflect.ValueOfMessage(x.DURATION.ProtoReflect()) + case "A.ANY": + if x.ANY == nil { + x.ANY = new(anypb.Any) + } + return protoreflect.ValueOfMessage(x.ANY.ProtoReflect()) case "A.MAP": if x.MAP == nil { x.MAP = make(map[string]*A) @@ -789,6 +812,9 @@ func (x *fastReflection_A) NewField(fd protoreflect.FieldDescriptor) protoreflec return protoreflect.ValueOfMessage(m.ProtoReflect()) case "A.ENUM": return protoreflect.ValueOfEnum(0) + case "A.ANY": + m := new(anypb.Any) + return protoreflect.ValueOfMessage(m.ProtoReflect()) case "A.SINT32": return protoreflect.ValueOfInt32(int32(0)) case "A.SINT64": @@ -922,6 +948,10 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods { if x.ENUM != 0 { n += 1 + runtime.Sov(uint64(x.ENUM)) } + if x.ANY != nil { + l = options.Size(x.ANY) + n += 1 + l + runtime.Sov(uint64(l)) + } if x.SINT32 != 0 { n += 2 + runtime.Soz(uint64(x.SINT32)) } @@ -1115,6 +1145,20 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods { i-- dAtA[i] = 0xa8 } + if x.ANY != nil { + encoded, err := options.Marshal(x.ANY) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x6a + } if x.ENUM != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.ENUM)) i-- @@ -1603,6 +1647,42 @@ func (x *fastReflection_A) ProtoMethods() *protoiface.Methods { break } } + case 13: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ANY", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.ANY == nil { + x.ANY = &anypb.Any{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.ANY); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex case 101: if wireType != 0 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field SINT32", wireType) @@ -2672,9 +2752,10 @@ func (x *fastReflection_Foo) ProtoMethods() *protoiface.Methods { } var ( - md_Bar protoreflect.MessageDescriptor - fd_Bar_bar_id protoreflect.FieldDescriptor - fd_Bar_data protoreflect.FieldDescriptor + md_Bar protoreflect.MessageDescriptor + fd_Bar_bar_id protoreflect.FieldDescriptor + fd_Bar_data protoreflect.FieldDescriptor + fd_Bar_payload protoreflect.FieldDescriptor ) func init() { @@ -2682,6 +2763,7 @@ func init() { md_Bar = File__1_proto.Messages().ByName("Bar") fd_Bar_bar_id = md_Bar.Fields().ByName("bar_id") fd_Bar_data = md_Bar.Fields().ByName("data") + fd_Bar_payload = md_Bar.Fields().ByName("payload") } var _ protoreflect.Message = (*fastReflection_Bar)(nil) @@ -2761,6 +2843,12 @@ func (x *fastReflection_Bar) Range(f func(protoreflect.FieldDescriptor, protoref return } } + if x.Payload != nil { + value := protoreflect.ValueOfMessage(x.Payload.ProtoReflect()) + if !f(fd_Bar_payload, value) { + return + } + } } // Has reports whether a field is populated. @@ -2780,6 +2868,8 @@ func (x *fastReflection_Bar) Has(fd protoreflect.FieldDescriptor) bool { return x.BarId != "" case "Bar.data": return len(x.Data) != 0 + case "Bar.payload": + return x.Payload != nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: Bar")) @@ -2800,6 +2890,8 @@ func (x *fastReflection_Bar) Clear(fd protoreflect.FieldDescriptor) { x.BarId = "" case "Bar.data": x.Data = nil + case "Bar.payload": + x.Payload = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: Bar")) @@ -2822,6 +2914,9 @@ func (x *fastReflection_Bar) Get(descriptor protoreflect.FieldDescriptor) protor case "Bar.data": value := x.Data return protoreflect.ValueOfBytes(value) + case "Bar.payload": + value := x.Payload + return protoreflect.ValueOfMessage(value.ProtoReflect()) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: Bar")) @@ -2846,6 +2941,8 @@ func (x *fastReflection_Bar) Set(fd protoreflect.FieldDescriptor, value protoref x.BarId = value.Interface().(string) case "Bar.data": x.Data = value.Bytes() + case "Bar.payload": + x.Payload = value.Message().Interface().(*anypb.Any) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: Bar")) @@ -2866,6 +2963,11 @@ func (x *fastReflection_Bar) Set(fd protoreflect.FieldDescriptor, value protoref // Mutable is a mutating operation and unsafe for concurrent use. func (x *fastReflection_Bar) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { switch fd.FullName() { + case "Bar.payload": + if x.Payload == nil { + x.Payload = new(anypb.Any) + } + return protoreflect.ValueOfMessage(x.Payload.ProtoReflect()) case "Bar.bar_id": panic(fmt.Errorf("field bar_id of message Bar is not mutable")) case "Bar.data": @@ -2887,6 +2989,9 @@ func (x *fastReflection_Bar) NewField(fd protoreflect.FieldDescriptor) protorefl return protoreflect.ValueOfString("") case "Bar.data": return protoreflect.ValueOfBytes(nil) + case "Bar.payload": + m := new(anypb.Any) + return protoreflect.ValueOfMessage(m.ProtoReflect()) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: Bar")) @@ -2964,6 +3069,10 @@ func (x *fastReflection_Bar) ProtoMethods() *protoiface.Methods { if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) } + if x.Payload != nil { + l = options.Size(x.Payload) + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -2993,6 +3102,20 @@ func (x *fastReflection_Bar) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.Payload != nil { + encoded, err := options.Marshal(x.Payload) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x1a + } if len(x.Data) > 0 { i -= len(x.Data) copy(dAtA[i:], x.Data) @@ -3122,6 +3245,42 @@ func (x *fastReflection_Bar) ProtoMethods() *protoiface.Methods { x.Data = []byte{} } iNdEx = postIndex + case 3: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.Payload == nil { + x.Payload = &anypb.Any{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.Payload); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -3839,6 +3998,7 @@ type A struct { TIMESTAMP *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=TIMESTAMP,proto3" json:"TIMESTAMP,omitempty"` DURATION *durationpb.Duration `protobuf:"bytes,11,opt,name=DURATION,proto3" json:"DURATION,omitempty"` ENUM ExternalEnum `protobuf:"varint,12,opt,name=ENUM,proto3,enum=ExternalEnum" json:"ENUM,omitempty"` + ANY *anypb.Any `protobuf:"bytes,13,opt,name=ANY,proto3" json:"ANY,omitempty"` // Fields that are not handled by SIGN_MODE_TEXTUAL. SINT32 int32 `protobuf:"zigzag32,101,opt,name=SINT32,proto3" json:"SINT32,omitempty"` SINT64 int64 `protobuf:"zigzag64,102,opt,name=SINT64,proto3" json:"SINT64,omitempty"` @@ -3955,6 +4115,13 @@ func (x *A) GetENUM() ExternalEnum { return ExternalEnum_One } +func (x *A) GetANY() *anypb.Any { + if x != nil { + return x.ANY + } + return nil +} + func (x *A) GetSINT32() int32 { if x != nil { return x.SINT32 @@ -4100,8 +4267,9 @@ type Bar struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - BarId string `protobuf:"bytes,1,opt,name=bar_id,json=barId,proto3" json:"bar_id,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + BarId string `protobuf:"bytes,1,opt,name=bar_id,json=barId,proto3" json:"bar_id,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Payload *anypb.Any `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` } func (x *Bar) Reset() { @@ -4138,7 +4306,14 @@ func (x *Bar) GetData() []byte { return nil } -// Baz is a sampel message type used for testing enum rendering. +func (x *Bar) GetPayload() *anypb.Any { + if x != nil { + return x.Payload + } + return nil +} + +// Baz is a sample message type used for testing enum rendering. type Baz struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4193,105 +4368,112 @@ func (x *Baz) GetOption() BallotOption { var File__1_proto protoreflect.FileDescriptor var file__1_proto_rawDesc = []byte{ - 0x0a, 0x07, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x63, 0x6f, - 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, - 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69, - 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xde, 0x05, 0x0a, 0x01, 0x41, 0x12, 0x16, 0x0a, - 0x06, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, - 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x14, 0x0a, - 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x49, 0x4e, - 0x54, 0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, - 0x49, 0x4e, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x49, 0x4e, - 0x54, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, - 0x63, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x12, 0x2d, 0x0a, 0x04, 0x43, 0x4f, 0x49, - 0x4e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, - 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, - 0x69, 0x6e, 0x52, 0x04, 0x43, 0x4f, 0x49, 0x4e, 0x12, 0x2f, 0x0a, 0x05, 0x43, 0x4f, 0x49, 0x4e, - 0x53, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, - 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, - 0x69, 0x6e, 0x52, 0x05, 0x43, 0x4f, 0x49, 0x4e, 0x53, 0x12, 0x14, 0x0a, 0x05, 0x42, 0x59, 0x54, - 0x45, 0x53, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x12, - 0x38, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x44, 0x55, 0x52, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x12, 0x21, 0x0a, 0x04, 0x45, 0x4e, 0x55, 0x4d, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, - 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x04, 0x45, - 0x4e, 0x55, 0x4d, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x18, 0x65, 0x20, - 0x01, 0x28, 0x11, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x53, - 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x66, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x53, 0x49, 0x4e, - 0x54, 0x36, 0x34, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, - 0x69, 0x20, 0x01, 0x28, 0x0f, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, - 0x18, 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x07, - 0x52, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x4c, 0x4f, - 0x41, 0x54, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x12, - 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x6c, 0x20, 0x01, 0x28, - 0x10, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x46, - 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x6d, 0x20, 0x01, 0x28, 0x06, 0x52, 0x07, 0x46, 0x49, - 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x18, - 0x6e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x12, 0x1d, 0x0a, - 0x03, 0x4d, 0x41, 0x50, 0x18, 0x6f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x41, 0x2e, 0x4d, - 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x4d, 0x41, 0x50, 0x1a, 0x3a, 0x0a, 0x08, - 0x4d, 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x02, 0x2e, 0x41, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x01, 0x0a, 0x03, 0x46, 0x6f, 0x6f, - 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x6d, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x04, 0x6c, - 0x65, 0x66, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x6f, 0x6f, 0x52, - 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x1a, 0x0a, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x6f, 0x6f, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, - 0x74, 0x12, 0x16, 0x0a, 0x03, 0x62, 0x61, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, - 0x2e, 0x42, 0x61, 0x72, 0x52, 0x03, 0x62, 0x61, 0x72, 0x22, 0x30, 0x0a, 0x03, 0x42, 0x61, 0x72, - 0x12, 0x15, 0x0a, 0x06, 0x62, 0x61, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x62, 0x61, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x94, 0x01, 0x0a, 0x03, - 0x42, 0x61, 0x7a, 0x12, 0x1d, 0x0a, 0x02, 0x65, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x0d, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x02, - 0x65, 0x65, 0x12, 0x22, 0x0a, 0x02, 0x69, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, - 0x2e, 0x42, 0x61, 0x7a, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x45, 0x6e, - 0x75, 0x6d, 0x52, 0x02, 0x69, 0x65, 0x12, 0x25, 0x0a, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x42, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x23, 0x0a, - 0x0d, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x08, - 0x0a, 0x04, 0x46, 0x6f, 0x75, 0x72, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x69, 0x76, 0x65, - 0x10, 0x01, 0x2a, 0x39, 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x6e, - 0x75, 0x6d, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, - 0x77, 0x6f, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, - 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x45, 0x10, 0x7f, 0x2a, 0x95, 0x01, - 0x0a, 0x0c, 0x42, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, - 0x0a, 0x19, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, - 0x11, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x59, - 0x45, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, - 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x42, 0x53, 0x54, 0x41, 0x49, 0x4e, 0x10, 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, + 0x0a, 0x07, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x86, 0x06, 0x0a, 0x01, 0x41, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x49, 0x4e, 0x54, + 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, + 0x12, 0x16, 0x0a, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, + 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x14, + 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x49, + 0x4e, 0x54, 0x36, 0x34, 0x12, 0x26, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x49, 0x4e, 0x54, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, + 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x53, 0x44, 0x4b, 0x49, 0x4e, 0x54, 0x12, 0x26, 0x0a, 0x06, + 0x53, 0x44, 0x4b, 0x44, 0x45, 0x43, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xd2, 0xb4, + 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x52, 0x06, 0x53, 0x44, + 0x4b, 0x44, 0x45, 0x43, 0x12, 0x2d, 0x0a, 0x04, 0x43, 0x4f, 0x49, 0x4e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x04, 0x43, + 0x4f, 0x49, 0x4e, 0x12, 0x2f, 0x0a, 0x05, 0x43, 0x4f, 0x49, 0x4e, 0x53, 0x18, 0x08, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x05, 0x43, + 0x4f, 0x49, 0x4e, 0x53, 0x12, 0x14, 0x0a, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x12, 0x38, 0x0a, 0x09, 0x54, 0x49, + 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x53, + 0x54, 0x41, 0x4d, 0x50, 0x12, 0x35, 0x0a, 0x08, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x08, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x21, 0x0a, 0x04, 0x45, + 0x4e, 0x55, 0x4d, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x04, 0x45, 0x4e, 0x55, 0x4d, 0x12, 0x26, + 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, + 0x79, 0x52, 0x03, 0x41, 0x4e, 0x59, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, + 0x18, 0x65, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x12, 0x16, + 0x0a, 0x06, 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x18, 0x66, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, + 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, + 0x33, 0x32, 0x18, 0x69, 0x20, 0x01, 0x28, 0x0f, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, + 0x33, 0x32, 0x12, 0x18, 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x18, 0x6a, 0x20, + 0x01, 0x28, 0x07, 0x52, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x12, 0x14, 0x0a, 0x05, + 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x46, 0x4c, 0x4f, + 0x41, 0x54, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x6c, + 0x20, 0x01, 0x28, 0x10, 0x52, 0x08, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x18, + 0x0a, 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x18, 0x6d, 0x20, 0x01, 0x28, 0x06, 0x52, + 0x07, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, + 0x4c, 0x45, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, + 0x12, 0x1d, 0x0a, 0x03, 0x4d, 0x41, 0x50, 0x18, 0x6f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, + 0x41, 0x2e, 0x4d, 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x4d, 0x41, 0x50, 0x1a, + 0x3a, 0x0a, 0x08, 0x4d, 0x41, 0x50, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x02, 0x2e, 0x41, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x01, 0x0a, 0x03, + 0x46, 0x6f, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x05, + 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, + 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, + 0x6f, 0x6f, 0x52, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x1a, 0x0a, 0x05, 0x72, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x6f, 0x6f, 0x52, 0x05, 0x72, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x03, 0x62, 0x61, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x04, 0x2e, 0x42, 0x61, 0x72, 0x52, 0x03, 0x62, 0x61, 0x72, 0x22, 0x60, 0x0a, 0x03, + 0x42, 0x61, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x61, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x61, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, + 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x94, + 0x01, 0x0a, 0x03, 0x42, 0x61, 0x7a, 0x12, 0x1d, 0x0a, 0x02, 0x65, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x75, + 0x6d, 0x52, 0x02, 0x65, 0x65, 0x12, 0x22, 0x0a, 0x02, 0x69, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x12, 0x2e, 0x42, 0x61, 0x7a, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x5f, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x02, 0x69, 0x65, 0x12, 0x25, 0x0a, 0x06, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x42, 0x61, 0x6c, 0x6c, + 0x6f, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x23, 0x0a, 0x0d, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x45, 0x6e, 0x75, + 0x6d, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x6f, 0x75, 0x72, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, + 0x69, 0x76, 0x65, 0x10, 0x01, 0x2a, 0x39, 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, + 0x0a, 0x03, 0x54, 0x77, 0x6f, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x58, 0x54, 0x45, 0x52, + 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x45, 0x10, 0x7f, + 0x2a, 0x95, 0x01, 0x0a, 0x0c, 0x42, 0x61, 0x6c, 0x6c, 0x6f, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x15, 0x0a, 0x11, 0x42, 0x41, 0x4c, 0x4c, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x59, 0x45, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x41, 0x4c, 0x4c, 0x4f, + 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x42, 0x53, 0x54, 0x41, 0x49, 0x4e, + 0x10, 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, } var ( @@ -4320,6 +4502,7 @@ var file__1_proto_goTypes = []interface{}{ (*v1beta1.Coin)(nil), // 8: cosmos.base.v1beta1.Coin (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 10: google.protobuf.Duration + (*anypb.Any)(nil), // 11: google.protobuf.Any } var file__1_proto_depIdxs = []int32{ 8, // 0: A.COIN:type_name -> cosmos.base.v1beta1.Coin @@ -4327,20 +4510,22 @@ var file__1_proto_depIdxs = []int32{ 9, // 2: A.TIMESTAMP:type_name -> google.protobuf.Timestamp 10, // 3: A.DURATION:type_name -> google.protobuf.Duration 0, // 4: A.ENUM:type_name -> ExternalEnum - 7, // 5: A.MAP:type_name -> A.MAPEntry - 9, // 6: Foo.mtime:type_name -> google.protobuf.Timestamp - 4, // 7: Foo.left:type_name -> Foo - 4, // 8: Foo.right:type_name -> Foo - 5, // 9: Foo.bar:type_name -> Bar - 0, // 10: Baz.ee:type_name -> ExternalEnum - 2, // 11: Baz.ie:type_name -> Baz.Internal_Enum - 1, // 12: Baz.option:type_name -> BallotOption - 3, // 13: A.MAPEntry.value:type_name -> A - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 11, // 5: A.ANY:type_name -> google.protobuf.Any + 7, // 6: A.MAP:type_name -> A.MAPEntry + 9, // 7: Foo.mtime:type_name -> google.protobuf.Timestamp + 4, // 8: Foo.left:type_name -> Foo + 4, // 9: Foo.right:type_name -> Foo + 5, // 10: Foo.bar:type_name -> Bar + 11, // 11: Bar.payload:type_name -> google.protobuf.Any + 0, // 12: Baz.ee:type_name -> ExternalEnum + 2, // 13: Baz.ie:type_name -> Baz.Internal_Enum + 1, // 14: Baz.option:type_name -> BallotOption + 3, // 15: A.MAPEntry.value:type_name -> A + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file__1_proto_init() } diff --git a/tx/textual/valuerenderer/any.go b/tx/textual/valuerenderer/any.go new file mode 100644 index 0000000000..decb7e86c1 --- /dev/null +++ b/tx/textual/valuerenderer/any.go @@ -0,0 +1,102 @@ +package valuerenderer + +import ( + "context" + "fmt" + "strings" + + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/known/anypb" +) + +// Any messages are rendered as a one-screen header of "Object: " +// followed by an indented rendering of the contained message. + +// anyValueRenderer is a ValueRenderer for google.protobuf.Any messages. +type anyValueRenderer struct { + tr *Textual +} + +// NewAnyValueRenderer returns a ValueRenderer for google.protobuf.Any messages. +func NewAnyValueRenderer(t *Textual) ValueRenderer { + return anyValueRenderer{tr: t} +} + +// Format implements the ValueRenderer interface. +func (ar anyValueRenderer) Format(ctx context.Context, v protoreflect.Value) ([]Screen, error) { + msg := v.Message().Interface() + anymsg, ok := msg.(*anypb.Any) + if !ok { + return nil, fmt.Errorf("expected Any, got %T", msg) + } + + internalMsg, err := anymsg.UnmarshalNew() + if err != nil { + return nil, err + } + vr, err := ar.tr.GetMessageValueRenderer(internalMsg.ProtoReflect().Descriptor()) + if err != nil { + return nil, err + } + + subscreens, err := vr.Format(ctx, protoreflect.ValueOfMessage(internalMsg.ProtoReflect())) + if err != nil { + return nil, err + } + + screens := make([]Screen, 1+len(subscreens)) + screens[0].Text = "Object: " + anymsg.GetTypeUrl() + for i, subscreen := range subscreens { + subscreen.Indent++ + screens[i+1] = subscreen + } + + return screens, nil +} + +// Parse implements the ValueRenderer interface. +func (ar anyValueRenderer) Parse(ctx context.Context, screens []Screen) (protoreflect.Value, error) { + if len(screens) == 0 { + return nilValue, fmt.Errorf("expect at least one screen") + } + if screens[0].Indent != 0 { + return nilValue, fmt.Errorf("bad indentation: want 0, got %d", screens[0].Indent) + } + + prefix := "Object: " + if !strings.HasPrefix(screens[0].Text, prefix) { + return nilValue, fmt.Errorf("bad Any header: %s", screens[0].Text) + } + url := strings.TrimPrefix(screens[0].Text, prefix) + + msgType, err := protoregistry.GlobalTypes.FindMessageByURL(url) + if err != nil { + return nilValue, err + } + vr, err := ar.tr.GetMessageValueRenderer(msgType.Descriptor()) + if err != nil { + return nilValue, err + } + + subscreens := make([]Screen, len(screens)-1) + for i := 1; i < len(screens); i++ { + if screens[i].Indent < 1 { + return nilValue, fmt.Errorf("bad indent for subscreen %d: %d", i, screens[i].Indent) + } + subscreens[i-1] = screens[i] + subscreens[i-1].Indent-- + } + + internalMsg, err := vr.Parse(ctx, subscreens) + if err != nil { + return nilValue, err + } + + anyMsg, err := anypb.New(internalMsg.Message().Interface()) + if err != nil { + return nilValue, err + } + + return protoreflect.ValueOfMessage(anyMsg.ProtoReflect()), nil +} diff --git a/tx/textual/valuerenderer/any_test.go b/tx/textual/valuerenderer/any_test.go new file mode 100644 index 0000000000..1af3abaca5 --- /dev/null +++ b/tx/textual/valuerenderer/any_test.go @@ -0,0 +1,57 @@ +package valuerenderer_test + +import ( + "context" + "encoding/json" + "fmt" + "os" + "testing" + + "cosmossdk.io/tx/textual/valuerenderer" + + "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" + "google.golang.org/protobuf/types/known/anypb" +) + +type anyJsonTest struct { + Proto json.RawMessage + Screens []valuerenderer.Screen +} + +func TestAny(t *testing.T) { + raw, err := os.ReadFile("../internal/testdata/any.json") + require.NoError(t, err) + + var testcases []anyJsonTest + err = json.Unmarshal(raw, &testcases) + require.NoError(t, err) + + tr := valuerenderer.NewTextual(EmptyCoinMetadataQuerier) + for i, tc := range testcases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + anyMsg := anypb.Any{} + err = protojson.Unmarshal(tc.Proto, &anyMsg) + require.NoError(t, err) + + // Format into screens and check vs expected + rend := valuerenderer.NewAnyValueRenderer((&tr)) + screens, err := rend.Format(context.Background(), protoreflect.ValueOfMessage(anyMsg.ProtoReflect())) + require.NoError(t, err) + require.Equal(t, tc.Screens, screens) + + // Parse back into a google.Protobuf.Any message. + val, err := rend.Parse(context.Background(), screens) + require.NoError(t, err) + parsedMsg := val.Message().Interface() + require.IsType(t, &anypb.Any{}, parsedMsg) + parsedAny := parsedMsg.(*anypb.Any) + diff := cmp.Diff(anyMsg, parsedAny, protocmp.Transform()) + require.Empty(t, diff) + }) + } +} diff --git a/tx/textual/valuerenderer/bytes_test.go b/tx/textual/valuerenderer/bytes_test.go index f79dd407a4..ce0caf3cec 100644 --- a/tx/textual/valuerenderer/bytes_test.go +++ b/tx/textual/valuerenderer/bytes_test.go @@ -27,7 +27,7 @@ func TestBytesJsonTestCases(t *testing.T) { data, err := base64.StdEncoding.DecodeString(tc.base64) require.NoError(t, err) - valrend, err := textual.GetValueRenderer(fieldDescriptorFromName("BYTES")) + valrend, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("BYTES")) require.NoError(t, err) screens, err := valrend.Format(context.Background(), protoreflect.ValueOfBytes(data)) diff --git a/tx/textual/valuerenderer/coin_test.go b/tx/textual/valuerenderer/coin_test.go index 69e36a1d4a..abce7f9ea1 100644 --- a/tx/textual/valuerenderer/coin_test.go +++ b/tx/textual/valuerenderer/coin_test.go @@ -34,7 +34,7 @@ func mockCoinMetadataQuerier(ctx context.Context, denom string) (*bankv1beta1.Me func TestMetadataQuerier(t *testing.T) { // Errors on nil metadata querier textual := valuerenderer.NewTextual(nil) - vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN")) + vr, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) require.NoError(t, err) _, err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect())) require.Error(t, err) @@ -44,7 +44,7 @@ func TestMetadataQuerier(t *testing.T) { textual = valuerenderer.NewTextual(func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) { return nil, expErr }) - vr, err = textual.GetValueRenderer(fieldDescriptorFromName("COIN")) + vr, err = textual.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) require.NoError(t, err) _, err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect())) require.ErrorIs(t, err, expErr) @@ -60,7 +60,7 @@ func TestCoinJsonTestcases(t *testing.T) { require.NoError(t, err) textual := valuerenderer.NewTextual(mockCoinMetadataQuerier) - vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN")) + vr, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("COIN")) require.NoError(t, err) for _, tc := range testcases { diff --git a/tx/textual/valuerenderer/coins_test.go b/tx/textual/valuerenderer/coins_test.go index 9d3b2eea40..6bb64e6deb 100644 --- a/tx/textual/valuerenderer/coins_test.go +++ b/tx/textual/valuerenderer/coins_test.go @@ -21,7 +21,7 @@ func TestCoinsJsonTestcases(t *testing.T) { require.NoError(t, err) textual := valuerenderer.NewTextual(mockCoinMetadataQuerier) - vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COINS")) + vr, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("COINS")) require.NoError(t, err) for _, tc := range testcases { diff --git a/tx/textual/valuerenderer/dec_test.go b/tx/textual/valuerenderer/dec_test.go index 46fafac22e..afcacb49be 100644 --- a/tx/textual/valuerenderer/dec_test.go +++ b/tx/textual/valuerenderer/dec_test.go @@ -23,7 +23,7 @@ func TestDecJsonTestcases(t *testing.T) { for _, tc := range testcases { tc := tc t.Run(tc[0], func(t *testing.T) { - r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKDEC")) + r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("SDKDEC")) require.NoError(t, err) checkNumberTest(t, r, protoreflect.ValueOf(tc[0]), tc[1]) diff --git a/tx/textual/valuerenderer/enum_test.go b/tx/textual/valuerenderer/enum_test.go index 36e59f200b..baa68b8360 100644 --- a/tx/textual/valuerenderer/enum_test.go +++ b/tx/textual/valuerenderer/enum_test.go @@ -37,7 +37,7 @@ func TestEnumJsonTestcases(t *testing.T) { require.NoError(t, err) fd := getFd(tc.Proto, m) - valrend, err := textual.GetValueRenderer(fd) + valrend, err := textual.GetFieldValueRenderer(fd) require.NoError(t, err) val := m.ProtoReflect().Get(fd) diff --git a/tx/textual/valuerenderer/int_test.go b/tx/textual/valuerenderer/int_test.go index 37fd90fc36..0b9b69016e 100644 --- a/tx/textual/valuerenderer/int_test.go +++ b/tx/textual/valuerenderer/int_test.go @@ -28,7 +28,7 @@ func TestIntJsonTestcases(t *testing.T) { // Parse test case strings as protobuf uint64 i, err := strconv.ParseUint(tc[0], 10, 64) if err == nil { - r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT64")) + r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT64")) require.NoError(t, err) checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1]) @@ -37,7 +37,7 @@ func TestIntJsonTestcases(t *testing.T) { // Parse test case strings as protobuf uint32 i, err = strconv.ParseUint(tc[0], 10, 32) if err == nil { - r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT32")) + r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT32")) require.NoError(t, err) checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1]) @@ -46,7 +46,7 @@ func TestIntJsonTestcases(t *testing.T) { // Parse test case strings as sdk.Ints _, ok := math.NewIntFromString(tc[0]) if ok { - r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKINT")) + r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("SDKINT")) require.NoError(t, err) checkNumberTest(t, r, protoreflect.ValueOf(tc[0]), tc[1]) diff --git a/tx/textual/valuerenderer/message.go b/tx/textual/valuerenderer/message.go index e87d304bf8..dbfaae6486 100644 --- a/tx/textual/valuerenderer/message.go +++ b/tx/textual/valuerenderer/message.go @@ -42,14 +42,14 @@ func (mr *messageValueRenderer) Format(ctx context.Context, v protoreflect.Value screens[0].Text = mr.header() for _, fd := range mr.fds { - vr, err := mr.tr.GetValueRenderer(fd) + if !v.Message().Has(fd) { + // Skip default values. + continue + } + vr, err := mr.tr.GetFieldValueRenderer(fd) if err != nil { return nil, err } - // Skip default values. - if !v.Message().Has(fd) { - continue - } subscreens, err := vr.Format(ctx, v.Message().Get(fd)) if err != nil { @@ -116,7 +116,7 @@ func (mr *messageValueRenderer) Parse(ctx context.Context, screens []Screen) (pr break } - vr, err := mr.tr.GetValueRenderer(fd) + vr, err := mr.tr.GetFieldValueRenderer(fd) if err != nil { return nilValue, err } diff --git a/tx/textual/valuerenderer/valuerenderer.go b/tx/textual/valuerenderer/valuerenderer.go index 438990653e..d94d1ab959 100644 --- a/tx/textual/valuerenderer/valuerenderer.go +++ b/tx/textual/valuerenderer/valuerenderer.go @@ -6,6 +6,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -45,8 +46,8 @@ func NewTextual(q CoinMetadataQueryFn) Textual { return t } -// GetValueRenderer returns the value renderer for the given FieldDescriptor. -func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRenderer, error) { +// GetFieldValueRenderer returns the value renderer for the given FieldDescriptor. +func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueRenderer, error) { switch { // Scalars, such as sdk.Int and sdk.Dec encoded as strings. case fd.Kind() == protoreflect.StringKind && proto.GetExtension(fd.Options(), cosmos_proto.E_Scalar) != "": @@ -96,13 +97,25 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere // This will be implemented in https://github.com/cosmos/cosmos-sdk/issues/12714 return nil, fmt.Errorf("repeated field renderer not yet implemented") } - return NewMessageValueRenderer(&r, md), nil + return NewMessageValueRenderer(r, md), nil default: return nil, fmt.Errorf("value renderers cannot format value of type %s", fd.Kind()) } } +// 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) { + fullName := md.FullName() + vr, found := r.messages[fullName] + if found { + return vr, nil + } + return NewMessageValueRenderer(r, md), nil +} + func (r *Textual) init() { if r.scalars == nil { r.scalars = map[string]ValueRenderer{} @@ -114,6 +127,7 @@ func (r *Textual) init() { r.messages[(&basev1beta1.Coin{}).ProtoReflect().Descriptor().FullName()] = NewCoinsValueRenderer(r.coinMetadataQuerier) r.messages[(&durationpb.Duration{}).ProtoReflect().Descriptor().FullName()] = NewDurationValueRenderer() r.messages[(×tamppb.Timestamp{}).ProtoReflect().Descriptor().FullName()] = NewTimestampValueRenderer() + r.messages[(&anypb.Any{}).ProtoReflect().Descriptor().FullName()] = NewAnyValueRenderer(r) } } diff --git a/tx/textual/valuerenderer/valuerenderer_test.go b/tx/textual/valuerenderer/valuerenderer_test.go index 912b6aa228..a499158d53 100644 --- a/tx/textual/valuerenderer/valuerenderer_test.go +++ b/tx/textual/valuerenderer/valuerenderer_test.go @@ -27,6 +27,7 @@ func TestDispatcher(t *testing.T) { {"COIN", false, valuerenderer.NewCoinsValueRenderer(nil)}, {"COINS", false, valuerenderer.NewCoinsValueRenderer(nil)}, {"ENUM", false, valuerenderer.NewEnumValueRenderer(fieldDescriptorFromName("ENUM"))}, + {"ANY", false, valuerenderer.NewAnyValueRenderer(nil)}, {"FLOAT", true, nil}, } @@ -34,7 +35,7 @@ func TestDispatcher(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { textual := valuerenderer.NewTextual(nil) - rend, err := textual.GetValueRenderer(fieldDescriptorFromName(tc.name)) + rend, err := textual.GetFieldValueRenderer(fieldDescriptorFromName(tc.name)) if tc.expErr { require.Error(t, err)