From 3d15233578f4cdca69276f56fad6232320419957 Mon Sep 17 00:00:00 2001 From: testinginprod <98415576+testinginprod@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:44:28 +0200 Subject: [PATCH] feat(codec): add collections value codec for protov2 APIs. (#17042) Co-authored-by: unknown unknown --- CHANGELOG.md | 1 + codec/collections.go | 55 ++++++++++++++++++++++++++++++++++++--- codec/collections_test.go | 25 ++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e45a0f71..e45c043767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (baseapp) [#16898](https://github.com/cosmos/cosmos-sdk/pull/16898) Add `preFinalizeBlockHook` to allow vote extensions persistence. * (cli) [#16887](https://github.com/cosmos/cosmos-sdk/pull/16887) Add two new CLI commands: `tx simulate` for simulating a transaction; `query block-results` for querying CometBFT RPC for block results. * (x/gov) [#16976](https://github.com/cosmos/cosmos-sdk/pull/16976) Add `failed_reason` field to `Proposal` under `x/gov` to indicate the reason for a failed proposal. Referenced from [#238](https://github.com/bnb-chain/greenfield-cosmos-sdk/pull/238) under `bnb-chain/greenfield-cosmos-sdk`. +* (codec) [#17042](https://github.com/cosmos/cosmos-sdk/pull/17042) Add `CollValueV2` which supports encoding of protov2 messages in collections. ### Improvements diff --git a/codec/collections.go b/codec/collections.go index c58da9cb8e..12f1c396d5 100644 --- a/codec/collections.go +++ b/codec/collections.go @@ -6,6 +6,8 @@ import ( "github.com/cosmos/gogoproto/proto" gogotypes "github.com/cosmos/gogoproto/types" + "google.golang.org/protobuf/encoding/protojson" + protov2 "google.golang.org/protobuf/proto" "cosmossdk.io/collections" collcodec "cosmossdk.io/collections/codec" @@ -51,10 +53,13 @@ type protoMessage[T any] interface { // CollValue inits a collections.ValueCodec for a generic gogo protobuf message. func CollValue[T any, PT protoMessage[T]](cdc BinaryCodec) collcodec.ValueCodec[T] { - return &collValue[T, PT]{cdc.(Codec)} + return &collValue[T, PT]{cdc.(Codec), proto.MessageName(PT(new(T)))} } -type collValue[T any, PT protoMessage[T]] struct{ cdc Codec } +type collValue[T any, PT protoMessage[T]] struct { + cdc Codec + messageName string +} func (c collValue[T, PT]) Encode(value T) ([]byte, error) { return c.cdc.Marshal(PT(&value)) @@ -79,7 +84,51 @@ func (c collValue[T, PT]) Stringify(value T) string { } func (c collValue[T, PT]) ValueType() string { - return "gogoproto/" + proto.MessageName(PT(new(T))) + return "github.com/cosmos/gogoproto/" + c.messageName +} + +type protoMessageV2[T any] interface { + *T + protov2.Message +} + +// CollValueV2 is used for protobuf values of the newest google.golang.org/protobuf API. +func CollValueV2[T any, PT protoMessageV2[T]]() collcodec.ValueCodec[PT] { + return &collValue2[T, PT]{ + messageName: string(PT(new(T)).ProtoReflect().Descriptor().FullName()), + } +} + +type collValue2[T any, PT protoMessageV2[T]] struct { + messageName string +} + +func (c collValue2[T, PT]) Encode(value PT) ([]byte, error) { + return protov2.Marshal(value) +} + +func (c collValue2[T, PT]) Decode(b []byte) (PT, error) { + var value T + err := protov2.Unmarshal(b, PT(&value)) + return &value, err +} + +func (c collValue2[T, PT]) EncodeJSON(value PT) ([]byte, error) { + return protojson.Marshal(value) +} + +func (c collValue2[T, PT]) DecodeJSON(b []byte) (PT, error) { + var value T + err := protojson.Unmarshal(b, PT(&value)) + return &value, err +} + +func (c collValue2[T, PT]) Stringify(value PT) string { + return fmt.Sprintf("%v", value) +} + +func (c collValue2[T, PT]) ValueType() string { + return "google.golang.org/protobuf/" + c.messageName } // CollInterfaceValue instantiates a new collections.ValueCodec for a generic diff --git a/codec/collections_test.go b/codec/collections_test.go index 3183c9228a..5aff0e0e06 100644 --- a/codec/collections_test.go +++ b/codec/collections_test.go @@ -4,7 +4,10 @@ import ( "testing" gogotypes "github.com/cosmos/gogoproto/types" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/wrapperspb" "cosmossdk.io/collections/colltest" @@ -21,6 +24,28 @@ func TestCollectionsCorrectness(t *testing.T) { }) }) + t.Run("CollValueV2", func(t *testing.T) { + // NOTE: we cannot use colltest.TestValueCodec because protov2 has different + // compare semantics than protov1. We need to use protocmp.Transform() alongside + // cmp to ensure equality. + encoder := codec.CollValueV2[wrapperspb.UInt64Value]() + value := &wrapperspb.UInt64Value{Value: 500} + encodedValue, err := encoder.Encode(value) + require.NoError(t, err) + decodedValue, err := encoder.Decode(encodedValue) + require.NoError(t, err) + require.True(t, cmp.Equal(value, decodedValue, protocmp.Transform()), "encoding and decoding produces different values") + + encodedJSONValue, err := encoder.EncodeJSON(value) + require.NoError(t, err) + decodedJSONValue, err := encoder.DecodeJSON(encodedJSONValue) + require.NoError(t, err) + require.True(t, cmp.Equal(value, decodedJSONValue, protocmp.Transform()), "encoding and decoding produces different values") + require.NotEmpty(t, encoder.ValueType()) + + _ = encoder.Stringify(value) + }) + t.Run("BoolValue", func(t *testing.T) { colltest.TestValueCodec(t, codec.BoolValue, true) colltest.TestValueCodec(t, codec.BoolValue, false)