Skip to content

Commit

Permalink
Add typed events and metrics to the Attribute module (#279)
Browse files Browse the repository at this point in the history
* Add attribute add/delete typed events, Add emit typed events from keeper, Add handler_tests.go tests

* Add telemetry for add and delete attribute

* Add changelog for feature 86

* Add attribute size gauge, Add keeper time measurement

* Linkify change log

* Add linkify to makefile
  • Loading branch information
channa-figure authored Apr 29, 2021
1 parent eede82d commit 0141016
Show file tree
Hide file tree
Showing 9 changed files with 940 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

* Add grpc messages and cli command to add/remove addresses from metadata scope data access [#220](https://github.com/provenance-io/provenance/issues/220)
* Add a `context` field to the `Session` [#276](https://github.com/provenance-io/provenance/issues/276)
* Add typed events and telemetry metrics to attribute module [#86](https://github.com/provenance-io/provenance/issues/86)

### Improvements

Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,10 @@ statik:
$(GO) get -u github.com/rakyll/statik
$(GO) generate ./api/...

.PHONY: go-mod-cache go.sum lint clean format check-built statik
linkify:
python ./scripts/linkify.py CHANGELOG.md

.PHONY: go-mod-cache go.sum lint clean format check-built statik linkify


##############################
Expand Down
16 changes: 16 additions & 0 deletions proto/provenance/attribute/v1/attribute.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,19 @@ enum AttributeType {
// ATTRIBUTE_TYPE_BYTES defines an attribute value that contains an untyped array of bytes
ATTRIBUTE_TYPE_BYTES = 8 [(gogoproto.enumvalue_customname) = "Bytes"];
}

// EventAttributeAdd event emitted when attribute is added
message EventAttributeAdd {
string name = 1;
string value = 2;
string type = 3;
string account = 4;
string owner = 5;
}

// EventAttributeDelete event emitted when attribute is deleted
message EventAttributeDelete {
string name = 1;
string account = 2;
string owner = 3;
}
150 changes: 150 additions & 0 deletions x/attribute/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package attribute_test

import (
"fmt"
"testing"

"github.com/golang/protobuf/proto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/provenance-io/provenance/app"
"github.com/provenance-io/provenance/x/attribute"
"github.com/provenance-io/provenance/x/attribute/types"
nametypes "github.com/provenance-io/provenance/x/name/types"
)

type HandlerTestSuite struct {
suite.Suite

app *app.App
ctx sdk.Context
handler sdk.Handler

pubkey1 cryptotypes.PubKey
user1 string
user1Addr sdk.AccAddress

pubkey2 cryptotypes.PubKey
user2 string
user2Addr sdk.AccAddress
}

func (s *HandlerTestSuite) SetupTest() {
s.app = app.Setup(false)
s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{})
s.handler = attribute.NewHandler(s.app.AttributeKeeper)

s.pubkey1 = secp256k1.GenPrivKey().PubKey()
s.user1Addr = sdk.AccAddress(s.pubkey1.Address())
s.user1 = s.user1Addr.String()

s.pubkey2 = secp256k1.GenPrivKey().PubKey()
s.user2Addr = sdk.AccAddress(s.pubkey2.Address())
s.user2 = s.user2Addr.String()

s.app.AccountKeeper.SetAccount(s.ctx, s.app.AccountKeeper.NewAccountWithAddress(s.ctx, s.user1Addr))

var nameData nametypes.GenesisState
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("name", s.user1Addr, false))
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("example.name", s.user1Addr, false))
nameData.Params.AllowUnrestrictedNames = false
nameData.Params.MaxNameLevels = 16
nameData.Params.MinSegmentLength = 2
nameData.Params.MaxSegmentLength = 16

s.app.NameKeeper.InitGenesis(s.ctx, nameData)

}

func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}

type CommonTest struct {
name string
msg sdk.Msg
signers []string
errorMsg string
expectedEvent proto.Message
}

func (s HandlerTestSuite) containsMessage(msg proto.Message) bool {
events := s.ctx.EventManager().Events().ToABCIEvents()
for _, event := range events {
typeEvent, _ := sdk.ParseTypedEvent(event)
if assert.ObjectsAreEqual(msg, typeEvent) {
return true
}
}
return false
}

func (s HandlerTestSuite) runTests(cases []CommonTest) {
for _, tc := range cases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := s.handler(s.ctx, tc.msg)

if len(tc.errorMsg) > 0 {
assert.EqualError(t, err, tc.errorMsg)
} else {
if tc.expectedEvent != nil {
result := s.containsMessage(tc.expectedEvent)
s.True(result, fmt.Sprintf("Expected typed event was not found: %v", tc.expectedEvent))
}

}
})
}
}

func (s HandlerTestSuite) TestMsgAddAttributeRequest() {
cases := []CommonTest{
{
"should successfully add new attribute",
types.NewMsgAddAttributeRequest(s.user1Addr,
s.user1Addr, "example.name", types.AttributeType_String, []byte("value")),
[]string{s.user1},
"",
types.NewEventAttributeAdd(
types.Attribute{
Address: s.user1,
Name: "example.name",
Value: []byte("value"),
AttributeType: types.AttributeType_String,
},
s.user1),
},
}
s.runTests(cases)
}

func (s HandlerTestSuite) TestMsgDeleteAttributeRequest() {
testAttr := types.Attribute{
Address: s.user1,
Name: "example.name",
Value: []byte("value"),
AttributeType: types.AttributeType_String,
}
var attrData types.GenesisState
attrData.Attributes = append(attrData.Attributes, testAttr)
attrData.Params.MaxValueLength = 100
s.app.AttributeKeeper.InitGenesis(s.ctx, &attrData)

cases := []CommonTest{
{
"should successfully add new attribute",
types.NewMsgDeleteAttributeRequest(s.user1Addr, s.user1Addr, "example.name"),
[]string{s.user1},
"",
types.NewEventAttributeDelete("example.name", s.user1, s.user1),
},
}
s.runTests(cases)
}
20 changes: 20 additions & 0 deletions x/attribute/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"bytes"
"fmt"
"strings"
"time"

"github.com/tendermint/tendermint/libs/log"

"github.com/provenance-io/provenance/x/attribute/types"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
)
Expand Down Expand Up @@ -64,12 +66,16 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {

// GetAllAttributes gets all attributes for account.
func (k Keeper) GetAllAttributes(ctx sdk.Context, acc sdk.AccAddress) ([]types.Attribute, error) {
defer telemetry.MeasureSince(time.Now(), types.ModuleName, "keeper_method", "get_all")

pred := func(s string) bool { return true }
return k.prefixScan(ctx, types.AccountAttributesKeyPrefix(acc), pred)
}

// GetAttributes gets all attributes with the given name from an account.
func (k Keeper) GetAttributes(ctx sdk.Context, acc sdk.AccAddress, name string) ([]types.Attribute, error) {
defer telemetry.MeasureSince(time.Now(), types.ModuleName, "keeper_method", "get")

name = strings.ToLower(strings.TrimSpace(name))
if _, err := k.nameKeeper.GetRecordByName(ctx, name); err != nil { // Ensure name exists (ie was bound to an address)
return nil, err
Expand Down Expand Up @@ -108,6 +114,8 @@ func (k Keeper) IterateRecords(ctx sdk.Context, prefix []byte, handle Handler) e
func (k Keeper) SetAttribute(
ctx sdk.Context, acc sdk.AccAddress, attr types.Attribute, owner sdk.AccAddress,
) error {
defer telemetry.MeasureSince(time.Now(), types.ModuleName, "keeper_method", "set")

// Ensure attribute is valid
if err := attr.ValidateBasic(); err != nil {
return err
Expand Down Expand Up @@ -142,11 +150,18 @@ func (k Keeper) SetAttribute(
key := types.AccountAttributeKey(acc, attr)
store := ctx.KVStore(k.storeKey)
store.Set(key, bz)

attributeAddEvent := types.NewEventAttributeAdd(attr, owner.String())
if err := ctx.EventManager().EmitTypedEvent(attributeAddEvent); err != nil {
return err
}

return nil
}

// Removes attributes under the given account. The attribute name must resolve to the given owner address.
func (k Keeper) DeleteAttribute(ctx sdk.Context, acc sdk.AccAddress, name string, owner sdk.AccAddress) error {
defer telemetry.MeasureSince(time.Now(), types.ModuleName, "keeper_method", "delete")
// Verify an account exists for the given owner address
if ownerAcc := k.authKeeper.GetAccount(ctx, owner); ownerAcc == nil {
return fmt.Errorf("no account found for owner address \"%s\"", owner.String())
Expand All @@ -168,6 +183,11 @@ func (k Keeper) DeleteAttribute(ctx sdk.Context, acc sdk.AccAddress, name string
if attr.Name == name {
count++
store.Delete(it.Key())

attributeDeleteEvent := types.NewEventAttributeDelete(name, acc.String(), owner.String())
if err := ctx.EventManager().EmitTypedEvent(attributeDeleteEvent); err != nil {
return err
}
}
}
if count == 0 {
Expand Down
30 changes: 28 additions & 2 deletions x/attribute/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
"context"

"github.com/armon/go-metrics"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"

Expand Down Expand Up @@ -46,7 +47,22 @@ func (k msgServer) AddAttribute(goCtx context.Context, msg *types.MsgAddAttribut
return nil, err
}

defer telemetry.IncrCounter(1, types.ModuleName, "attribute")
defer func() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, types.EventTelemetryKeyAdd},
1,
[]metrics.Label{
telemetry.NewLabel(types.EventTelemetryLabelName, msg.Name),
telemetry.NewLabel(types.EventTelemetryLabelType, msg.AttributeType.String()),
telemetry.NewLabel(types.EventTelemetryLabelAccount, msg.Account),
telemetry.NewLabel(types.EventTelemetryLabelOwner, msg.Owner),
},
)
telemetry.SetGaugeWithLabels(
[]string{types.ModuleName, types.EventTelemetryKeyAdd},
float32(len(msg.Value)),
[]metrics.Label{telemetry.NewLabel(types.EventTelemetryLabelSize, msg.Name)})
}()

ctx.EventManager().EmitEvent(
sdk.NewEvent(
Expand Down Expand Up @@ -77,7 +93,17 @@ func (k msgServer) DeleteAttribute(goCtx context.Context, msg *types.MsgDeleteAt
return nil, err
}

defer telemetry.IncrCounter(1, types.ModuleName, "attribute")
defer func() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, types.EventTelemetryKeyDelete},
1,
[]metrics.Label{
telemetry.NewLabel(types.EventTelemetryLabelName, msg.Name),
telemetry.NewLabel(types.EventTelemetryLabelAccount, msg.Account),
telemetry.NewLabel(types.EventTelemetryLabelOwner, msg.Owner),
},
)
}()

ctx.EventManager().EmitEvent(
sdk.NewEvent(
Expand Down
Loading

0 comments on commit 0141016

Please sign in to comment.