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

Custom MsgMultiSend Handler (x/bank fork) #3807

Merged
merged 18 commits into from
Mar 6, 2019
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
5 changes: 5 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

### Gaia

* [\#3787] Fork the `x/bank` module into the Gaia application with only a
modified message handler, where the modified message handler behaves the same as
the standard `x/bank` message handler except for `MsgMultiSend` that must burn
exactly 9 atoms and transfer 1 atom, and `MsgSend` is disabled.
* [\#3789] Update validator creation flow:
* Remove `NewMsgCreateValidatorOnBehalfOf` and corresponding business logic
* Ensure the validator address equals the delegator address during
Expand All @@ -32,6 +36,7 @@ tags.
* [\#3751] Disable (temporarily) support for ED25519 account key pairs.

### Tendermint

* [\#3804] Update to Tendermint `v0.31.0-dev0`

<!--------------------------------- FEATURES --------------------------------->
Expand Down
7 changes: 6 additions & 1 deletion cmd/gaia/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"

// TODO: Remove once transfers are enabled.
gaiabank "github.com/cosmos/cosmos-sdk/cmd/gaia/app/x/bank"

bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -149,8 +152,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
)

// register message routes
//
// TODO: Use standard bank router once transfers are enabled.
app.Router().
AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)).
AddRoute(bank.RouterKey, gaiabank.NewHandler(app.bankKeeper)).
AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)).
AddRoute(distr.RouterKey, distr.NewHandler(app.distrKeeper)).
AddRoute(slashing.RouterKey, slashing.NewHandler(app.slashingKeeper)).
Expand Down
3 changes: 1 addition & 2 deletions cmd/gaia/app/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import (
"strings"
"time"

"github.com/cosmos/cosmos-sdk/x/bank"

tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
Expand Down
100 changes: 100 additions & 0 deletions cmd/gaia/app/x/bank/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package bank contains a forked version of the bank module. It only contains
// a modified message handler to support a very limited form of transfers during
// mainnet launch -- MsgMultiSend messages.
//
// NOTE: This fork should be removed entirely once transfers are enabled and
// the Gaia router should be reset to using the original bank module handler.
package bank

import (
"github.com/tendermint/tendermint/crypto"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)

var (
uatomDenom = "uatom"
atomsToUatoms = int64(1000000)

// BurnedCoinsAccAddr represents the burn account address used for
// MsgMultiSend message during the period for which transfers are disabled.
// Its Bech32 address is cosmos1x4p90uuy63fqzsheamn48vq88q3eusykf0a69v.
BurnedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("bankBurnedCoins")))
)

// NewHandler returns a handler for "bank" type messages.
func NewHandler(k bank.Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case bank.MsgSend:
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
return handleMsgSend(ctx, k, msg)

case bank.MsgMultiSend:
return handleMsgMultiSend(ctx, k, msg)

default:
errMsg := "Unrecognized bank Msg type: %s" + msg.Type()
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}

// handleMsgSend implements a MsgSend message handler. It operates no differently
// than the standard bank module MsgSend message handler in that it transfers
// an amount from one account to another under the condition of transfers being
// enabled.
func handleMsgSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgSend) sdk.Result {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// No need to modify handleMsgSend as the forked module requires no changes,
// so we can just call the standard bank modules handleMsgSend since we know
// the message is of type MsgSend.
return bank.NewHandler(k)(ctx, msg)
}

// handleMsgMultiSend implements a modified forked version of a MsgMultiSend
// message handler. If transfers are disabled, a modified version of MsgMultiSend
// is allowed where there must be a single input and only two outputs. The first
// of the two outputs must be to a specific burn address defined by
// burnedCoinsAccAddr. In addition, the output amounts must be of 9atom and
// 1uatom respectively.
func handleMsgMultiSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgMultiSend) sdk.Result {
// NOTE: totalIn == totalOut should already have been checked
if !k.GetSendEnabled(ctx) {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
if !validateMultiSendTransfersDisabled(msg) {
return bank.ErrSendDisabled(k.Codespace()).Result()
}
}

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

return sdk.Result{
Tags: tags,
}
}

func validateMultiSendTransfersDisabled(msg bank.MsgMultiSend) bool {
nineAtoms := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 9*atomsToUatoms)}
oneAtom := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 1*atomsToUatoms)}

if len(msg.Inputs) != 1 {
return false
}
if len(msg.Outputs) != 2 {
return false
}

if !msg.Outputs[0].Address.Equals(BurnedCoinsAccAddr) {
return false
}
if !msg.Outputs[0].Coins.IsEqual(nineAtoms) {
return false
}
if !msg.Outputs[1].Coins.IsEqual(oneAtom) {
return false
}

return true
}
216 changes: 216 additions & 0 deletions cmd/gaia/app/x/bank/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package bank
Copy link
Member

Choose a reason for hiding this comment

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

love these testcases @alexanderbez !


import (
"testing"
"time"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/params"
)

var (
addrs = []sdk.AccAddress{
sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()),
sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()),
sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()),
}

initAmt = sdk.NewInt(atomsToUatoms * 100)
)

type testInput struct {
ctx sdk.Context
accKeeper auth.AccountKeeper
bankKeeper bank.Keeper
}

func newTestCodec() *codec.Codec {
cdc := codec.New()

bank.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)

return cdc
}

func createTestInput(t *testing.T) testInput {
keyAcc := sdk.NewKVStoreKey(auth.StoreKey)
keyParams := sdk.NewKVStoreKey(params.StoreKey)
tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey)

cdc := newTestCodec()
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db)
ctx := sdk.NewContext(ms, abci.Header{Time: time.Now().UTC()}, false, log.NewNopLogger())

ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db)
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)

require.NoError(t, ms.LoadLatestVersion())

paramsKeeper := params.NewKeeper(cdc, keyParams, tKeyParams)
accKeeper := auth.NewAccountKeeper(
cdc,
keyAcc,
paramsKeeper.Subspace(auth.DefaultParamspace),
auth.ProtoBaseAccount,
)

bankKeeper := bank.NewBaseKeeper(
accKeeper,
paramsKeeper.Subspace(bank.DefaultParamspace),
bank.DefaultCodespace,
)

for _, addr := range addrs {
_, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)})
require.NoError(t, err)
}

return testInput{ctx, accKeeper, bankKeeper}
}

func TestHandlerMsgSendTransfersDisabled(t *testing.T) {
input := createTestInput(t)
input.bankKeeper.SetSendEnabled(input.ctx, false)

handler := NewHandler(input.bankKeeper)
amt := sdk.NewInt(atomsToUatoms * 5)
msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)})

res := handler(input.ctx, msg)
require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log)

from := input.accKeeper.GetAccount(input.ctx, addrs[0])
require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)})

to := input.accKeeper.GetAccount(input.ctx, addrs[1])
require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)})
}

func TestHandlerMsgSendTransfersEnabled(t *testing.T) {
input := createTestInput(t)
input.bankKeeper.SetSendEnabled(input.ctx, true)

handler := NewHandler(input.bankKeeper)
amt := sdk.NewInt(atomsToUatoms * 5)
msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)})

res := handler(input.ctx, msg)
require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log)

from := input.accKeeper.GetAccount(input.ctx, addrs[0])
balance := initAmt.Sub(amt)
require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})

to := input.accKeeper.GetAccount(input.ctx, addrs[1])
balance = initAmt.Add(amt)
require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})
}

func TestHandlerMsgMultiSendTransfersDisabledBurn(t *testing.T) {
input := createTestInput(t)
input.bankKeeper.SetSendEnabled(input.ctx, false)

handler := NewHandler(input.bankKeeper)
totalAmt := sdk.NewInt(10 * atomsToUatoms)
burnAmt := sdk.NewInt(9 * atomsToUatoms)
transAmt := sdk.NewInt(1 * atomsToUatoms)
msg := bank.NewMsgMultiSend(
[]bank.Input{
bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}),
},
[]bank.Output{
bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}),
bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}),
},
)

res := handler(input.ctx, msg)
require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log)

from := input.accKeeper.GetAccount(input.ctx, addrs[0])
balance := initAmt.Sub(totalAmt)
require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})

to := input.accKeeper.GetAccount(input.ctx, addrs[1])
balance = initAmt.Add(transAmt)
require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})

burn := input.accKeeper.GetAccount(input.ctx, BurnedCoinsAccAddr)
require.Equal(t, burn.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)})
}

func TestHandlerMsgMultiSendTransfersDisabledInvalidBurn(t *testing.T) {
input := createTestInput(t)
input.bankKeeper.SetSendEnabled(input.ctx, false)

handler := NewHandler(input.bankKeeper)
totalAmt := sdk.NewInt(15 * atomsToUatoms)
burnAmt := sdk.NewInt(10 * atomsToUatoms)
transAmt := sdk.NewInt(5 * atomsToUatoms)
msg := bank.NewMsgMultiSend(
[]bank.Input{
bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}),
},
[]bank.Output{
bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}),
bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}),
},
)

res := handler(input.ctx, msg)
require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log)

from := input.accKeeper.GetAccount(input.ctx, addrs[0])
require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)})

to := input.accKeeper.GetAccount(input.ctx, addrs[1])
require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)})
}

func TestHandlerMsgMultiSendTransfersEnabled(t *testing.T) {
input := createTestInput(t)
input.bankKeeper.SetSendEnabled(input.ctx, true)

handler := NewHandler(input.bankKeeper)
totalAmt := sdk.NewInt(10 * atomsToUatoms)
outAmt := sdk.NewInt(5 * atomsToUatoms)
msg := bank.NewMsgMultiSend(
[]bank.Input{
bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}),
},
[]bank.Output{
bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}),
bank.NewOutput(addrs[2], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}),
},
)

res := handler(input.ctx, msg)
require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log)

from := input.accKeeper.GetAccount(input.ctx, addrs[0])
balance := initAmt.Sub(totalAmt)
require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})

out1 := input.accKeeper.GetAccount(input.ctx, addrs[1])
balance = initAmt.Add(outAmt)
require.Equal(t, out1.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})

out2 := input.accKeeper.GetAccount(input.ctx, addrs[2])
balance = initAmt.Add(outAmt)
require.Equal(t, out2.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)})
}