diff --git a/CHANGELOG.md b/CHANGELOG.md index fedeb5a846..8e07e963f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ *TBD* BREAKING CHANGES +* msg.GetSignBytes() returns sorted JSON (by key) +* msg.GetSignBytes() field changes + * `msg_bytes` -> `msgs` + * `fee_bytes` -> `fee` * Update Tendermint to v0.22.0 * Default ports changed from 466xx to 266xx * Amino JSON uses type names instead of prefix bytes @@ -41,6 +45,9 @@ BREAKING CHANGES * [lcd] Switch key creation output to return bech32 * [x/stake] store-value for delegation, validator, ubd, and red do not hold duplicate information contained store-key +DEPRECATED +* [cli] Deprecate `--name` flag in commands that send txs, in favor of `--from` + FEATURES * [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag * [lcd] Queried TXs now include the tx hash to identify each tx @@ -70,7 +77,13 @@ FEATURES * [gaiacli] Ledger support added - You can now use a Ledger with `gaiacli --ledger` for all key-related commands - Ledger keys can be named and tracked locally in the key DB -* [gaiacli] added an --async flag to the cli to deliver transactions without waiting for a tendermint response +* [testing] created a randomized testing framework. + - Currently bank has limited functionality in the framework + - Auth has its invariants checked within the framework +* [gaiacli] added the following flags for commands that post transactions to the chain: + * async -- send the tx without waiting for a tendermint response + * json -- return the output in json format for increased readability + * print-response -- return the tx response. (includes fields like gas cost) IMPROVEMENTS * bank module uses go-wire codec instead of 'encoding/json' @@ -79,13 +92,16 @@ IMPROVEMENTS * [stake] module reorganized to include `types` and `keeper` package * [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency) * [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value) -* [types] added common tag constants * [stake] offload more generic functionality from the handler into the keeper +* [types] added common tag constants +* [keys] improve error message when deleting non-existent key +* [gaiacli] improve error messages on `send` and `account` commands * added contributing guidelines BUG FIXES * [x/slashing] \#1510 Unrevoked validators cannot un-revoke themselves * [x/stake] \#1513 Validators slashed to zero power are unbonded and removed from the store +* [x/stake] \#1567 Validators decreased in power but not unbonded are now updated in Tendermint * [gaia] Added self delegation for validators in the genesis creation * [lcd] tests now don't depend on raw json text * [stake] error strings lower case @@ -100,6 +116,8 @@ BUG FIXES * \#1353 - CLI: Show pool shares fractions in human-readable format * \#1258 - printing big.rat's can no longer overflow int64 * \#887 - limit the size of rationals that can be passed in from user input +* \#1461 - CLI tests now no longer reset your local environment data +* \#1505 - `gaiacli stake validator` no longer panics if validator doesn't exist ## 0.19.0 diff --git a/Gopkg.lock b/Gopkg.lock index 7a9b566965..8794539880 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -241,7 +241,7 @@ "nfs", "xfs" ] - revision = "40f013a808ec4fa79def444a1a56de4d1727efcb" + revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] branch = "master" @@ -440,7 +440,7 @@ branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "7138fd3d9dc8335c567ca206f4333fb75eb05d56" + revision = "151529c776cdc58ddbe7963ba9af779f3577b419" [[projects]] name = "golang.org/x/text" diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index e919375d90..2ed89c53ff 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -1,7 +1,6 @@ package baseapp import ( - "encoding/json" "fmt" "os" "testing" @@ -19,7 +18,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" ) func defaultLogger() log.Logger { @@ -651,324 +649,6 @@ func TestValidatorChange(t *testing.T) { //---------------------------------------- -// Use burn and send msg types to test multiple msgs in one tx -type testBurnMsg struct { - Addr sdk.Address - Amount sdk.Coins -} - -const msgType3 = "burn" - -func (msg testBurnMsg) Type() string { return msgType3 } -func (msg testBurnMsg) GetSignBytes() []byte { - bz, _ := json.Marshal(msg) - return bz -} -func (msg testBurnMsg) ValidateBasic() sdk.Error { - if msg.Addr == nil { - return sdk.ErrInvalidAddress("Cannot use nil as Address") - } - return nil -} -func (msg testBurnMsg) GetSigners() []sdk.Address { - return []sdk.Address{msg.Addr} -} - -type testSendMsg struct { - Sender sdk.Address - Receiver sdk.Address - Amount sdk.Coins -} - -const msgType4 = "send" - -func (msg testSendMsg) Type() string { return msgType4 } -func (msg testSendMsg) GetSignBytes() []byte { - bz, _ := json.Marshal(msg) - return bz -} -func (msg testSendMsg) ValidateBasic() sdk.Error { - if msg.Sender == nil || msg.Receiver == nil { - return sdk.ErrInvalidAddress("Cannot use nil as Address") - } - return nil -} -func (msg testSendMsg) GetSigners() []sdk.Address { - return []sdk.Address{msg.Sender} -} - -// Simple Handlers for burn and send - -func newHandleBurn(keeper bank.Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - burnMsg := msg.(testBurnMsg) - _, _, err := keeper.SubtractCoins(ctx, burnMsg.Addr, burnMsg.Amount) - if err != nil { - return err.Result() - } - return sdk.Result{} - } -} - -func newHandleSpend(keeper bank.Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - spendMsg := msg.(testSendMsg) - _, _, err := keeper.SubtractCoins(ctx, spendMsg.Sender, spendMsg.Amount) - if err != nil { - return err.Result() - } - _, _, err = keeper.AddCoins(ctx, spendMsg.Receiver, spendMsg.Amount) - if err != nil { - return err.Result() - } - return sdk.Result{} - } -} - -// generate a signed transaction -func GenTx(chainID string, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { - // make the transaction free - fee := auth.StdFee{ - sdk.Coins{{"foocoin", sdk.NewInt(0)}}, - 100000, - } - - sigs := make([]auth.StdSignature, len(priv)) - for i, p := range priv { - sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, "")) - // TODO: replace with proper error handling: - if err != nil { - panic(err) - } - sigs[i] = auth.StdSignature{ - PubKey: p.PubKey(), - Signature: sig, - AccountNumber: accnums[i], - Sequence: seq[i], - } - } - return auth.NewStdTx(msgs, fee, sigs, "") -} - -// spin up simple app for testing -type testApp struct { - *BaseApp - accountMapper auth.AccountMapper - accountKeeper bank.Keeper -} - -func newTestApp(name string) testApp { - return testApp{ - BaseApp: newBaseApp(name), - } -} - -func MakeCodec() *wire.Codec { - cdc := wire.NewCodec() - cdc.RegisterInterface((*sdk.Msg)(nil), nil) - crypto.RegisterAmino(cdc) - cdc.RegisterInterface((*auth.Account)(nil), nil) - cdc.RegisterConcrete(&auth.BaseAccount{}, "cosmos-sdk/BaseAccount", nil) - cdc.Seal() - return cdc -} - -// tests multiple msgs of same type from same address in single tx -func TestMultipleBurn(t *testing.T) { - // Create app. - app := newTestApp(t.Name()) - capKey := sdk.NewKVStoreKey("key") - app.MountStoresIAVL(capKey) - app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx auth.StdTx - fromJSON(txBytes, &tx) - return tx, nil - }) - - err := app.LoadLatestVersion(capKey) - if err != nil { - panic(err) - } - - app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) - app.accountKeeper = bank.NewKeeper(app.accountMapper) - - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) - - app.Router(). - AddRoute("burn", newHandleBurn(app.accountKeeper)). - AddRoute("send", newHandleSpend(app.accountKeeper)) - - app.InitChain(abci.RequestInitChain{}) - app.BeginBlock(abci.RequestBeginBlock{}) - - // Set chain-id - app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) - - priv := makePrivKey("my secret") - addr := priv.PubKey().Address() - - app.accountKeeper.AddCoins(app.deliverState.ctx, addr, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr), "Balance did not update") - - msg := testBurnMsg{addr, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} - tx := GenTx(t.Name(), []sdk.Msg{msg, msg}, []int64{0}, []int64{0}, priv) - - res := app.Deliver(tx) - - require.Equal(t, true, res.IsOK(), res.Log) - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr), "Double burn did not work") -} - -// tests multiples msgs of same type from different addresses in single tx -func TestBurnMultipleOwners(t *testing.T) { - // Create app. - app := newTestApp(t.Name()) - capKey := sdk.NewKVStoreKey("key") - app.MountStoresIAVL(capKey) - app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx auth.StdTx - fromJSON(txBytes, &tx) - return tx, nil - }) - - err := app.LoadLatestVersion(capKey) - if err != nil { - panic(err) - } - - app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) - app.accountKeeper = bank.NewKeeper(app.accountMapper) - - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) - - app.Router(). - AddRoute("burn", newHandleBurn(app.accountKeeper)). - AddRoute("send", newHandleSpend(app.accountKeeper)) - - app.InitChain(abci.RequestInitChain{}) - app.BeginBlock(abci.RequestBeginBlock{}) - - // Set chain-id - app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) - - priv1 := makePrivKey("my secret 1") - addr1 := priv1.PubKey().Address() - - priv2 := makePrivKey("my secret 2") - addr2 := priv2.PubKey().Address() - - // fund accounts - app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) - app.accountKeeper.AddCoins(app.deliverState.ctx, addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) - - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not update") - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not update") - - msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}} - msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}}} - - // test wrong signers: Address 1 signs both messages - tx := GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 0}, []int64{0, 0}, priv1, priv1) - - res := app.Deliver(tx) - require.Equal(t, sdk.ABCICodeType(0x10003), res.Code, "Wrong signatures passed") - - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 changed after invalid sig") - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 changed after invalid sig") - - // test valid tx - tx = GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 1}, []int64{1, 0}, priv1, priv2) - - res = app.Deliver(tx) - require.Equal(t, true, res.IsOK(), res.Log) - - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not change after valid tx") - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not change after valid tx") -} - -// tests different msg types in single tx with different addresses -func TestSendBurn(t *testing.T) { - // Create app. - app := newTestApp(t.Name()) - capKey := sdk.NewKVStoreKey("key") - app.MountStoresIAVL(capKey) - app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx auth.StdTx - fromJSON(txBytes, &tx) - return tx, nil - }) - - err := app.LoadLatestVersion(capKey) - if err != nil { - panic(err) - } - - app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) - app.accountKeeper = bank.NewKeeper(app.accountMapper) - - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) - - app.Router(). - AddRoute("burn", newHandleBurn(app.accountKeeper)). - AddRoute("send", newHandleSpend(app.accountKeeper)) - - app.InitChain(abci.RequestInitChain{}) - app.BeginBlock(abci.RequestBeginBlock{}) - - // Set chain-id - app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) - - priv1 := makePrivKey("my secret 1") - addr1 := priv1.PubKey().Address() - - priv2 := makePrivKey("my secret 2") - addr2 := priv2.PubKey().Address() - - // fund accounts - app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) - acc := app.accountMapper.NewAccountWithAddress(app.deliverState.ctx, addr2) - app.accountMapper.SetAccount(app.deliverState.ctx, acc) - - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not update") - - sendMsg := testSendMsg{addr1, addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} - - msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} - msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} - - // send then burn - tx := GenTx(t.Name(), []sdk.Msg{sendMsg, msg2, msg1}, []int64{0, 1}, []int64{0, 0}, priv1, priv2) - - res := app.Deliver(tx) - require.Equal(t, true, res.IsOK(), res.Log) - - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not change after valid tx") - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not change after valid tx") - - // Check that state is only updated if all msgs in tx pass. - app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}}) - - // burn then send - tx = GenTx(t.Name(), []sdk.Msg{msg1, sendMsg}, []int64{0}, []int64{1}, priv1) - - res = app.Deliver(tx) - - // Double check that state is correct after Commit. - app.EndBlock(abci.RequestEndBlock{}) - app.Commit() - - app.BeginBlock(abci.RequestBeginBlock{}) - app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) - - require.Equal(t, sdk.ABCICodeType(0x1000a), res.Code, "Allowed tx to pass with insufficient funds") - - require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(50)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Allowed valid msg to pass in invalid tx") - require.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 changed after invalid tx") -} - -//---------------------------------------- - func randPower() int64 { return cmn.RandInt64() } diff --git a/baseapp/multimsg_test.go b/baseapp/multimsg_test.go new file mode 100644 index 0000000000..581da4b176 --- /dev/null +++ b/baseapp/multimsg_test.go @@ -0,0 +1,352 @@ +package baseapp + +import ( + "encoding/json" + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// tests multiple msgs of same type from same address in single tx +func TestMultipleBurn(t *testing.T) { + // Create app. + app := newTestApp(t.Name()) + capKey := sdk.NewKVStoreKey("key") + app.MountStoresIAVL(capKey) + app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx auth.StdTx + fromJSON(txBytes, &tx) + return tx, nil + }) + + err := app.LoadLatestVersion(capKey) + if err != nil { + panic(err) + } + + app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) + + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) + + app.Router(). + AddRoute("burn", newHandleBurn(app.accountMapper)). + AddRoute("send", newHandleSpend(app.accountMapper)) + + app.InitChain(abci.RequestInitChain{}) + app.BeginBlock(abci.RequestBeginBlock{}) + + // Set chain-id + app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) + + priv := makePrivKey("my secret") + addr := priv.PubKey().Address() + + addCoins(app.accountMapper, app.deliverState.ctx, addr, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) + + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountMapper.GetAccount(app.deliverState.ctx, addr).GetCoins(), "Balance did not update") + + msg := testBurnMsg{addr, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} + tx := GenTx(t.Name(), []sdk.Msg{msg, msg}, []int64{0}, []int64{0}, priv) + + res := app.Deliver(tx) + + require.Equal(t, true, res.IsOK(), res.Log) + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr), "Double burn did not work") +} + +// tests multiples msgs of same type from different addresses in single tx +func TestBurnMultipleOwners(t *testing.T) { + // Create app. + app := newTestApp(t.Name()) + capKey := sdk.NewKVStoreKey("key") + app.MountStoresIAVL(capKey) + app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx auth.StdTx + fromJSON(txBytes, &tx) + return tx, nil + }) + + err := app.LoadLatestVersion(capKey) + if err != nil { + panic(err) + } + + app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) + + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) + + app.Router(). + AddRoute("burn", newHandleBurn(app.accountMapper)). + AddRoute("send", newHandleSpend(app.accountMapper)) + + app.InitChain(abci.RequestInitChain{}) + app.BeginBlock(abci.RequestBeginBlock{}) + + // Set chain-id + app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) + + priv1 := makePrivKey("my secret 1") + addr1 := priv1.PubKey().Address() + + priv2 := makePrivKey("my secret 2") + addr2 := priv2.PubKey().Address() + + // fund accounts + addCoins(app.accountMapper, app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) + addCoins(app.accountMapper, app.deliverState.ctx, addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) + + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Balance1 did not update") + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr2), "Balance2 did not update") + + msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}} + msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}}} + + // test wrong signers: Address 1 signs both messages + tx := GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 0}, []int64{0, 0}, priv1, priv1) + + res := app.Deliver(tx) + require.Equal(t, sdk.ABCICodeType(0x10003), res.Code, "Wrong signatures passed") + + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Balance1 changed after invalid sig") + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr2), "Balance2 changed after invalid sig") + + // test valid tx + tx = GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 1}, []int64{1, 0}, priv1, priv2) + + res = app.Deliver(tx) + require.Equal(t, true, res.IsOK(), res.Log) + + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Balance1 did not change after valid tx") + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr2), "Balance2 did not change after valid tx") +} + +func getCoins(am auth.AccountMapper, ctx sdk.Context, addr sdk.Address) sdk.Coins { + return am.GetAccount(ctx, addr).GetCoins() +} + +func addCoins(am auth.AccountMapper, ctx sdk.Context, addr sdk.Address, coins sdk.Coins) sdk.Error { + acc := am.GetAccount(ctx, addr) + if acc == nil { + acc = am.NewAccountWithAddress(ctx, addr) + } + err := acc.SetCoins(acc.GetCoins().Plus(coins)) + if err != nil { + fmt.Println(err) + return sdk.ErrInternal(err.Error()) + } + am.SetAccount(ctx, acc) + return nil +} + +// tests different msg types in single tx with different addresses +func TestSendBurn(t *testing.T) { + // Create app. + app := newTestApp(t.Name()) + capKey := sdk.NewKVStoreKey("key") + app.MountStoresIAVL(capKey) + app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx auth.StdTx + fromJSON(txBytes, &tx) + return tx, nil + }) + + err := app.LoadLatestVersion(capKey) + if err != nil { + panic(err) + } + + app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{}) + + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{})) + + app.Router(). + AddRoute("burn", newHandleBurn(app.accountMapper)). + AddRoute("send", newHandleSpend(app.accountMapper)) + + app.InitChain(abci.RequestInitChain{}) + app.BeginBlock(abci.RequestBeginBlock{}) + + // Set chain-id + app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) + + priv1 := makePrivKey("my secret 1") + addr1 := priv1.PubKey().Address() + + priv2 := makePrivKey("my secret 2") + addr2 := priv2.PubKey().Address() + + // fund accounts + addCoins(app.accountMapper, app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}) + acc := app.accountMapper.NewAccountWithAddress(app.deliverState.ctx, addr2) + app.accountMapper.SetAccount(app.deliverState.ctx, acc) + + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Balance1 did not update") + + sendMsg := testSendMsg{addr1, addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} + + msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} + msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}} + + // send then burn + tx := GenTx(t.Name(), []sdk.Msg{sendMsg, msg2, msg1}, []int64{0, 1}, []int64{0, 0}, priv1, priv2) + + res := app.Deliver(tx) + require.Equal(t, true, res.IsOK(), res.Log) + + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Balance1 did not change after valid tx") + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr2), "Balance2 did not change after valid tx") + + // Check that state is only updated if all msgs in tx pass. + addCoins(app.accountMapper, app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}}) + + // burn then send, with fee thats greater than individual tx, but less than combination + tx = GenTxWithFeeAmt(50000, t.Name(), []sdk.Msg{msg1, sendMsg}, []int64{0}, []int64{1}, priv1) + + res = app.Deliver(tx) + require.Equal(t, sdk.ABCICodeType(0x1000c), res.Code, "Allowed tx to pass with insufficient funds") + + // Double check that state is correct after Commit. + app.EndBlock(abci.RequestEndBlock{}) + app.Commit() + + app.BeginBlock(abci.RequestBeginBlock{}) + app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name()) + + require.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(50)}}, getCoins(app.accountMapper, app.deliverState.ctx, addr1), "Allowed valid msg to pass in invalid tx") + require.Equal(t, sdk.Coins(nil), getCoins(app.accountMapper, app.deliverState.ctx, addr2), "Balance2 changed after invalid tx") +} + +// Use burn and send msg types to test multiple msgs in one tx +type testBurnMsg struct { + Addr sdk.Address + Amount sdk.Coins +} + +const msgType3 = "burn" + +func (msg testBurnMsg) Type() string { return msgType3 } +func (msg testBurnMsg) GetSignBytes() []byte { + bz, _ := json.Marshal(msg) + return sdk.MustSortJSON(bz) +} +func (msg testBurnMsg) ValidateBasic() sdk.Error { + if msg.Addr == nil { + return sdk.ErrInvalidAddress("Cannot use nil as Address") + } + return nil +} +func (msg testBurnMsg) GetSigners() []sdk.Address { + return []sdk.Address{msg.Addr} +} + +type testSendMsg struct { + Sender sdk.Address + Receiver sdk.Address + Amount sdk.Coins +} + +const msgType4 = "send" + +func (msg testSendMsg) Type() string { return msgType4 } +func (msg testSendMsg) GetSignBytes() []byte { + bz, _ := json.Marshal(msg) + return sdk.MustSortJSON(bz) +} +func (msg testSendMsg) ValidateBasic() sdk.Error { + if msg.Sender == nil || msg.Receiver == nil { + return sdk.ErrInvalidAddress("Cannot use nil as Address") + } + return nil +} +func (msg testSendMsg) GetSigners() []sdk.Address { + return []sdk.Address{msg.Sender} +} + +// Simple Handlers for burn and send + +func newHandleBurn(am auth.AccountMapper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx.GasMeter().ConsumeGas(20000, "burning coins") + burnMsg := msg.(testBurnMsg) + err := addCoins(am, ctx, burnMsg.Addr, burnMsg.Amount.Negative()) + if err != nil { + return err.Result() + } + return sdk.Result{} + } +} + +func newHandleSpend(am auth.AccountMapper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx.GasMeter().ConsumeGas(40000, "spending coins") + spendMsg := msg.(testSendMsg) + err := addCoins(am, ctx, spendMsg.Sender, spendMsg.Amount.Negative()) + if err != nil { + return err.Result() + } + + err = addCoins(am, ctx, spendMsg.Receiver, spendMsg.Amount) + if err != nil { + return err.Result() + } + return sdk.Result{} + } +} + +// generate a signed transaction +func GenTx(chainID string, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { + return GenTxWithFeeAmt(100000, chainID, msgs, accnums, seq, priv...) +} + +// generate a signed transaction with the given fee amount +func GenTxWithFeeAmt(feeAmt int64, chainID string, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { + // make the transaction free + fee := auth.StdFee{ + sdk.Coins{{"foocoin", sdk.NewInt(0)}}, + feeAmt, + } + + sigs := make([]auth.StdSignature, len(priv)) + for i, p := range priv { + sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, "")) + // TODO: replace with proper error handling: + if err != nil { + panic(err) + } + sigs[i] = auth.StdSignature{ + PubKey: p.PubKey(), + Signature: sig, + AccountNumber: accnums[i], + Sequence: seq[i], + } + } + return auth.NewStdTx(msgs, fee, sigs, "") +} + +// spin up simple app for testing +type testApp struct { + *BaseApp + accountMapper auth.AccountMapper +} + +func newTestApp(name string) testApp { + return testApp{ + BaseApp: newBaseApp(name), + } +} + +func MakeCodec() *wire.Codec { + cdc := wire.NewCodec() + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + crypto.RegisterAmino(cdc) + cdc.RegisterInterface((*auth.Account)(nil), nil) + cdc.RegisterConcrete(&auth.BaseAccount{}, "cosmos-sdk/BaseAccount", nil) + cdc.Seal() + return cdc +} diff --git a/client/context/helpers.go b/client/context/helpers.go index 89c1713533..e0fa45580b 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -224,25 +224,58 @@ func (ctx CoreContext) ensureSignBuild(name string, msgs []sdk.Msg, cdc *wire.Co } // sign and build the transaction from the msg -func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (res *ctypes.ResultBroadcastTxCommit, err error) { +func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (err error) { txBytes, err := ctx.ensureSignBuild(name, msgs, cdc) if err != nil { - return nil, err + return err } - return ctx.BroadcastTx(txBytes) -} - -// sign and build the async transaction from the msg -func (ctx CoreContext) EnsureSignBuildBroadcastAsync(name string, msgs []sdk.Msg, cdc *wire.Codec) (res *ctypes.ResultBroadcastTx, err error) { - - txBytes, err := ctx.ensureSignBuild(name, msgs, cdc) + if ctx.Async { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return err + } + if ctx.JSON { + type toJSON struct { + TxHash string + } + valueToJSON := toJSON{res.Hash.String()} + JSON, err := cdc.MarshalJSON(valueToJSON) + if err != nil { + return err + } + fmt.Println(string(JSON)) + } else { + fmt.Println("Async tx sent. tx hash: ", res.Hash.String()) + } + return nil + } + res, err := ctx.BroadcastTx(txBytes) if err != nil { - return nil, err + return err } - - return ctx.BroadcastTxAsync(txBytes) + if ctx.JSON { + // Since JSON is intended for automated scripts, always include response in JSON mode + type toJSON struct { + Height int64 + TxHash string + Response string + } + valueToJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)} + JSON, err := cdc.MarshalJSON(valueToJSON) + if err != nil { + return err + } + fmt.Println(string(JSON)) + return nil + } + if ctx.PrintResponse { + fmt.Printf("Committed at block %d. Hash: %s Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx) + } else { + fmt.Printf("Committed at block %d. Hash: %s \n", res.Height, res.Hash.String()) + } + return nil } // get the next sequence for the account address diff --git a/client/context/types.go b/client/context/types.go index 58df9b5bf2..03dd6b9d03 100644 --- a/client/context/types.go +++ b/client/context/types.go @@ -22,6 +22,9 @@ type CoreContext struct { Decoder auth.AccountDecoder AccountStore string UseLedger bool + Async bool + JSON bool + PrintResponse bool } // WithChainID - return a copy of the context with an updated chainID diff --git a/client/context/viper.go b/client/context/viper.go index c0b48fc85d..611ad1b92f 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -27,13 +27,20 @@ func NewCoreContextFromViper() CoreContext { chainID = def } } + // TODO: Remove the following deprecation code after Gaia-7000 is launched + keyName := viper.GetString(client.FlagName) + if keyName != "" { + fmt.Println("** Note --name is deprecated and will be removed next release. Please use --from instead **") + } else { + keyName = viper.GetString(client.FlagFrom) + } return CoreContext{ ChainID: chainID, Height: viper.GetInt64(client.FlagHeight), Gas: viper.GetInt64(client.FlagGas), Fee: viper.GetString(client.FlagFee), TrustNode: viper.GetBool(client.FlagTrustNode), - FromAddressName: viper.GetString(client.FlagName), + FromAddressName: keyName, NodeURI: nodeURI, AccountNumber: viper.GetInt64(client.FlagAccountNumber), Sequence: viper.GetInt64(client.FlagSequence), @@ -42,6 +49,9 @@ func NewCoreContextFromViper() CoreContext { Decoder: nil, AccountStore: "acc", UseLedger: viper.GetBool(client.FlagUseLedger), + Async: viper.GetBool(client.FlagAsync), + JSON: viper.GetBool(client.FlagJson), + PrintResponse: viper.GetBool(client.FlagPrintResponse), } } diff --git a/client/flags.go b/client/flags.go index 31e9c087bf..b96012da7d 100644 --- a/client/flags.go +++ b/client/flags.go @@ -10,11 +10,15 @@ const ( FlagHeight = "height" FlagGas = "gas" FlagTrustNode = "trust-node" + FlagFrom = "from" FlagName = "name" FlagAccountNumber = "account-number" FlagSequence = "sequence" FlagMemo = "memo" FlagFee = "fee" + FlagAsync = "async" + FlagJson = "json" + FlagPrintResponse = "print-response" ) // LineBreak can be included in a command list to provide a blank line @@ -37,7 +41,8 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { // PostCommands adds common flags for commands to post tx func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - c.Flags().String(FlagName, "", "Name of private key with which to sign") + c.Flags().String(FlagFrom, "", "Name of private key with which to sign") + c.Flags().String(FlagName, "", "DEPRECATED - Name of private key with which to sign") c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagMemo, "", "Memo to send along with transaction") @@ -46,6 +51,9 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction") + c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") + c.Flags().Bool(FlagJson, false, "return output in json format") + c.Flags().Bool(FlagPrintResponse, false, "return tx response (only works with async = false)") } return cmds } diff --git a/client/keys/delete.go b/client/keys/delete.go index f38c529a6f..944feb4b19 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -25,14 +25,19 @@ func deleteKeyCommand() *cobra.Command { func runDeleteCmd(cmd *cobra.Command, args []string) error { name := args[0] - buf := client.BufferStdin() - oldpass, err := client.GetPassword( - "DANGER - enter password to permanently delete key:", buf) + kb, err := GetKeyBase() if err != nil { return err } - kb, err := GetKeyBase() + _, err = kb.Get(name) + if err != nil { + return err + } + + buf := client.BufferStdin() + oldpass, err := client.GetPassword( + "DANGER - enter password to permanently delete key:", buf) if err != nil { return err } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 07230a4f90..18e967bd3f 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -3,6 +3,7 @@ package clitest import ( "encoding/json" "fmt" + "os" "testing" "github.com/stretchr/testify/require" @@ -21,37 +22,44 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -func TestGaiaCLISend(t *testing.T) { +var ( + pass = "1234567890" + gaiadHome = "" + gaiacliHome = "" +) - tests.ExecuteT(t, "gaiad unsafe_reset_all") - pass := "1234567890" - executeWrite(t, "gaiacli keys delete foo", pass) - executeWrite(t, "gaiacli keys delete bar", pass) - chainID := executeInit(t, "gaiad init -o --name=foo") - executeWrite(t, "gaiacli keys add bar", pass) +func init() { + gaiadHome, gaiacliHome = getTestingHomeDirs() +} + +func TestGaiaCLISend(t *testing.T) { + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), pass) // get a free port, also setup some common flags servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) - flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + defer proc.Stop(false) tests.WaitForTMStart(port) tests.WaitForNextHeightTM(port) - fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - fooCech, err := sdk.Bech32ifyAcc(fooAddr) - require.NoError(t, err) - barAddr, _ := executeGetAddrPK(t, "gaiacli keys show bar --output=json") - barCech, err := sdk.Bech32ifyAcc(barAddr) - require.NoError(t, err) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + fooCech := sdk.MustBech32ifyAcc(fooAddr) + barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) + barCech := sdk.MustBech32ifyAcc(barAddr) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --from=foo", flags, barCech), pass) tests.WaitForNextHeightTM(port) barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) @@ -60,7 +68,7 @@ func TestGaiaCLISend(t *testing.T) { require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --from=foo", flags, barCech), pass) tests.WaitForNextHeightTM(port) barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) @@ -69,7 +77,7 @@ func TestGaiaCLISend(t *testing.T) { require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak").Int64()) // test memo - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo --memo 'testmemo'", flags, barCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --from=foo --memo 'testmemo'", flags, barCech), pass) tests.WaitForNextHeightTM(port) barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) @@ -79,35 +87,31 @@ func TestGaiaCLISend(t *testing.T) { } func TestGaiaCLICreateValidator(t *testing.T) { - - tests.ExecuteT(t, "gaiad unsafe_reset_all") - pass := "1234567890" - executeWrite(t, "gaiacli keys delete foo", pass) - executeWrite(t, "gaiacli keys delete bar", pass) - chainID := executeInit(t, "gaiad init -o --name=foo") - executeWrite(t, "gaiacli keys add bar", pass) + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), pass) // get a free port, also setup some common flags servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) - flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + defer proc.Stop(false) tests.WaitForTMStart(port) tests.WaitForNextHeightTM(port) - fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - fooCech, err := sdk.Bech32ifyAcc(fooAddr) - require.NoError(t, err) - barAddr, barPubKey := executeGetAddrPK(t, "gaiacli keys show bar --output=json") - barCech, err := sdk.Bech32ifyAcc(barAddr) - require.NoError(t, err) - barCeshPubKey, err := sdk.Bech32ifyValPub(barPubKey) - require.NoError(t, err) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + fooCech := sdk.MustBech32ifyAcc(fooAddr) + barAddr, barPubKey := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) + barCech := sdk.MustBech32ifyAcc(barAddr) + barCeshPubKey := sdk.MustBech32ifyValPub(barPubKey) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --from=foo", flags, barCech), pass) tests.WaitForNextHeightTM(port) barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) @@ -117,7 +121,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { // create validator cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags) - cvStr += fmt.Sprintf(" --name=%v", "bar") + cvStr += fmt.Sprintf(" --from=%v", "bar") cvStr += fmt.Sprintf(" --address-validator=%v", barCech) cvStr += fmt.Sprintf(" --pubkey=%v", barCeshPubKey) cvStr += fmt.Sprintf(" --amount=%v", "2steak") @@ -135,7 +139,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { // unbond a single share unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags) - unbondStr += fmt.Sprintf(" --name=%v", "bar") + unbondStr += fmt.Sprintf(" --from=%v", "bar") unbondStr += fmt.Sprintf(" --address-validator=%v", barCech) unbondStr += fmt.Sprintf(" --address-delegator=%v", barCech) unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") @@ -153,33 +157,31 @@ func TestGaiaCLICreateValidator(t *testing.T) { } func TestGaiaCLISubmitProposal(t *testing.T) { - - tests.ExecuteT(t, "gaiad unsafe_reset_all") - pass := "1234567890" - executeWrite(t, "gaiacli keys delete foo", pass) - executeWrite(t, "gaiacli keys delete bar", pass) - chainID := executeInit(t, "gaiad init -o --name=foo") - executeWrite(t, "gaiacli keys add bar", pass) + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe_reset_all", gaiadHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), pass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), pass) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), pass) // get a free port, also setup some common flags servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) - flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + defer proc.Stop(false) tests.WaitForTMStart(port) tests.WaitForNextHeightTM(port) - fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - fooCech, err := sdk.Bech32ifyAcc(fooAddr) - require.NoError(t, err) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) + fooCech := sdk.MustBech32ifyAcc(fooAddr) fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - executeWrite(t, fmt.Sprintf("gaiacli gov submitproposal %v --proposer=%v --deposit=5steak --type=Text --title=Test --description=test --name=foo", flags, fooCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli gov submit-proposal %v --proposer=%v --deposit=5steak --type=Text --title=Test --description=test --from=foo", flags, fooCech), pass) tests.WaitForNextHeightTM(port) fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) @@ -189,7 +191,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(1), proposal1.ProposalID) require.Equal(t, gov.StatusToString(gov.StatusDepositPeriod), proposal1.Status) - executeWrite(t, fmt.Sprintf("gaiacli gov deposit %v --depositer=%v --deposit=10steak --proposalID=1 --name=foo", flags, fooCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli gov deposit %v --depositer=%v --deposit=10steak --proposalID=1 --from=foo", flags, fooCech), pass) tests.WaitForNextHeightTM(port) fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) @@ -198,7 +200,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(1), proposal1.ProposalID) require.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal1.Status) - executeWrite(t, fmt.Sprintf("gaiacli gov vote %v --proposalID=1 --voter=%v --option=Yes --name=foo", flags, fooCech), pass) + executeWrite(t, fmt.Sprintf("gaiacli gov vote %v --proposalID=1 --voter=%v --option=Yes --from=foo", flags, fooCech), pass) tests.WaitForNextHeightTM(port) vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposalID=1 --voter=%v --output=json %v", fooCech, flags)) @@ -206,6 +208,16 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option) } +//___________________________________________________________________________________ +// helper methods + +func getTestingHomeDirs() (string, string) { + tmpDir := os.TempDir() + gaiadHome := fmt.Sprintf("%s%s.test_gaiad", tmpDir, string(os.PathSeparator)) + gaiacliHome := fmt.Sprintf("%s%s.test_gaiacli", tmpDir, string(os.PathSeparator)) + return gaiadHome, gaiacliHome +} + //___________________________________________________________________________________ // executors diff --git a/cmd/gaia/testnets/README.md b/cmd/gaia/testnets/README.md index 57bbfc48ac..e8d1f83e4d 100644 --- a/cmd/gaia/testnets/README.md +++ b/cmd/gaia/testnets/README.md @@ -220,7 +220,7 @@ gaiacli stake create-validator \ --address-validator= --moniker="choose a moniker" \ --chain-id=gaia-6002 \ - --name= + --from= ``` ### Edit Validator Description @@ -237,7 +237,7 @@ gaiacli stake edit-validator --keybase-sig="6A0D65E29A4CBC8E" --details="To infinity and beyond!" --chain-id=gaia-6002 \ - --name= + --from= ``` ### View Validator Description @@ -272,7 +272,7 @@ gaiad start Wait for your full node to catch up to the latest block. Next, run the following command. Note that `` is the address of your validator account, and `` is the name of the validator account. You can find this info by running `gaiacli keys list`. ```bash -gaiacli stake unrevoke --chain-id=gaia-6002 --name= +gaiacli stake unrevoke --chain-id=gaia-6002 --from= ``` **WARNING:** If you don't wait for `gaiad` to sync before running `unrevoke`, you will receive an error message telling you your validator is still jailed. @@ -320,8 +320,8 @@ On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond t gaiacli stake delegate \ --amount=10steak \ --address-delegator= \ - --address-validator=$(gaiad tendermint show_validator) \ - --name= \ + --address-validator= \ + --from= \ --chain-id=gaia-6002 ``` @@ -336,9 +336,9 @@ If for any reason the validator misbehaves, or you want to unbond a certain amou ```bash gaiacli stake unbond \ --address-delegator= \ - --address-validator=$(gaiad tendermint show_validator) \ + --address-validator= \ --shares=MAX \ - --name= \ + --from= \ --chain-id=gaia-6002 ``` @@ -349,7 +349,7 @@ gaiacli account gaiacli stake delegation \ --address-delegator= \ - --address-validator=$(gaiad tendermint show_validator) \ + --address-validator= \ --chain-id=gaia-6002 ``` @@ -361,7 +361,7 @@ gaiacli stake delegation \ gaiacli send \ --amount=10faucetToken \ --chain-id=gaia-6002 \ - --name= \ + --from= \ --to= ``` diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 82adc53dda..7cf43eea67 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -188,6 +188,9 @@ func (kb dbKeybase) List() ([]Info, error) { // Get returns the public information about one key. func (kb dbKeybase) Get(name string) (Info, error) { bs := kb.db.Get(infoKey(name)) + if len(bs) == 0 { + return nil, fmt.Errorf("Key %s not found", name) + } return readInfo(bs) } diff --git a/docs/_attic/staking/intro.rst b/docs/_attic/staking/intro.rst index 3ed20852b4..14735ef33b 100644 --- a/docs/_attic/staking/intro.rst +++ b/docs/_attic/staking/intro.rst @@ -197,9 +197,9 @@ We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the net :: - gaiacli send --amount=1000mycoin --sequence=0 --name=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU + gaiacli send --amount=1000mycoin --sequence=0 --from=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU -where the ``--sequence`` flag is to be incremented for each transaction, the ``--name`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like: +where the ``--sequence`` flag is to be incremented for each transaction, the ``--from`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like: :: @@ -264,7 +264,7 @@ Now ``bob`` can create a validator with that pubkey. :: - gaiacli stake create-validator --amount=10mycoin --name=bob --address-validator=
--pub-key= --moniker=bobby + gaiacli stake create-validator --amount=10mycoin --from=bob --address-validator=
--pub-key= --moniker=bobby with an output like: @@ -306,13 +306,13 @@ First let's have ``alice`` send some coins to ``charlie``: :: - gaiacli send --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF + gaiacli send --amount=1000mycoin --sequence=2 --from=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF Then ``charlie`` will delegate some mycoin to ``bob``: :: - gaiacli stake delegate --amount=10mycoin --address-delegator= --address-validator= --name=charlie + gaiacli stake delegate --amount=10mycoin --address-delegator= --address-validator= --from=charlie You'll see output like: @@ -396,7 +396,7 @@ your VotingPower reduce and your account balance increase. :: - gaiacli stake unbond --amount=5mycoin --name=charlie --address-delegator=
--address-validator=
+ gaiacli stake unbond --amount=5mycoin --from=charlie --address-delegator=
--address-validator=
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF See the bond decrease with ``gaiacli stake delegation`` like above. diff --git a/docs/_attic/staking/testnet.md b/docs/_attic/staking/testnet.md index b2bbd8f1a3..5228070cf1 100644 --- a/docs/_attic/staking/testnet.md +++ b/docs/_attic/staking/testnet.md @@ -63,7 +63,7 @@ Then, we try to transfer some `steak` to another account: ``` gaiacli account gaiacli account -gaiacli send --amount=10steak --to= --name=foo --chain-id=test-chain +gaiacli send --amount=10steak --to= --from=foo --chain-id=test-chain ``` **Note:** We need to be careful with the `chain-id` and `sequence` @@ -84,7 +84,7 @@ Finally, to relinquish all your power, unbond some coins. You should see your VotingPower reduce and your account balance increase. ``` -gaiacli unbond --chain-id= --name=test +gaiacli unbond --chain-id= --from=test ``` That's it! diff --git a/docs/clients/ledger.md b/docs/clients/ledger.md index 9f6386715d..5ea1224eae 100644 --- a/docs/clients/ledger.md +++ b/docs/clients/ledger.md @@ -23,7 +23,7 @@ NAME: TYPE: ADDRESS: PUBKEY: This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: ```bash -$ gaiacli send --name {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 ``` You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go index f9eceecdb5..a9580fcc29 100644 --- a/docs/core/examples/app1.go +++ b/docs/core/examples/app1.go @@ -85,7 +85,7 @@ func (msg MsgSend) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // Implements Msg. Return the signer. diff --git a/docs/core/examples/app2.go b/docs/core/examples/app2.go index b4d44dca7b..c96a1022fa 100644 --- a/docs/core/examples/app2.go +++ b/docs/core/examples/app2.go @@ -100,7 +100,7 @@ func (msg MsgIssue) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // Implements Msg. Return the signer. diff --git a/examples/README.md b/examples/README.md index a8a483ee62..e3016fb707 100644 --- a/examples/README.md +++ b/examples/README.md @@ -123,12 +123,12 @@ Where `90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD` is alice's address we got from The following command will send coins from alice, to bob: ``` -basecli send --name=alice --amount=10000mycoin --to=29D721F054537C91F618A0FDBF770DA51EF8C48D +basecli send --from=alice --amount=10000mycoin --to=29D721F054537C91F618A0FDBF770DA51EF8C48D --sequence=0 --chain-id=test-chain-AE4XQo ``` Flag Descriptions: -- `name` is the name you gave your key +- `from` is the name you gave your key - `mycoin` is the name of the token for this basecoin demo, initialized in the genesis.json file - `sequence` is a tally of how many transactions have been made by this account. Since this is the first tx on this account, it is 0 - `chain-id` is the unique ID that helps tendermint identify which network to connect to. You can find it in the terminal output from the gaiad daemon in the header block , or in the genesis.json file at `~/.basecoind/config/genesis.json` @@ -142,16 +142,16 @@ basecli account 29D721F054537C91F618A0FDBF770DA51EF8C48D Now lets send some from bob to charlie. Make sure you send less than bob has, otherwise the transaction will fail: ``` -basecli send --name=bob --amount=5000mycoin --to=2E8E13EEB8E3F0411ACCBC9BE0384732C24FBD5E +basecli send --from=bob --amount=5000mycoin --to=2E8E13EEB8E3F0411ACCBC9BE0384732C24FBD5E --sequence=0 --chain-id=test-chain-AE4XQo ``` -Note how we use the ``--name`` flag to select a different account to send from. +Note how we use the ``--from`` flag to select a different account to send from. Lets now try to send from bob back to alice: ``` -basecli send --name=bob --amount=3000mycoin --to=90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD +basecli send --from=bob --amount=3000mycoin --to=90B0B9BE0914ECEE0B6DB74E67B07A00056B9BBD --sequence=1 --chain-id=test-chain-AE4XQo ``` diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 0bb84c146a..e74b3b700b 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -29,20 +29,24 @@ func CoolAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso if err != nil { return } + key := "cool" value := json.RawMessage(`{ "trend": "ice-cold" }`) - appState, err = server.AppendJSON(cdc, appState, key, value) + + appState, err = server.InsertKeyJSON(cdc, appState, key, value) if err != nil { return } + key = "pow" value = json.RawMessage(`{ "difficulty": 1, "count": 0 }`) - appState, err = server.AppendJSON(cdc, appState, key, value) + + appState, err = server.InsertKeyJSON(cdc, appState, key, value) return } diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index cb65830c93..3e034600bd 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/spf13/cobra" "github.com/spf13/viper" @@ -37,12 +35,11 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { name := viper.GetString(client.FlagName) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -70,12 +67,11 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { msg := cool.NewMsgSetTrend(from, args[0]) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/examples/democoin/x/cool/types.go b/examples/democoin/x/cool/types.go index 77f49a0ba3..82c9286c68 100644 --- a/examples/democoin/x/cool/types.go +++ b/examples/democoin/x/cool/types.go @@ -58,7 +58,7 @@ func (msg MsgSetTrend) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } //_______________________________________________________________________ @@ -102,5 +102,5 @@ func (msg MsgQuiz) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/examples/democoin/x/oracle/types.go b/examples/democoin/x/oracle/types.go index 041b9ab349..d06f47a4e2 100644 --- a/examples/democoin/x/oracle/types.go +++ b/examples/democoin/x/oracle/types.go @@ -18,7 +18,7 @@ func (msg Msg) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // GetSigners implements sdk.Msg diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index 49527c8c08..bc958ffaeb 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -1,7 +1,6 @@ package cli import ( - "fmt" "strconv" "github.com/spf13/cobra" @@ -49,12 +48,11 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { name := ctx.FromAddressName // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(name, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/examples/democoin/x/pow/types.go b/examples/democoin/x/pow/types.go index cd77a68af5..3529917dce 100644 --- a/examples/democoin/x/pow/types.go +++ b/examples/democoin/x/pow/types.go @@ -76,5 +76,5 @@ func (msg MsgMine) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/examples/democoin/x/pow/types_test.go b/examples/democoin/x/pow/types_test.go index 9ed5dc102c..1291cb96a6 100644 --- a/examples/democoin/x/pow/types_test.go +++ b/examples/democoin/x/pow/types_test.go @@ -62,7 +62,7 @@ func TestMsgMineGetSignBytes(t *testing.T) { addr := sdk.Address([]byte("sender")) msg := MsgMine{addr, 1, 1, 1, []byte("abc")} res := msg.GetSignBytes() - require.Equal(t, string(res), `{"sender":"73656E646572","difficulty":1,"count":1,"nonce":1,"proof":"YWJj"}`) + require.Equal(t, string(res), `{"count":1,"difficulty":1,"nonce":1,"proof":"YWJj","sender":"73656E646572"}`) } func TestMsgMineGetSigners(t *testing.T) { diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/examples/democoin/x/simplestake/client/cli/commands.go index fcd0c84b45..20b5d95221 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -87,11 +87,10 @@ func UnbondTxCmd(cdc *wire.Codec) *cobra.Command { func sendMsg(cdc *wire.Codec, msg sdk.Msg) error { ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil } diff --git a/examples/democoin/x/simplestake/msgs.go b/examples/democoin/x/simplestake/msgs.go index 564e0d9796..91729e3ef7 100644 --- a/examples/democoin/x/simplestake/msgs.go +++ b/examples/democoin/x/simplestake/msgs.go @@ -48,7 +48,7 @@ func (msg MsgBond) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } //_______________________________________________________________ diff --git a/examples/kvstore/tx.go b/examples/kvstore/tx.go index 0e94ace285..e1c5cab3a8 100644 --- a/examples/kvstore/tx.go +++ b/examples/kvstore/tx.go @@ -27,7 +27,7 @@ func (tx kvstoreTx) GetMemo() string { } func (tx kvstoreTx) GetSignBytes() []byte { - return tx.bytes + return sdk.MustSortJSON(tx.bytes) } // Should the app be calling this? Or only handlers? diff --git a/server/init.go b/server/init.go index 0cc99e79fa..3c9186fba3 100644 --- a/server/init.go +++ b/server/init.go @@ -332,32 +332,20 @@ func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey { return privValidator.GetPubKey() } -// create the genesis file +// writeGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. func writeGenesisFile(cdc *wire.Codec, genesisFile, chainID string, validators []tmtypes.GenesisValidator, appState json.RawMessage) error { genDoc := tmtypes.GenesisDoc{ - ChainID: chainID, - Validators: validators, + ChainID: chainID, + Validators: validators, + AppStateJSON: appState, } + if err := genDoc.ValidateAndComplete(); err != nil { return err } - if err := genDoc.SaveAs(genesisFile); err != nil { - return err - } - return addAppStateToGenesis(cdc, genesisFile, appState) -} -// Add one line to the genesis file -func addAppStateToGenesis(cdc *wire.Codec, genesisConfigPath string, appState json.RawMessage) error { - bz, err := ioutil.ReadFile(genesisConfigPath) - if err != nil { - return err - } - out, err := AppendJSON(cdc, bz, "app_state", appState) - if err != nil { - return err - } - return ioutil.WriteFile(genesisConfigPath, out, 0600) + return genDoc.SaveAs(genesisFile) } //_____________________________________________________________________ diff --git a/server/util.go b/server/util.go index d1ad001c52..0f1816b166 100644 --- a/server/util.go +++ b/server/util.go @@ -123,15 +123,21 @@ func AddCommands( //___________________________________________________________________________________ -// append a new json field to existing json message -func AppendJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawMessage) (appended []byte, err error) { +// InsertKeyJSON inserts a new JSON field/key with a given value to an existing +// JSON message. An error is returned if any serialization operation fails. +// +// NOTE: The ordering of the keys returned as the resulting JSON message is +// non-deterministic, so the client should not rely on key ordering. +func InsertKeyJSON(cdc *wire.Codec, baseJSON []byte, key string, value json.RawMessage) ([]byte, error) { var jsonMap map[string]json.RawMessage - err = cdc.UnmarshalJSON(baseJSON, &jsonMap) - if err != nil { + + if err := cdc.UnmarshalJSON(baseJSON, &jsonMap); err != nil { return nil, err } + jsonMap[key] = value bz, err := wire.MarshalJSONIndent(cdc, jsonMap) + return json.RawMessage(bz), err } diff --git a/server/util_test.go b/server/util_test.go index 8f1ab21dbc..6082caa2ce 100644 --- a/server/util_test.go +++ b/server/util_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestAppendJSON(t *testing.T) { +func TestInsertKeyJSON(t *testing.T) { cdc := wire.NewCodec() foo := map[string]string{"foo": "foofoo"} @@ -24,7 +24,7 @@ func TestAppendJSON(t *testing.T) { barRaw := json.RawMessage(bz) // make the append - appBz, err := AppendJSON(cdc, fooRaw, "barOuter", barRaw) + appBz, err := InsertKeyJSON(cdc, fooRaw, "barOuter", barRaw) require.NoError(t, err) // test the append diff --git a/tests/check_basecli.sh b/tests/check_basecli.sh index ec2c458cb6..ffcf39a517 100755 --- a/tests/check_basecli.sh +++ b/tests/check_basecli.sh @@ -36,7 +36,7 @@ echo; echo "Empty account:" $TO ./build/basecli account $TO # send some money -TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --name=demo --seq=0` +TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --from=demo --seq=0` echo; echo "SendTx"; echo $TX HASH=`echo $TX | cut -d' ' -f6` echo "tx hash:" $HASH diff --git a/types/tx_msg.go b/types/tx_msg.go index c1af91df82..fbadfb49d0 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -60,7 +60,7 @@ func (msg *TestMsg) GetSignBytes() []byte { if err != nil { panic(err) } - return bz + return MustSortJSON(bz) } func (msg *TestMsg) ValidateBasic() Error { return nil } func (msg *TestMsg) GetSigners() []Address { diff --git a/types/utils.go b/types/utils.go new file mode 100644 index 0000000000..2e027676a0 --- /dev/null +++ b/types/utils.go @@ -0,0 +1,31 @@ +package types + +import "encoding/json" + +// SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces +// are removed. +// This method can be used to canonicalize JSON to be returned by GetSignBytes, +// e.g. for the ledger integration. +// If the passed JSON isn't valid it will return an error. +func SortJSON(toSortJSON []byte) ([]byte, error) { + var c interface{} + err := json.Unmarshal(toSortJSON, &c) + if err != nil { + return nil, err + } + js, err := json.Marshal(c) + if err != nil { + return nil, err + } + return js, nil +} + +// MustSortJSON is like SortJSON but panic if an error occurs, e.g., if +// the passed JSON isn't valid. +func MustSortJSON(toSortJSON []byte) []byte { + js, err := SortJSON(toSortJSON) + if err != nil { + panic(err) + } + return js +} diff --git a/types/utils_test.go b/types/utils_test.go new file mode 100644 index 0000000000..8c84e2ace2 --- /dev/null +++ b/types/utils_test.go @@ -0,0 +1,39 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSortJSON(t *testing.T) { + cases := []struct { + unsortedJSON string + want string + wantErr bool + }{ + // simple case + {unsortedJSON: `{"cosmos":"foo", "atom":"bar", "tendermint":"foobar"}`, + want: `{"atom":"bar","cosmos":"foo","tendermint":"foobar"}`, wantErr: false}, + // failing case (invalid JSON): + {unsortedJSON: `"cosmos":"foo",,,, "atom":"bar", "tendermint":"foobar"}`, + want: "", + wantErr: true}, + // genesis.json + {unsortedJSON: `{"consensus_params":{"block_size_params":{"max_bytes":22020096,"max_txs":100000,"max_gas":-1},"tx_size_params":{"max_bytes":10240,"max_gas":-1},"block_gossip_params":{"block_part_size_bytes":65536},"evidence_params":{"max_age":100000}},"validators":[{"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="},"power":100,"name":""}],"app_hash":"","genesis_time":"2018-05-11T15:52:25.424795506Z","chain_id":"test-chain-Q6VeoW","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"denom":"Token","amount":1000},{"denom":"steak","amount":50}]}],"stake":{"pool":{"total_supply":50,"bonded_shares":"0","unbonded_shares":"0","bonded_pool":0,"unbonded_pool":0,"inflation_last_time":0,"inflation":"7/100"},"params":{"inflation_rate_change":"13/100","inflation_max":"1/5","inflation_min":"7/100","goal_bonded":"67/100","max_validators":100,"bond_denom":"steak"},"candidates":null,"bonds":null}}}`, + want: `{"app_hash":"","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"amount":1000,"denom":"Token"},{"amount":50,"denom":"steak"}]}],"stake":{"bonds":null,"candidates":null,"params":{"bond_denom":"steak","goal_bonded":"67/100","inflation_max":"1/5","inflation_min":"7/100","inflation_rate_change":"13/100","max_validators":100},"pool":{"bonded_pool":0,"bonded_shares":"0","inflation":"7/100","inflation_last_time":0,"total_supply":50,"unbonded_pool":0,"unbonded_shares":"0"}}},"chain_id":"test-chain-Q6VeoW","consensus_params":{"block_gossip_params":{"block_part_size_bytes":65536},"block_size_params":{"max_bytes":22020096,"max_gas":-1,"max_txs":100000},"evidence_params":{"max_age":100000},"tx_size_params":{"max_bytes":10240,"max_gas":-1}},"genesis_time":"2018-05-11T15:52:25.424795506Z","validators":[{"name":"","power":100,"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="}}]}`, + wantErr: false}, + // from the TXSpec: + {unsortedJSON: `{"chain_id":"test-chain-1","sequence":1,"fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"alt_bytes":null}`, + want: `{"alt_bytes":null,"chain_id":"test-chain-1","fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"sequence":1}`, + wantErr: false}, + } + + for _, tc := range cases { + got, err := SortJSON([]byte(tc.unsortedJSON)) + if tc.wantErr != (err != nil) { + t.Fatalf("got %t, want: %t, err=%s", err != nil, tc.wantErr, err) + } + require.Equal(t, string(got), tc.want) + } +} diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index ccc1e277be..47980a64ca 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "github.com/spf13/cobra" @@ -54,7 +55,7 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode // Check if account was found if res == nil { - return sdk.ErrUnknownAddress("No account with address " + addr + + return errors.New("No account with address " + addr + " was found in the state.\nAre you sure there has been a transaction involving it?") } diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index fd55eebb95..27d075535e 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -110,38 +110,32 @@ func (fee StdFee) Bytes() []byte { // and the Sequence numbers for each signature (prevent // inchain replay and enforce tx ordering per account). type StdSignDoc struct { - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - FeeBytes json.RawMessage `json:"fee_bytes"` - MsgsBytes json.RawMessage `json:"msg_bytes"` - Memo string `json:"memo"` + AccountNumber int64 `json:"account_number"` + ChainID string `json:"chain_id"` + Fee json.RawMessage `json:"fee"` + Memo string `json:"memo"` + Msgs []json.RawMessage `json:"msgs"` + Sequence int64 `json:"sequence"` } // StdSignBytes returns the bytes to sign for a transaction. -// TODO: change the API to just take a chainID and StdTx ? func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs []sdk.Msg, memo string) []byte { var msgsBytes []json.RawMessage for _, msg := range msgs { msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes())) } - msgBytes, err := msgCdc.MarshalJSON(msgsBytes) - if err != nil { - panic(err) - } - bz, err := msgCdc.MarshalJSON(StdSignDoc{ - ChainID: chainID, AccountNumber: accnum, - Sequence: sequence, - FeeBytes: json.RawMessage(fee.Bytes()), - MsgsBytes: json.RawMessage(msgBytes), + ChainID: chainID, + Fee: json.RawMessage(fee.Bytes()), Memo: memo, + Msgs: msgsBytes, + Sequence: sequence, }) if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // StdSignMsg is a convenience structure for passing along diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index 533596431a..6e6816f6b9 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -1,6 +1,7 @@ package auth import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -10,12 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// func newStdFee() StdFee { -// return NewStdFee(100, -// Coin{"atom", 150}, -// ) -// } - func TestStdTx(t *testing.T) { priv := crypto.GenPrivKeyEd25519() addr := priv.PubKey().Address() @@ -30,3 +25,19 @@ func TestStdTx(t *testing.T) { feePayer := FeePayer(tx) require.Equal(t, addr, feePayer) } + +func TestStdSignBytes(t *testing.T) { + priv := crypto.GenPrivKeyEd25519() + addr := priv.PubKey().Address() + msgs := []sdk.Msg{sdk.NewTestMsg(addr)} + fee := newStdFee() + signMsg := StdSignMsg{ + "1234", + 3, + 6, + fee, + msgs, + "memo", + } + require.Equal(t, fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), string(signMsg.Bytes())) +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 425e9aa2d9..a89806d123 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -5,6 +5,8 @@ import ( "github.com/stretchr/testify/require" + "math/rand" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/mock" @@ -81,17 +83,19 @@ func getMockApp(t *testing.T) *mock.App { return mapp } -// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking -// Any long term API support commitments do not apply to this function. -func getBenchmarkMockApp() (*mock.App, error) { - mapp := mock.NewApp() +func TestBankWithRandomMessages(t *testing.T) { + mapp := getMockApp(t) + setup := func(r *rand.Rand, keys []crypto.PrivKey) { + return + } - RegisterWire(mapp.Cdc) - coinKeeper := NewKeeper(mapp.AccountMapper) - mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) - - err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) - return mapp, err + mapp.RandomizedTesting( + t, + []mock.TestAndRunTx{TestAndRunSingleInputMsgSend}, + []mock.RandSetup{setup}, + []mock.Invariant{ModuleInvariants}, + 100, 30, 30, + ) } func TestMsgSendWithAccounts(t *testing.T) { diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index be8319f9fd..b90d569551 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -10,6 +10,19 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking +// Any long term API support commitments do not apply to this function. +func getBenchmarkMockApp() (*mock.App, error) { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + coinKeeper := NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) + + err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + return mapp, err +} + func BenchmarkOneBankSendTxPerBlock(b *testing.B) { benchmarkApp, _ := getBenchmarkMockApp() diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index d3f7377fab..8731de40e9 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -1,25 +1,24 @@ package cli import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" + "errors" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/bank/client" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( flagTo = "to" flagAmount = "amount" - flagAsync = "async" ) -// SendTxCommand will create a send tx and sign it with the given key +// SendTxCmd will create a send tx and sign it with the given key func SendTxCmd(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "send", @@ -33,35 +32,48 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { return err } + fromAcc, err := ctx.QueryStore(auth.AddressStoreKey(from), ctx.AccountStore) + if err != nil { + return err + } + + bech32From := sdk.MustBech32ifyAcc(from) + // Check if account was found + if fromAcc == nil { + return errors.New("No account with address " + bech32From + + " was found in the state.\nAre you sure there has been a transaction involving it?") + } + toStr := viper.GetString(flagTo) to, err := sdk.GetAccAddressBech32(toStr) if err != nil { return err } - // parse coins + // parse coins trying to be sent amount := viper.GetString(flagAmount) coins, err := sdk.ParseCoins(amount) if err != nil { return err } - // build and sign the transaction, then broadcast to Tendermint - msg := client.BuildMsg(from, to, coins) - - if viper.GetBool(flagAsync) { - res, err := ctx.EnsureSignBuildBroadcastAsync(ctx.FromAddressName, []sdk.Msg{msg}, cdc) - if err != nil { - return err - } - fmt.Println("Async tx sent. tx hash: ", res.Hash.String()) - return nil - } - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + // ensure account has enough coins + account, err := ctx.Decoder(fromAcc) + if err != nil { + return err + } + if !account.GetCoins().IsGTE(coins) { + return errors.New("Address " + bech32From + + " doesn't have enough coins to pay for this transaction.") + } + + // build and sign the transaction, then broadcast to Tendermint + msg := client.BuildMsg(from, to, coins) + + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, @@ -69,7 +81,6 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { cmd.Flags().String(flagTo, "", "Address to send coins") cmd.Flags().String(flagAmount, "", "Amount of coins to send") - cmd.Flags().Bool(flagAsync, false, "Pass the async flag to send a tx without waiting for the tx to be included in a block") return cmd } diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 1a871979e2..46b1995633 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -72,7 +72,7 @@ func (msg MsgSend) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. @@ -133,7 +133,7 @@ func (msg MsgIssue) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. @@ -162,7 +162,7 @@ func (in Input) GetSignBytes() []byte { if err != nil { panic(err) } - return bin + return sdk.MustSortJSON(bin) } // ValidateBasic - validate transaction input @@ -209,7 +209,7 @@ func (out Output) GetSignBytes() []byte { if err != nil { panic(err) } - return bin + return sdk.MustSortJSON(bin) } // ValidateBasic - validate transaction output diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 3754858ec7..f4e7363d3b 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -187,7 +187,7 @@ func TestMsgSendGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - expected := `{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"denom":"atom","amount":"10"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"denom":"atom","amount":"10"}]}]}` + expected := `{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}` require.Equal(t, expected, string(res)) } @@ -257,7 +257,7 @@ func TestMsgIssueGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - expected := `{"banker":"cosmosaccaddr1d9h8qat5e4ehc5","outputs":[{"address":"cosmosaccaddr1d3hkzm3dveex7mfdvfsku6cwsauqd","coins":[{"denom":"atom","amount":"10"}]}]}` + expected := `{"banker":"cosmosaccaddr1d9h8qat5e4ehc5","outputs":[{"address":"cosmosaccaddr1d3hkzm3dveex7mfdvfsku6cwsauqd","coins":[{"amount":"10","denom":"atom"}]}]}` require.Equal(t, expected, string(res)) } diff --git a/x/bank/test_helpers.go b/x/bank/test_helpers.go new file mode 100644 index 0000000000..ecaae62817 --- /dev/null +++ b/x/bank/test_helpers.go @@ -0,0 +1,150 @@ +package bank + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// ModuleInvariants runs all invariants of the bank module. +// Currently runs non-negative balance invariant and TotalCoinsInvariant +func ModuleInvariants(t *testing.T, app *mock.App, log string) { + NonnegativeBalanceInvariant(t, app, log) + TotalCoinsInvariant(t, app, log) +} + +// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances +func NonnegativeBalanceInvariant(t *testing.T, app *mock.App, log string) { + ctx := app.NewContext(false, abci.Header{}) + accts := mock.GetAllAccounts(app.AccountMapper, ctx) + for _, acc := range accts { + coins := acc.GetCoins() + assert.True(t, coins.IsNotNegative(), + fmt.Sprintf("%s has a negative denomination of %s\n%s", + acc.GetAddress().String(), + coins.String(), + log), + ) + } +} + +// TotalCoinsInvariant checks that the sum of the coins across all accounts +// is what is expected +func TotalCoinsInvariant(t *testing.T, app *mock.App, log string) { + ctx := app.BaseApp.NewContext(false, abci.Header{}) + totalCoins := sdk.Coins{} + + chkAccount := func(acc auth.Account) bool { + coins := acc.GetCoins() + totalCoins = totalCoins.Plus(coins) + return false + } + + app.AccountMapper.IterateAccounts(ctx, chkAccount) + require.Equal(t, app.TotalCoinsSupply, totalCoins, log) +} + +// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both +// accounts already exist. +func TestAndRunSingleInputMsgSend(t *testing.T, r *rand.Rand, app *mock.App, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + fromKey := keys[r.Intn(len(keys))] + fromAddr := fromKey.PubKey().Address() + toKey := keys[r.Intn(len(keys))] + // Disallow sending money to yourself + for { + if !fromKey.Equals(toKey) { + break + } + toKey = keys[r.Intn(len(keys))] + } + toAddr := toKey.PubKey().Address() + initFromCoins := app.AccountMapper.GetAccount(ctx, fromAddr).GetCoins() + + denomIndex := r.Intn(len(initFromCoins)) + amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount) + if goErr != nil { + return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil + } + + action = fmt.Sprintf("%s is sending %s %s to %s", + fromAddr.String(), + amt.String(), + initFromCoins[denomIndex].Denom, + toAddr.String(), + ) + log = fmt.Sprintf("%s\n%s", log, action) + + coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} + var msg = MsgSend{ + Inputs: []Input{NewInput(fromAddr, coins)}, + Outputs: []Output{NewOutput(toAddr, coins)}, + } + sendAndVerifyMsgSend(t, app, msg, ctx, log, []crypto.PrivKey{fromKey}) + + return action, nil +} + +// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs +func sendAndVerifyMsgSend(t *testing.T, app *mock.App, msg MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { + initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) + initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) + AccountNumbers := make([]int64, len(msg.Inputs)) + SequenceNumbers := make([]int64, len(msg.Inputs)) + + for i := 0; i < len(msg.Inputs); i++ { + acc := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address) + AccountNumbers[i] = acc.GetAccountNumber() + SequenceNumbers[i] = acc.GetSequence() + initialInputAddrCoins[i] = acc.GetCoins() + } + for i := 0; i < len(msg.Outputs); i++ { + acc := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address) + initialOutputAddrCoins[i] = acc.GetCoins() + } + tx := mock.GenTx([]sdk.Msg{msg}, + AccountNumbers, + SequenceNumbers, + privkeys...) + res := app.Deliver(tx) + if !res.IsOK() { + // TODO: Do this in a more 'canonical' way + fmt.Println(res) + fmt.Println(log) + t.FailNow() + } + + for i := 0; i < len(msg.Inputs); i++ { + terminalInputCoins := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() + require.Equal(t, + initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), + terminalInputCoins, + fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), + ) + } + for i := 0; i < len(msg.Outputs); i++ { + terminalOutputCoins := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() + require.Equal(t, + initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), + terminalOutputCoins, + fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log), + ) + } +} + +func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { + if !max.GT(sdk.OneInt()) { + return sdk.Int{}, errors.New("max too small") + } + max = max.Sub(sdk.OneInt()) + return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil +} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 00933284e9..89b5c2eb17 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -29,7 +29,7 @@ const ( // submit a proposal tx func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "submitproposal", + Use: "submit-proposal", Short: "Submit a proposal along with an initial deposit", RunE: func(cmd *cobra.Command, args []string) error { title := viper.GetString(flagTitle) @@ -63,13 +63,13 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + // proposalID must be returned, and it is a part of response + ctx.PrintResponse = true - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block:%d. Hash:%s.Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx) return nil }, } @@ -113,11 +113,10 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -164,11 +163,10 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/x/gov/msgs.go b/x/gov/msgs.go index f436d024b1..75a8d51e39 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -82,7 +82,7 @@ func (msg MsgSubmitProposal) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. @@ -149,7 +149,7 @@ func (msg MsgDeposit) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. @@ -213,7 +213,7 @@ func (msg MsgVote) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // Implements Msg. diff --git a/x/ibc/client/cli/README.md b/x/ibc/client/cli/README.md index e91fb55bfc..ab9e8e5558 100644 --- a/x/ibc/client/cli/README.md +++ b/x/ibc/client/cli/README.md @@ -109,7 +109,7 @@ key2 DC26002735D3AA9573707CFA6D77C12349E49868 ## Transfer coins (addr1:chain1 -> addr2:chain2) ```console -> basecli transfer --name key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1 +> basecli transfer --from key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1 Password to sign with 'key1': Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0 > basecli account $ADDR1 --node $NODE1 @@ -133,7 +133,7 @@ Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0 ## Relay IBC packets ```console -> basecli relay --name key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2 +> basecli relay --from key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2 Password to sign with 'key2': I[04-03|16:18:59.984] Detected IBC packet number=0 I[04-03|16:19:00.869] Relayed IBC packet number=0 diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 9df58d6a8a..21aa3a3089 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -2,7 +2,6 @@ package cli import ( "encoding/hex" - "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -43,12 +42,10 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { } // get password - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/x/ibc/types.go b/x/ibc/types.go index a311b98698..33f2a9a8d3 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -59,7 +59,7 @@ func (p IBCPacket) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // validator the ibc packey @@ -131,5 +131,5 @@ func (msg IBCReceiveMsg) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } diff --git a/x/mock/app.go b/x/mock/app.go index e16a322688..dd8edb102f 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -1,6 +1,7 @@ package mock import ( + "math/rand" "os" bam "github.com/cosmos/cosmos-sdk/baseapp" @@ -15,7 +16,9 @@ import ( const chainID = "" -// App extends an ABCI application. +// App extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions, as object +// capabilities aren't needed for testing. type App struct { *bam.BaseApp Cdc *wire.Codec // Cdc is public since the codec is passed into the module anyways @@ -26,7 +29,8 @@ type App struct { AccountMapper auth.AccountMapper FeeCollectionKeeper auth.FeeCollectionKeeper - GenesisAccounts []auth.Account + GenesisAccounts []auth.Account + TotalCoinsSupply sdk.Coins } // NewApp partially constructs a new app on the memstore for module and genesis @@ -43,10 +47,11 @@ func NewApp() *App { // Create your application object app := &App{ - BaseApp: bam.NewBaseApp("mock", cdc, logger, db), - Cdc: cdc, - KeyMain: sdk.NewKVStoreKey("main"), - KeyAccount: sdk.NewKVStoreKey("acc"), + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + TotalCoinsSupply: sdk.Coins{}, } // Define the accountMapper @@ -124,8 +129,8 @@ func SetGenesis(app *App, accs []auth.Account) { func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { // Make the transaction free fee := auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", 0)}, - 100000, + Amount: sdk.Coins{sdk.NewCoin("foocoin", 0)}, + Gas: 100000, } sigs := make([]auth.StdSignature, len(priv)) @@ -148,6 +153,70 @@ func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) return auth.NewStdTx(msgs, fee, sigs, memo) } +// GeneratePrivKeys generates a total n Ed25519 private keys. +func GeneratePrivKeys(n int) (keys []crypto.PrivKey) { + // TODO: Randomize this between ed25519 and secp256k1 + keys = make([]crypto.PrivKey, n, n) + for i := 0; i < n; i++ { + keys[i] = crypto.GenPrivKeyEd25519() + } + + return +} + +// GeneratePrivKeyAddressPairs generates a total of n private key, address +// pairs. +func GeneratePrivKeyAddressPairs(n int) (keys []crypto.PrivKey, addrs []sdk.Address) { + keys = make([]crypto.PrivKey, n, n) + addrs = make([]sdk.Address, n, n) + for i := 0; i < n; i++ { + keys[i] = crypto.GenPrivKeyEd25519() + addrs[i] = keys[i].PubKey().Address() + } + return +} + +// RandomSetGenesis set genesis accounts with random coin values using the +// provided addresses and coin denominations. +func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.Address, denoms []string) { + accts := make([]auth.Account, len(addrs), len(addrs)) + randCoinIntervals := []BigInterval{ + {sdk.NewIntWithDecimal(1, 0), sdk.NewIntWithDecimal(1, 1)}, + {sdk.NewIntWithDecimal(1, 2), sdk.NewIntWithDecimal(1, 3)}, + {sdk.NewIntWithDecimal(1, 40), sdk.NewIntWithDecimal(1, 50)}, + } + + for i := 0; i < len(accts); i++ { + coins := make([]sdk.Coin, len(denoms), len(denoms)) + + // generate a random coin for each denomination + for j := 0; j < len(denoms); j++ { + coins[j] = sdk.Coin{Denom: denoms[j], + Amount: RandFromBigInterval(r, randCoinIntervals), + } + } + + app.TotalCoinsSupply = app.TotalCoinsSupply.Plus(coins) + baseAcc := auth.NewBaseAccountWithAddress(addrs[i]) + + (&baseAcc).SetCoins(coins) + accts[i] = &baseAcc + } + + SetGenesis(app, accts) +} + +// GetAllAccounts returns all accounts in the accountMapper. +func GetAllAccounts(mapper auth.AccountMapper, ctx sdk.Context) []auth.Account { + accounts := []auth.Account{} + appendAccount := func(acc auth.Account) (stop bool) { + accounts = append(accounts, acc) + return false + } + mapper.IterateAccounts(ctx, appendAccount) + return accounts +} + // GenSequenceOfTxs generates a set of signed transactions of messages, such // that they differ only by having the sequence numbers incremented between // every transaction. diff --git a/x/mock/doc.go b/x/mock/doc.go new file mode 100644 index 0000000000..d23aac3936 --- /dev/null +++ b/x/mock/doc.go @@ -0,0 +1,15 @@ +/* +Package mock provides functions for creating applications for testing. + +This module also features randomized testing, so that various modules can test +that their operations are interoperable. + +The intended method of using this randomized testing framework is that every +module provides TestAndRunTx methods for each of its desired methods of fuzzing +its own txs, and it also provides the invariants that it assumes to be true. +You then pick and choose from these tx types and invariants. To pick and choose +these, you first build a mock app with the correct keepers. Then you call the +app.RandomizedTesting method with the set of desired txs, invariants, along +with the setups each module requires. +*/ +package mock diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go new file mode 100644 index 0000000000..a37913065d --- /dev/null +++ b/x/mock/random_simulate_blocks.go @@ -0,0 +1,95 @@ +package mock + +import ( + "fmt" + "math/big" + "math/rand" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +// RandomizedTesting tests application by sending random messages. +func (app *App) RandomizedTesting( + t *testing.T, ops []TestAndRunTx, setups []RandSetup, + invariants []Invariant, numKeys int, numBlocks int, blockSize int, +) { + time := time.Now().UnixNano() + app.RandomizedTestingFromSeed(t, time, ops, setups, invariants, numKeys, numBlocks, blockSize) +} + +// RandomizedTestingFromSeed tests an application by running the provided +// operations, testing the provided invariants, but using the provided seed. +func (app *App) RandomizedTestingFromSeed( + t *testing.T, seed int64, ops []TestAndRunTx, setups []RandSetup, + invariants []Invariant, numKeys int, numBlocks int, blockSize int, +) { + log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) + keys, addrs := GeneratePrivKeyAddressPairs(numKeys) + r := rand.New(rand.NewSource(seed)) + + for i := 0; i < len(setups); i++ { + setups[i](r, keys) + } + + RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + header := abci.Header{Height: 0} + + for i := 0; i < numBlocks; i++ { + app.BeginBlock(abci.RequestBeginBlock{}) + + // Make sure invariants hold at beginning of block and when nothing was + // done. + app.assertAllInvariants(t, invariants, log) + + ctx := app.NewContext(false, header) + + // TODO: Add modes to simulate "no load", "medium load", and + // "high load" blocks. + for j := 0; j < blockSize; j++ { + logUpdate, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log) + log += "\n" + logUpdate + + require.Nil(t, err, log) + app.assertAllInvariants(t, invariants, log) + } + + app.EndBlock(abci.RequestEndBlock{}) + header.Height++ + } +} + +func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) { + for i := 0; i < len(tests); i++ { + tests[i](t, app, log) + } +} + +// BigInterval is a representation of the interval [lo, hi), where +// lo and hi are both of type sdk.Int +type BigInterval struct { + lo sdk.Int + hi sdk.Int +} + +// RandFromBigInterval chooses an interval uniformly from the provided list of +// BigIntervals, and then chooses an element from an interval uniformly at random. +func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { + if len(intervals) == 0 { + return sdk.ZeroInt() + } + + interval := intervals[r.Intn(len(intervals))] + + lo := interval.lo + hi := interval.hi + + diff := hi.Sub(lo) + result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) + result = result.Add(lo) + + return result +} diff --git a/x/mock/types.go b/x/mock/types.go new file mode 100644 index 0000000000..50957e1c46 --- /dev/null +++ b/x/mock/types.go @@ -0,0 +1,38 @@ +package mock + +import ( + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +type ( + // TestAndRunTx produces a fuzzed transaction, and ensures the state + // transition was as expected. It returns a descriptive message "action" + // about what this fuzzed tx actually did, for ease of debugging. + TestAndRunTx func( + t *testing.T, r *rand.Rand, app *App, ctx sdk.Context, + privKeys []crypto.PrivKey, log string, + ) (action string, err sdk.Error) + + // RandSetup performs the random setup the mock module needs. + RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) + + // An Invariant is a function which tests a particular invariant. + // If the invariant has been broken, the function should halt the + // test and output the log. + Invariant func(t *testing.T, app *App, log string) +) + +// PeriodicInvariant returns an Invariant function closure that asserts +// a given invariant if the mock application's last block modulo the given +// period is congruent to the given offset. +func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { + return func(t *testing.T, app *App, log string) { + if int(app.LastBlockHeight())%period == offset { + invariant(t, app, log) + } + } +} diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index de390ce239..c73b2e162f 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" @@ -29,12 +27,10 @@ func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command { msg := slashing.NewMsgUnrevoke(validatorAddr) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 561c922665..6060079a49 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -38,7 +38,7 @@ func (msg MsgUnrevoke) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 39c7771f0a..30f833c020 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -31,8 +31,9 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { res, err := ctx.QueryStore(key, storeName) if err != nil { return err + } else if len(res) == 0 { + return fmt.Errorf("No validator found with address %s", args[0]) } - validator := types.MustUnmarshalValidator(cdc, addr, res) switch viper.Get(cli.OutputFlag) { @@ -118,7 +119,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + delAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) if err != nil { return err } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 7200caef3f..9b8acb8cc8 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -53,12 +53,10 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -92,12 +90,11 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -132,12 +129,11 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -197,12 +193,11 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -282,12 +277,11 @@ func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -340,12 +334,11 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } @@ -377,12 +370,11 @@ func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } - fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) return nil }, } diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 164c500492..c32b536cea 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -261,6 +261,13 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type if updatedVal.Owner != nil { // updates to validator occurred to be updated validator = updatedVal } + // if decreased in power but still bonded, update Tendermint validator + // (if updatedVal is set, the validator has changed bonding status) + stillBonded := oldFound && oldValidator.Status() == sdk.Bonded && updatedVal.Owner == nil + if stillBonded && oldValidator.PoolShares.Bonded().GT(validator.PoolShares.Bonded()) { + bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) + store.Set(GetTendermintUpdatesKey(ownerAddr), bz) + } return validator } diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index c037948da0..f42d8a7d1e 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -701,3 +701,43 @@ func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { require.Equal(t, validators[0].ABCIValidatorZero(), updates[0]) require.Equal(t, validators[2].ABCIValidator(), updates[1]) } + +func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{100, 100} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // check initial power + require.Equal(t, sdk.NewRat(100).RoundInt64(), validators[0].GetPower().RoundInt64()) + require.Equal(t, sdk.NewRat(100).RoundInt64(), validators[1].GetPower().RoundInt64()) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewRat(20)) + validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewRat(30)) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + // power has changed + require.Equal(t, sdk.NewRat(80).RoundInt64(), validators[0].GetPower().RoundInt64()) + require.Equal(t, sdk.NewRat(70).RoundInt64(), validators[1].GetPower().RoundInt64()) + + // Tendermint updates should reflect power change + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) +} diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index 878c1ba17b..aa9eea8cbb 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -63,7 +63,7 @@ func (msg MsgCreateValidator) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -114,7 +114,7 @@ func (msg MsgEditValidator) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -166,7 +166,7 @@ func (msg MsgDelegate) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -226,7 +226,7 @@ func (msg MsgBeginRedelegate) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -286,7 +286,7 @@ func (msg MsgCompleteRedelegate) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -338,7 +338,7 @@ func (msg MsgBeginUnbonding) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check @@ -387,7 +387,7 @@ func (msg MsgCompleteUnbonding) GetSignBytes() []byte { if err != nil { panic(err) } - return b + return sdk.MustSortJSON(b) } // quick validity check