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 <anil@vitwit.com>
Co-authored-by: Jack Zampolin <jack.zampolin@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Akhil Kumar P 2020-10-28 16:32:39 +05:30 committed by GitHub
parent afb6771726
commit c3638adddc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 0 deletions

View File

@ -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
// ----------------------------------------------------------------------------

View File

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