Merge PR #3807: Custom MsgMultiSend Handler (x/bank fork)

This commit is contained in:
Alexander Bezobchuk 2019-03-06 10:41:51 -05:00 committed by Christopher Goes
parent 805e7fbfc2
commit bf7cbbbdf8
5 changed files with 328 additions and 3 deletions

View File

@ -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
@ -32,6 +36,7 @@ tags.
* [\#3751] Disable (temporarily) support for ED25519 account key pairs.
### Tendermint
* [\#3804] Update to Tendermint `v0.31.0-dev0`
<!--------------------------------- FEATURES --------------------------------->

View File

@ -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"
@ -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)).

View File

@ -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"

View File

@ -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:
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 {
// 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) {
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
}

View File

@ -0,0 +1,216 @@
package bank
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)})
}