Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typed events and metrics to the Attribute module #279

Merged
merged 6 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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