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

Refactor x/bank according to ADR 031 #7520

Merged
merged 7 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 6 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 buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ lint:
- COMMENT_FIELD
- SERVICE_SUFFIX
- PACKAGE_VERSION_SUFFIX
- RPC_REQUEST_STANDARD_NAME
ignore:
- tendermint
- gogoproto
Expand Down
15 changes: 15 additions & 0 deletions proto/cosmos/bank/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import "cosmos/bank/v1beta1/bank.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types";

// Msg defines the bank Msg service.
service Msg {
// Send defines a method for sending coins from one account to another account.
rpc Send(MsgSend) returns (MsgSendResponse);

// MultiSend defines a method for sending coins from some accounts to other accounts.
rpc MultiSend(MsgMultiSend) returns (MsgMultiSendResponse);
}

// MsgSend represents a message to send coins from one account to another.
message MsgSend {
option (gogoproto.equal) = false;
Expand All @@ -18,10 +27,16 @@ message MsgSend {
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
}

// MsgSendResponse defines the Msg/Send response type.
message MsgSendResponse { }

// MsgMultiSend represents an arbitrary multi-in, multi-out send message.
message MsgMultiSend {
option (gogoproto.equal) = false;

repeated Input inputs = 1 [(gogoproto.nullable) = false];
repeated Output outputs = 2 [(gogoproto.nullable) = false];
}

// MsgMultiSendResponse defines the Msg/MultiSend response type.
message MsgMultiSendResponse { }
30 changes: 30 additions & 0 deletions types/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"math"
"strings"

"github.com/gogo/protobuf/proto"

yaml "gopkg.in/yaml.v2"

abci "github.com/tendermint/tendermint/abci/types"
ctypes "github.com/tendermint/tendermint/rpc/core/types"

"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -263,3 +266,30 @@ func (r TxResponse) GetTx() Tx {
}
return nil
}

// WrapServiceResult wraps a result from a protobuf RPC service method call in
// a Result object or error. This method takes care of marshaling the res param to
// protobuf and attaching any events on the ctx.EventManager() to the Result.
func WrapServiceResult(ctx Context, res proto.Message, err error) (*Result, error) {
if err != nil {
return nil, err
}

var data []byte
if res != nil {
data, err = proto.Marshal(res)
if err != nil {
return nil, err
}
}

var events []abci.Event
if evtMgr := ctx.EventManager(); evtMgr != nil {
events = evtMgr.ABCIEvents()
}

return &Result{
Data: data,
Events: events,
}, nil
}
35 changes: 35 additions & 0 deletions types/result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ package types_test

import (
"encoding/hex"
"fmt"
"strings"
"testing"

"github.com/golang/protobuf/proto"

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

abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/bytes"
ctypes "github.com/tendermint/tendermint/rpc/core/types"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -180,3 +185,33 @@ func (s *resultTestSuite) TestResponseFormatBroadcastTxCommit() {
s.Require().Equal(want, sdk.NewResponseFormatBroadcastTxCommit(checkTxResult))
s.Require().Equal(want, sdk.NewResponseFormatBroadcastTxCommit(deliverTxResult))
}

func TestWrapServiceResult(t *testing.T) {
ctx := sdk.Context{}

res, err := sdk.WrapServiceResult(ctx, nil, fmt.Errorf("test"))
require.Nil(t, res)
require.NotNil(t, err)

res, err = sdk.WrapServiceResult(ctx, nil, nil)
require.NotNil(t, res)
require.Nil(t, err)
require.Empty(t, res.Events)

ctx = ctx.WithEventManager(sdk.NewEventManager())
ctx.EventManager().EmitEvent(sdk.NewEvent("test"))
res, err = sdk.WrapServiceResult(ctx, nil, nil)
require.NotNil(t, res)
require.Nil(t, err)
require.Len(t, res.Events, 1)

spot := testdata.Dog{Name: "spot"}
res, err = sdk.WrapServiceResult(ctx, &spot, nil)
require.NotNil(t, res)
require.Nil(t, err)
require.Len(t, res.Events, 1)
var spot2 testdata.Dog
err = proto.Unmarshal(res.Data, &spot2)
require.NoError(t, err)
require.Equal(t, spot, spot2)
}
89 changes: 6 additions & 83 deletions x/bank/handler.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package bank

import (
metrics "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"
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
Expand All @@ -15,93 +12,19 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())

msgServer := keeper.NewMsgServerImpl(k)

switch msg := msg.(type) {
case *types.MsgSend:
return handleMsgSend(ctx, k, msg)
res, err := msgServer.Send(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

case *types.MsgMultiSend:
return handleMsgMultiSend(ctx, k, msg)
res, err := msgServer.MultiSend(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized bank message type: %T", msg)
}
}
}

// Handle MsgSend.
func handleMsgSend(ctx sdk.Context, k keeper.Keeper, msg *types.MsgSend) (*sdk.Result, error) {
if err := k.SendEnabledCoins(ctx, msg.Amount...); err != nil {
return nil, err
}

from, err := sdk.AccAddressFromBech32(msg.FromAddress)
if err != nil {
return nil, err
}
to, err := sdk.AccAddressFromBech32(msg.ToAddress)
if err != nil {
return nil, err
}

if k.BlockedAddr(to) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress)
}

err = k.SendCoins(ctx, from, to, msg.Amount)
if err != nil {
return nil, err
}

defer func() {
for _, a := range msg.Amount {
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", "send"},
float32(a.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", a.Denom)},
)
}
}()

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)

return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}

// Handle MsgMultiSend.
func handleMsgMultiSend(ctx sdk.Context, k keeper.Keeper, msg *types.MsgMultiSend) (*sdk.Result, error) {
// NOTE: totalIn == totalOut should already have been checked
for _, in := range msg.Inputs {
if err := k.SendEnabledCoins(ctx, in.Coins...); err != nil {
return nil, err
}
}

for _, out := range msg.Outputs {
accAddr, err := sdk.AccAddressFromBech32(out.Address)
if err != nil {
panic(err)
}
if k.BlockedAddr(accAddr) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive transactions", out.Address)
}
}

err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
if err != nil {
return nil, err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)

return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}
104 changes: 104 additions & 0 deletions x/bank/keeper/msg_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package keeper

import (
"context"

"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"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)

type msgServer struct {
Keeper
}

// NewMsgServerImpl returns an implementation of the bank MsgServer interface
// for the provided Keeper.
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
return &msgServer{Keeper: keeper}
}

var _ types.MsgServer = msgServer{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why implement MsgServer on a separate struct, and not on Keeper itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the weird way bank has a keeper interface. Other modules should do what you're saying. And we should refactor bank in the near future anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ cc @atheeshp

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in few cases we still needed this implementation,
https://github.com/cosmos/cosmos-sdk/pull/7524/files#diff-825d33eeaa8e743955334330c753008b9af67e55c61d03b348556a246b9b4bddR94
in this scenario few of the keeper functions are colliding with the msg sever functions. wdyt @amaurymartiny, @aaronc ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use a separate struct wrapping the keeper then


func (k msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := k.SendEnabledCoins(ctx, msg.Amount...); err != nil {
return nil, err
}

from, err := sdk.AccAddressFromBech32(msg.FromAddress)
if err != nil {
return nil, err
}
to, err := sdk.AccAddressFromBech32(msg.ToAddress)
if err != nil {
return nil, err
}

if k.BlockedAddr(to) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress)
}

err = k.SendCoins(ctx, from, to, msg.Amount)
if err != nil {
return nil, err
}

defer func() {
for _, a := range msg.Amount {
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", "send"},
float32(a.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", a.Denom)},
)
}
}()

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)

return &types.MsgSendResponse{}, nil
}

func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*types.MsgMultiSendResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

// NOTE: totalIn == totalOut should already have been checked
for _, in := range msg.Inputs {
if err := k.SendEnabledCoins(ctx, in.Coins...); err != nil {
return nil, err
}
}

for _, out := range msg.Outputs {
accAddr, err := sdk.AccAddressFromBech32(out.Address)
if err != nil {
panic(err)
}
if k.BlockedAddr(accAddr) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive transactions", out.Address)
}
}

err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
if err != nil {
return nil, err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)

return &types.MsgMultiSendResponse{}, nil
}
Loading