From c3638adddc1a0e43e3b63f7cf79edee6bcd9b8ee Mon Sep 17 00:00:00 2001 From: Akhil Kumar P <36399231+akhilkumarpilli@users.noreply.github.com> Date: Wed, 28 Oct 2020 16:32:39 +0530 Subject: [PATCH] init: Implement ADR 032 typed events (#7564) * Add EmitTypedEvent in events * Add parseTypedEvent method * Use jsonpb * Modify unmarshal proto in events * Add a test for typed events * Fix reflect issue in parseTypedEvent * Modify event tests and add comments * Add EmitTypedEvents and refactor other methods * Fix golangci-lint issues * Update ProtoMarshalJSON params * Address PR comments Co-authored-by: anilCSE Co-authored-by: Jack Zampolin Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- types/events.go | 98 ++++++++++++++++++++++++++++++++++++++++++++ types/events_test.go | 34 +++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/types/events.go b/types/events.go index d334d3d60b..44d48d9cd5 100644 --- a/types/events.go +++ b/types/events.go @@ -1,10 +1,15 @@ package types import ( + "encoding/json" "fmt" + "reflect" "sort" "strings" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/gogo/protobuf/jsonpb" + proto "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" ) @@ -25,11 +30,13 @@ func NewEventManager() *EventManager { func (em *EventManager) Events() Events { return em.events } // EmitEvent stores a single Event object. +// Deprecated: Use EmitTypedEvent func (em *EventManager) EmitEvent(event Event) { em.events = em.events.AppendEvent(event) } // EmitEvents stores a series of Event objects. +// Deprecated: Use EmitTypedEvents func (em *EventManager) EmitEvents(events Events) { em.events = em.events.AppendEvents(events) } @@ -39,6 +46,97 @@ func (em EventManager) ABCIEvents() []abci.Event { return em.events.ToABCIEvents() } +// EmitTypedEvent takes typed event and emits converting it into Event +func (em *EventManager) EmitTypedEvent(tev proto.Message) error { + event, err := TypedEventToEvent(tev) + if err != nil { + return err + } + + em.EmitEvent(event) + return nil +} + +// EmitTypedEvents takes series of typed events and emit +func (em *EventManager) EmitTypedEvents(tevs ...proto.Message) error { + events := make(Events, len(tevs)) + for i, tev := range tevs { + res, err := TypedEventToEvent(tev) + if err != nil { + return err + } + events[i] = res + } + + em.EmitEvents(events) + return nil +} + +// TypedEventToEvent takes typed event and converts to Event object +func TypedEventToEvent(tev proto.Message) (Event, error) { + evtType := proto.MessageName(tev) + evtJSON, err := codec.ProtoMarshalJSON(tev, nil) + if err != nil { + return Event{}, err + } + + var attrMap map[string]json.RawMessage + err = json.Unmarshal(evtJSON, &attrMap) + if err != nil { + return Event{}, err + } + + attrs := make([]abci.EventAttribute, 0, len(attrMap)) + for k, v := range attrMap { + attrs = append(attrs, abci.EventAttribute{ + Key: []byte(k), + Value: v, + }) + } + + return Event{ + Type: evtType, + Attributes: attrs, + }, nil +} + +// ParseTypedEvent converts abci.Event back to typed event +func ParseTypedEvent(event abci.Event) (proto.Message, error) { + concreteGoType := proto.MessageType(event.Type) + if concreteGoType == nil { + return nil, fmt.Errorf("failed to retrieve the message of type %q", event.Type) + } + + var value reflect.Value + if concreteGoType.Kind() == reflect.Ptr { + value = reflect.New(concreteGoType.Elem()) + } else { + value = reflect.Zero(concreteGoType) + } + + protoMsg, ok := value.Interface().(proto.Message) + if !ok { + return nil, fmt.Errorf("%q does not implement proto.Message", event.Type) + } + + attrMap := make(map[string]json.RawMessage) + for _, attr := range event.Attributes { + attrMap[string(attr.Key)] = attr.Value + } + + attrBytes, err := json.Marshal(attrMap) + if err != nil { + return nil, err + } + + err = jsonpb.Unmarshal(strings.NewReader(string(attrBytes)), protoMsg) + if err != nil { + return nil, err + } + + return protoMsg, nil +} + // ---------------------------------------------------------------------------- // Events // ---------------------------------------------------------------------------- diff --git a/types/events_test.go b/types/events_test.go index d712e24cd8..7363355fb1 100644 --- a/types/events_test.go +++ b/types/events_test.go @@ -2,11 +2,14 @@ package types_test import ( "encoding/json" + "reflect" "testing" "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + testdata "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -68,6 +71,37 @@ func (s *eventsTestSuite) TestEventManager() { s.Require().Equal(em.Events(), events.AppendEvent(event)) } +func (s *eventsTestSuite) TestEventManagerTypedEvents() { + em := sdk.NewEventManager() + + coin := sdk.NewCoin("fakedenom", sdk.NewInt(1999999)) + cat := testdata.Cat{ + Moniker: "Garfield", + Lives: 6, + } + animal, err := codectypes.NewAnyWithValue(&cat) + s.Require().NoError(err) + hasAnimal := testdata.HasAnimal{ + X: 1000, + Animal: animal, + } + + s.Require().NoError(em.EmitTypedEvents(&coin)) + s.Require().NoError(em.EmitTypedEvent(&hasAnimal)) + s.Require().Len(em.Events(), 2) + + msg1, err := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[0]) + s.Require().NoError(err) + s.Require().Equal(coin.String(), msg1.String()) + s.Require().Equal(reflect.TypeOf(&coin), reflect.TypeOf(msg1)) + + msg2, err := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[1]) + s.Require().NoError(err) + s.Require().Equal(reflect.TypeOf(&hasAnimal), reflect.TypeOf(msg2)) + response := msg2.(*testdata.HasAnimal) + s.Require().Equal(hasAnimal.Animal.String(), response.Animal.String()) +} + func (s *eventsTestSuite) TestStringifyEvents() { e := sdk.Events{ sdk.NewEvent("message", sdk.NewAttribute("sender", "foo")),