Skip to content

Commit

Permalink
init: Implement ADR 032 typed events (#7564)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Jack Zampolin <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 28, 2020
1 parent afb6771 commit c3638ad
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
98 changes: 98 additions & 0 deletions types/events.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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)
}
Expand All @@ -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
// ----------------------------------------------------------------------------
Expand Down
34 changes: 34 additions & 0 deletions types/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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")),
Expand Down

0 comments on commit c3638ad

Please sign in to comment.