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

Feature/named types #245

Merged
merged 13 commits into from Apr 14, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Features
* Add marker cli has two new flags to set SupplyFixed and AllowGovernanceControl #241
* Modify 'enable governance' behavior on marker module #227
* Typed Events and Metric counters in Name Module #85

### Improvements
* Add some extra aliases for the CLI query metadata commands.
Expand Down
14 changes: 13 additions & 1 deletion proto/provenance/name/v1/name.proto
Expand Up @@ -45,4 +45,16 @@ message CreateRootNameProposal {
string name = 3;
string owner = 4;
bool restricted = 5;
}
}

// Event emitted when name is bound.
message EventNameBound {
string address = 1;
string name = 2;
}

// Event emitted when name is unbound.
message EventNameUnbound {
string address = 1;
string name = 2;
}
159 changes: 159 additions & 0 deletions x/name/handler_test.go
@@ -0,0 +1,159 @@
package name_test

import (
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
simapp "github.com/provenance-io/provenance/app"
"github.com/provenance-io/provenance/x/name"
"github.com/provenance-io/provenance/x/name/keeper"
nametypes "github.com/provenance-io/provenance/x/name/types"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"strings"
"testing"
)

func TestInvalidMsg(t *testing.T) {
k := keeper.Keeper{}
h := name.NewHandler(k)

res, err := h(sdk.NewContext(nil, tmproto.Header{}, false, nil), testdata.NewTestMsg())
require.Error(t, err)
require.Nil(t, res)

_, _, log := sdkerrors.ABCIInfo(err, false)
require.True(t, strings.Contains(log, "unrecognized name message type"))
}

// create name record
func TestCreateName(t *testing.T) {
priv1 := secp256k1.GenPrivKey()
addr1 := sdk.AccAddress(priv1.PubKey().Address())
priv2 := secp256k1.GenPrivKey()
addr2 := sdk.AccAddress(priv2.PubKey().Address())

tests := []struct {
name string
expectedError error
msg *nametypes.MsgBindNameRequest
expectedEvent *nametypes.EventNameBound
}{
{
name: "create name record",
msg: nametypes.NewMsgBindNameRequest(nametypes.NewNameRecord("new", addr2, false), nametypes.NewNameRecord("example.name", addr1, false)),
expectedError: nil,
expectedEvent: &nametypes.EventNameBound{
Address: addr2.String(),
Name: "new.example.name",
},
},
{
name: "create bad name record",
msg: nametypes.NewMsgBindNameRequest(nametypes.NewNameRecord("new", addr2, false), nametypes.NewNameRecord("foo.name", addr1, false)),
expectedError: sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, nametypes.ErrNameNotBound.Error()),
},
}

acc1 := &authtypes.BaseAccount{
Address: addr1.String(),
}
accs := authtypes.GenesisAccounts{acc1}
app := simapp.SetupWithGenesisAccounts(accs)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
em := ctx.EventManager()
var nameData nametypes.GenesisState
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("name", addr1, false))
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("example.name", addr1, false))
nameData.Params.AllowUnrestrictedNames = false
nameData.Params.MaxNameLevels = 16
nameData.Params.MinSegmentLength = 2
nameData.Params.MaxSegmentLength = 16

app.NameKeeper.InitGenesis(ctx, nameData)

app.NameKeeper = keeper.NewKeeper(app.AppCodec(), app.GetKey(nametypes.ModuleName), app.GetSubspace(nametypes.ModuleName))
handler := name.NewHandler(app.NameKeeper)

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := handler(ctx, tc.msg)
if tc.expectedError != nil {
require.EqualError(t, err, tc.expectedError.Error())
} else {
require.NoError(t, err)
}
if tc.expectedEvent != nil {
require.Equal(t, 1, len(em.Events().ToABCIEvents()))
msg1, _ := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[0])
require.Equal(t, tc.expectedEvent, msg1)
}
})
}
}

// delete name record
func TestDeleteName(t *testing.T) {
priv1 := secp256k1.GenPrivKey()
addr1 := sdk.AccAddress(priv1.PubKey().Address())

tests := []struct {
name string
expectedError error
msg *nametypes.MsgDeleteNameRequest
expectedEvent *nametypes.EventNameUnbound
}{
{
name: "delete name record",
msg: nametypes.NewMsgDeleteNameRequest(nametypes.NewNameRecord("example.name", addr1, false)),
expectedError: nil,
expectedEvent: &nametypes.EventNameUnbound{
Address: addr1.String(),
Name: "example.name",
},
},
{
name: "create bad name record",
msg: nametypes.NewMsgDeleteNameRequest(nametypes.NewNameRecord("example.name", addr1, false)),
expectedError: sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "name does not exist"),
},
}

acc1 := &authtypes.BaseAccount{
Address: addr1.String(),
}
accs := authtypes.GenesisAccounts{acc1}
app := simapp.SetupWithGenesisAccounts(accs)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
em := ctx.EventManager()
var nameData nametypes.GenesisState
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("name", addr1, false))
nameData.Bindings = append(nameData.Bindings, nametypes.NewNameRecord("example.name", addr1, false))
nameData.Params.AllowUnrestrictedNames = false
nameData.Params.MaxNameLevels = 16
nameData.Params.MinSegmentLength = 2
nameData.Params.MaxSegmentLength = 16

app.NameKeeper.InitGenesis(ctx, nameData)

app.NameKeeper = keeper.NewKeeper(app.AppCodec(), app.GetKey(nametypes.ModuleName), app.GetSubspace(nametypes.ModuleName))
handler := name.NewHandler(app.NameKeeper)

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := handler(ctx, tc.msg)
if tc.expectedError != nil {
require.EqualError(t, err, tc.expectedError.Error())
} else {
require.NoError(t, err)
}
if tc.expectedEvent != nil {
require.Equal(t, 1, len(em.Events().ToABCIEvents()))
msg1, _ := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[0])
require.Equal(t, tc.expectedEvent, msg1)
}
})
}
}
64 changes: 50 additions & 14 deletions x/name/keeper/msg_server.go
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"

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

Expand Down Expand Up @@ -70,14 +72,36 @@ func (s msgServer) BindName(goCtx context.Context, msg *types.MsgBindNameRequest
ctx.Logger().Error("unable to bind name", "err", err)
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
}
// create event.
nameBoundEvent := types.EventNameBound{
Address: msg.Record.Address,
Name: name,
}

// before name of the event was `types.EventTypeNameBound` == name_bound
// because proto message format's do not encourage _ like convention
// but prefer CamelCase for message name, NameBound
// https://developers.google.com/protocol-buffers/docs/style
// Use CamelCase (with an initial capital) for message names – for example, SongServerRequest.
// Use underscore_separated_names for field names (including oneof field and extension names) – for example, song_name.

// Emit event and return
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeNameBound,
sdk.NewAttribute(types.KeyAttributeAddress, msg.Record.Address),
sdk.NewAttribute(types.KeyAttributeName, name),
),
)

// Sample event:
// [{"events":[{"type":"message","attributes":[{"key":"action","value":"bind_name"},{"key":"sender","value":"tp13ulywwfe7v38y0vetsqayccsgzexh6zq38h3d4"}]},{"type":"provenance.name.v1.EventNameBound","attributes":[{"key":"address","value":"\"tp13ulywwfe7v38y0vetsqayccsgzexh6zq38h3d4\""},{"key":"name","value":"\"sc1.pb\""}]},{"type":"transfer","attributes":[{"key":"recipient","value":"tp17xpfvakm2amg962yls6f84z3kell8c5l2udfyt"},{"key":"sender","value":"tp13ulywwfe7v38y0vetsqayccsgzexh6zq38h3d4"},{"key":"amount","value":"2000nhash"}]}]}]
if err := ctx.EventManager().EmitTypedEvent(&nameBoundEvent); err != nil {
return nil, err
}

// key: modulename+name+bind
defer func() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, "name", "bind"},
1,
[]metrics.Label{telemetry.NewLabel("name", name), telemetry.NewLabel("address", msg.Record.Address)},
)
}()

return &types.MsgBindNameResponse{}, nil
}

Expand Down Expand Up @@ -116,13 +140,25 @@ func (s msgServer) DeleteName(goCtx context.Context, msg *types.MsgDeleteNameReq
ctx.Logger().Error("error deleting name", "err", err)
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
}

// create name unbound event.
nameUnboundEvent := types.EventNameUnbound{
Address: msg.Record.Address,
Name: name,
}
// Emit event and return
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeNameUnbound,
sdk.NewAttribute(types.KeyAttributeAddress, msg.Record.Address),
sdk.NewAttribute(types.KeyAttributeName, msg.Record.Name),
),
)
if err := ctx.EventManager().EmitTypedEvent(&nameUnboundEvent); err != nil {
return nil, err
}

// key: modulename+name+unbind
defer func() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, "name", "unbind"},
1,
[]metrics.Label{telemetry.NewLabel("name", name), telemetry.NewLabel("address", msg.Record.Address)},
)
}()

return &types.MsgDeleteNameResponse{}, nil
}
2 changes: 1 addition & 1 deletion x/name/simulation/decoder_test.go
Expand Up @@ -15,7 +15,7 @@ import (
)

func TestDecodeStore(t *testing.T) {
cdc:= app.MakeEncodingConfig().Marshaler
cdc := app.MakeEncodingConfig().Marshaler
dec := simulation.NewDecodeStore(cdc)

testNameRecord := types.NewNameRecord("test", sdk.AccAddress{}, true)
Expand Down