diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..dcdf7080b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +# Primary repo maintainers +* @ebuchman @rigelrozanski @cwgoes + +# Precious documentation +/docs/ @zramsay @jolesbi diff --git a/CHANGELOG.md b/CHANGELOG.md index ecba00362..4808b6db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ *TBD* BREAKING CHANGES +* msg.GetSignBytes() returns sorted JSON (by key) * Update Tendermint to v0.22.0 * Default ports changed from 466xx to 266xx * Amino JSON uses type names instead of prefix bytes @@ -36,7 +37,13 @@ BREAKING CHANGES to an infraction, slash them proportional to their stake at the time * Add REST endpoint to unrevoke a validator previously revoked for downtime * Add REST endpoint to retrieve liveness signing information for a validator +* [types] renamed rational.Evaluate to rational.Round{Int64, Int} +* [x/stake] most index keys nolonger hold a value - inputs are rearranged to form the desired key * [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 @@ -67,7 +74,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' @@ -76,12 +89,15 @@ 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] \#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 @@ -96,6 +112,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 a9b4a1f5c..879453988 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -137,6 +137,7 @@ ".", "hcl/ast", "hcl/parser", + "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -240,7 +241,7 @@ "nfs", "xfs" ] - revision = "40f013a808ec4fa79def444a1a56de4d1727efcb" + revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] branch = "master" @@ -433,13 +434,13 @@ "netutil", "trace" ] - revision = "87b3feba568e144938625fc5d80ec92566c1a8fe" + revision = "ed29d75add3d7c4bf7ca65aac0c6df3d1420216f" [[projects]] 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 4eea49fd3..2ed89c53f 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,323 +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) - 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 000000000..581da4b17 --- /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 89c171353..e0fa45580 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 58df9b5bf..03dd6b9d0 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 c0b48fc85..611ad1b92 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 31e9c087b..b96012da7 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/add.go b/client/keys/add.go index 216f84c2a..b76354685 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -7,7 +7,6 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -162,12 +161,6 @@ type NewKeyBody struct { Password string `json:"password"` } -// new key response REST body -type NewKeyResponse struct { - Address string `json:"address"` - Mnemonic string `json:"mnemonic"` -} - // add new key REST handler func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { var kb keys.Keybase @@ -216,22 +209,24 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) return } - bech32Account, err := sdk.Bech32ifyAcc(sdk.Address(info.GetPubKey().Address().Bytes())) + + keyOutput, err := Bech32KeyOutput(info) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - bz, err := json.Marshal(NewKeyResponse{ - Address: bech32Account, - Mnemonic: mnemonic, - }) + + keyOutput.Seed = mnemonic + + output, err := json.MarshalIndent(keyOutput, "", " ") if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - w.Write(bz) + + w.Write(output) } // function to just a new seed to display in the UI before actually persisting it in the keybase diff --git a/client/keys/delete.go b/client/keys/delete.go index f38c529a6..944feb4b1 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/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index bb2a205a9..234ed57b0 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -56,7 +56,7 @@ func TestKeys(t *testing.T) { res, body = Request(t, port, "POST", "/keys", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) - var resp keys.NewKeyResponse + var resp keys.KeyOutput err = wire.Cdc.UnmarshalJSON([]byte(body), &resp) require.Nil(t, err, body) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 07230a4f9..18e967bd3 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/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 1f2ef0649..ddc8466c7 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -197,6 +197,7 @@ func MakeCodec() *wire.Codec { auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) + cdc.Seal() return cdc } diff --git a/cmd/gaia/testnets/README.md b/cmd/gaia/testnets/README.md index 57bbfc48a..e8d1f83e4 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 82adc53dd..7cf43eea6 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 3ed20852b..14735ef33 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 b2bbd8f1a..5228070cf 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 new file mode 100644 index 000000000..5ea1224ea --- /dev/null +++ b/docs/clients/ledger.md @@ -0,0 +1,29 @@ +# Ledger // Cosmos + +### Ledger Support for account keys + +`gaiacli` now supports derivation of account keys from a Ledger seed. To use this functionality you will need the following: + +- A running `gaiad` instance connected to the network you wish to use. +- A `gaiacli` instance configured to connect to your chosen `gaiad` instance. +- A LedgerNano with the `ledger-cosmos` app installed + * Install the Cosmos app onto your Ledger by following the instructions in the [`ledger-cosmos`](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) repository. + * A production-ready version of this app will soon be included in the [Ledger Apps Store](https://www.ledgerwallet.com/apps) + +> **NOTE:** Cosmos keys are derived acording to the [BIP 44 Hierarchical Deterministic wallet spec](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). For more information on Cosmos derivation paths [see the hd package](https://github.com/cosmos/cosmos-sdk/blob/develop/crypto/keys/hd/hdpath.go#L30). + +Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger: + +```bash +$ gaiacli keys add {{ .Key.Name }} --ledger +NAME: TYPE: ADDRESS: PUBKEY: +{{ .Key.Name }} ledger cosmosaccaddr1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmosaccpub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629 +``` + +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 --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/app1.md b/docs/core/app1.md index 91e75966a..ba7f02a8d 100644 --- a/docs/core/app1.md +++ b/docs/core/app1.md @@ -189,7 +189,7 @@ docs](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Context) for more det ### Result Handler takes a Context and Msg and returns a Result. -Result is motivated by the corresponding [ABCI result](https://github.com/tendermint/tendermint/abci/blob/master/types/types.proto#L165). +Result is motivated by the corresponding [ABCI result](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto#L165). It contains return values, error information, logs, and meta data about the transaction: ```go @@ -409,7 +409,7 @@ func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { Finally, we stitch it all together using the `BaseApp`. The BaseApp is an abstraction over the [Tendermint -ABCI](https://github.com/tendermint/tendermint/abci) that +ABCI](https://github.com/tendermint/tendermint/tree/master/abci) that simplifies application development by handling common low-level concerns. It serves as the mediator between the two key components of an SDK app: the store and the message handlers. The BaseApp implements the @@ -473,7 +473,7 @@ Tendermint consensus engine. It would be initialized by a Genesis file, and it would be driven by blocks of transactions committed by the underlying Tendermint consensus. We'll talk more about ABCI and how this all works a bit later, but feel free to check the -[specification](https://github.com/tendermint/tendermint/abci/blob/master/specification.md). +[specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md). We'll also see how to connect our app to a complete suite of components for running and using a live blockchain application. diff --git a/docs/core/app4.md b/docs/core/app4.md index d7226b466..0724e5e7a 100644 --- a/docs/core/app4.md +++ b/docs/core/app4.md @@ -16,7 +16,7 @@ here we will introduce the other ABCI requests sent by Tendermint, and how we can use them to build more advanced applications. For a more complete depiction of the ABCI and how its used, see [the -specification](https://github.com/tendermint/tendermint/abci/blob/master/specification.md) +specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md) ## InitChain diff --git a/docs/core/app5.md b/docs/core/app5.md index c6011f042..39cc7a263 100644 --- a/docs/core/app5.md +++ b/docs/core/app5.md @@ -59,7 +59,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { Note we utilize the popular [cobra library](https://github.com/spf13/cobra) for the CLI, in concert with the [viper library](https://github.com/spf13/library) -for managing configuration. See our [cli library](https://github.com/tendermint/tmlibs/blob/master/cli/setup.go) +for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go) for more details. TODO: compile and run the binary diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go index f9eceecdb..a9580fcc2 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 b4d44dca7..c96a1022f 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/docs/spec/slashing/end_block.md b/docs/spec/slashing/end_block.md index e923fd844..3eec27372 100644 --- a/docs/spec/slashing/end_block.md +++ b/docs/spec/slashing/end_block.md @@ -6,7 +6,7 @@ Tendermint blocks can include [Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator committed malicious behaviour. The relevant information is forwarded to the application as [ABCI -Evidence](https://github.com/tendermint/tendermint/abci/blob/develop/types/types.proto#L259), so the validator an be accordingly punished. +Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly punished. For some `evidence` to be valid, it must satisfy: diff --git a/examples/README.md b/examples/README.md index 3f45e025c..e3016fb70 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 ``` @@ -256,7 +256,7 @@ Accounts are serialized and stored in a Merkle tree under the key Typically, the address of the account is the 20-byte ``RIPEMD160`` hash of the public key, but other formats are acceptable as well, as defined in the `Tendermint crypto -library `__. The Merkle tree +library `__. The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an `IAVL tree `__. diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index f75d6a834..6df74ef28 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -103,6 +103,8 @@ func MakeCodec() *wire.Codec { cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&types.AppAccount{}, "basecoin/Account", nil) + cdc.Seal() + return cdc } diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 1c44a0bbd..6ea7d5247 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -113,6 +113,9 @@ func MakeCodec() *wire.Codec { // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) cdc.RegisterConcrete(&types.AppAccount{}, "democoin/Account", nil) + + cdc.Seal() + return cdc } diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index a642eff72..60005713e 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -4,14 +4,12 @@ import ( "os" "testing" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" 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" dbm "github.com/tendermint/tendermint/libs/db" diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 0bb84c146..e74b3b700 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/app_test.go b/examples/democoin/x/cool/app_test.go index e93f6d99c..df5786a04 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -3,15 +3,13 @@ package cool import ( "testing" - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" bank "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index cb65830c9..3e034600b 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 77f49a0ba..82c9286c6 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/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go index ef925c94f..467035897 100644 --- a/examples/democoin/x/oracle/oracle_test.go +++ b/examples/democoin/x/oracle/oracle_test.go @@ -48,6 +48,8 @@ func makeCodec() *wire.Codec { cdc.RegisterInterface((*Payload)(nil), nil) cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil) + cdc.Seal() + return cdc } diff --git a/examples/democoin/x/oracle/types.go b/examples/democoin/x/oracle/types.go index 041b9ab34..d06f47a4e 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/app_test.go b/examples/democoin/x/pow/app_test.go index d223a1f10..076dc20bc 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -7,8 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index 49527c8c0..bc958ffae 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 cd77a68af..3529917dc 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 9ed5dc102..1291cb96a 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 fcd0c84b4..20b5d9522 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 564e0d979..91729e3ef 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 0e94ace28..e1c5cab3a 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 0cc99e79f..3c9186fba 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 d1ad001c5..0f1816b16 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 8f1ab21db..6082caa2c 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 ec2c458cb..ffcf39a51 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/account.go b/types/account.go index ce872d8b5..0cdc75fe2 100644 --- a/types/account.go +++ b/types/account.go @@ -13,8 +13,12 @@ import ( //Address is a go crypto-style Address type Address = cmn.HexBytes -// Bech32 prefixes +// nolint const ( + // expected address length + AddrLen = 20 + + // Bech32 prefixes Bech32PrefixAccAddr = "cosmosaccaddr" Bech32PrefixAccPub = "cosmosaccpub" Bech32PrefixValAddr = "cosmosvaladdr" diff --git a/types/rational.go b/types/rational.go index ab400868d..24072fc09 100644 --- a/types/rational.go +++ b/types/rational.go @@ -41,6 +41,10 @@ func NewRat(Numerator int64, Denominator ...int64) Rat { // precision is the number of values after the decimal point which should be read func NewRatFromDecimal(decimalStr string, prec int) (f Rat, err Error) { // first extract any negative symbol + if len(decimalStr) == 0 { + return f, ErrUnknownRequest("decimal string is empty") + } + neg := false if string(decimalStr[0]) == "-" { neg = true diff --git a/types/rational_test.go b/types/rational_test.go index 3215313e0..ecbc09e88 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -27,6 +27,7 @@ func TestNewFromDecimal(t *testing.T) { expErr bool exp Rat }{ + {"", true, Rat{}}, {"0", false, NewRat(0)}, {"1", false, NewRat(1)}, {"1.1", false, NewRat(11, 10)}, diff --git a/types/tx_msg.go b/types/tx_msg.go index c1af91df8..fbadfb49d 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 000000000..2e027676a --- /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 000000000..8c84e2ace --- /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/wire/wire.go b/wire/wire.go index 1d5291f86..679bf7c28 100644 --- a/wire/wire.go +++ b/wire/wire.go @@ -44,6 +44,5 @@ var Cdc *Codec func init() { cdc := NewCodec() RegisterCrypto(cdc) - Cdc = cdc - //Cdc = cdc.Seal() // TODO uncomment once amino upgraded to 0.9.10 + Cdc = cdc.Seal() } diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index ccc1e277b..47980a64c 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/mock/app.go b/x/auth/mock/app.go deleted file mode 100644 index 758b1efab..000000000 --- a/x/auth/mock/app.go +++ /dev/null @@ -1,110 +0,0 @@ -package mock - -import ( - "os" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// Extended ABCI application -type App struct { - *bam.BaseApp - Cdc *wire.Codec // public since the codec is passed into the module anyways. - KeyMain *sdk.KVStoreKey - KeyAccount *sdk.KVStoreKey - - // TODO: Abstract this out from not needing to be auth specifically - AccountMapper auth.AccountMapper - FeeCollectionKeeper auth.FeeCollectionKeeper - - GenesisAccounts []auth.Account -} - -// partially construct a new app on the memstore for module and genesis testing -func NewApp() *App { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - - // create the cdc with some standard codecs - cdc := wire.NewCodec() - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) - auth.RegisterWire(cdc) - - // create your application object - app := &App{ - BaseApp: bam.NewBaseApp("mock", cdc, logger, db), - Cdc: cdc, - KeyMain: sdk.NewKVStoreKey("main"), - KeyAccount: sdk.NewKVStoreKey("acc"), - } - - // define the accountMapper - app.AccountMapper = auth.NewAccountMapper( - app.Cdc, - app.KeyAccount, // target store - &auth.BaseAccount{}, // prototype - ) - - // initialize the app, the chainers and blockers can be overwritten before calling complete setup - app.SetInitChainer(app.InitChainer) - - app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) - - return app -} - -// complete the application setup after the routes have been registered -func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { - newKeys = append(newKeys, app.KeyMain) - newKeys = append(newKeys, app.KeyAccount) - app.MountStoresIAVL(newKeys...) - err := app.LoadLatestVersion(app.KeyMain) - return err -} - -// custom logic for initialization -func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { - - // load the accounts - for _, genacc := range app.GenesisAccounts { - acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) - err := acc.SetCoins(genacc.GetCoins()) - if err != nil { - // TODO: Handle with #870 - panic(err) - } - app.AccountMapper.SetAccount(ctx, acc) - } - - return abci.ResponseInitChain{} -} - -// Generate genesis accounts loaded with coins, and returns their addresses, pubkeys, and privkeys -func CreateGenAccounts(numAccs int64, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.Address, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { - for i := int64(0); i < numAccs; i++ { - privKey := crypto.GenPrivKeyEd25519() - pubKey := privKey.PubKey() - addr := pubKey.Address() - - genAcc := &auth.BaseAccount{ - Address: addr, - Coins: genCoins, - } - - genAccs = append(genAccs, genAcc) - privKeys = append(privKeys, privKey) - pubKeys = append(pubKeys, pubKey) - addrs = append(addrs, addr) - } - - return -} diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go deleted file mode 100644 index 3f340bbf9..000000000 --- a/x/auth/mock/auth_app_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" -) - -// A mock transaction that has a validation which can fail. -type testMsg struct { - signers []sdk.Address - positiveNum int64 -} - -// TODO: Clean this up, make it public -const msgType = "testMsg" - -func (tx testMsg) Type() string { return msgType } -func (tx testMsg) GetMsg() sdk.Msg { return tx } -func (tx testMsg) GetMemo() string { return "" } -func (tx testMsg) GetSignBytes() []byte { return nil } -func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } -func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } -func (tx testMsg) ValidateBasic() sdk.Error { - if tx.positiveNum >= 0 { - return nil - } - return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") -} - -// test auth module messages - -var ( - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - - coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} - testMsg1 = testMsg{signers: []sdk.Address{addr1}, positiveNum: 1} -) - -// initialize the mock application for this module -func getMockApp(t *testing.T) *App { - mapp := NewApp() - - mapp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) - return mapp -} - -func TestMsgPrivKeys(t *testing.T) { - mapp := getMockApp(t) - mapp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins := sdk.Coins{sdk.NewCoin("foocoin", 77)} - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - accs := []auth.Account{acc1} - - // Construct genesis state - SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - require.Equal(t, acc1, res1.(*auth.BaseAccount)) - - // Run a CheckDeliver - SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{0}, true, priv1) - - // signing a SendMsg with the wrong privKey should be an auth error - mapp.BeginBlock(abci.RequestBeginBlock{}) - tx := GenTx([]sdk.Msg{testMsg1}, []int64{0}, []int64{1}, priv2) - res := mapp.Deliver(tx) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the correct priv key should still work - res = SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{1}, true, priv1) - - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), res.Code, res.Log) -} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go deleted file mode 100644 index f85fdf180..000000000 --- a/x/auth/mock/simulate_block.go +++ /dev/null @@ -1,110 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - - abci "github.com/tendermint/tendermint/abci/types" -) - -var chainID = "" // TODO - -// set the mock app genesis -func SetGenesis(app *App, accs []auth.Account) { - - // pass the accounts in via the application (lazy) instead of through RequestInitChain - app.GenesisAccounts = accs - - app.InitChain(abci.RequestInitChain{}) - app.Commit() -} - -// check an account balance -func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { - ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) - res := app.AccountMapper.GetAccount(ctxCheck, addr) - require.Equal(t, exp, res.GetCoins()) -} - -// generate a signed transaction -func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { - - // make the transaction free - fee := auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", 0)}, - 100000, - } - - sigs := make([]auth.StdSignature, len(priv)) - memo := "testmemotestmemo" - for i, p := range priv { - sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo)) - 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, memo) -} - -// generate a set of signed transactions a msg, that differ only by having the -// sequence numbers incremented between every transaction. -func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKeyEd25519) []auth.StdTx { - txs := make([]auth.StdTx, numToGenerate, numToGenerate) - for i := 0; i < numToGenerate; i++ { - txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) - incrementAllSequenceNumbers(initSeqNums) - } - return txs -} - -func incrementAllSequenceNumbers(initSeqNums []int64) { - for i := 0; i < len(initSeqNums); i++ { - initSeqNums[i]++ - } -} - -// check a transaction result -func SignCheck(app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { - tx := GenTx(msgs, accnums, seq, priv...) - res := app.Check(tx) - return res -} - -// simulate a block -func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) sdk.Result { - - // Sign the tx - tx := GenTx(msgs, accnums, seq, priv...) - - // Run a Check - res := app.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - app.BeginBlock(abci.RequestBeginBlock{}) - res = app.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - app.EndBlock(abci.RequestEndBlock{}) - - app.Commit() - return res -} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index fd55eebb9..960d9855e 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -141,7 +141,7 @@ func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs if err != nil { panic(err) } - return bz + return sdk.MustSortJSON(bz) } // StdSignMsg is a convenience structure for passing along diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 72ea312a9..a89806d12 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -5,9 +5,11 @@ 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/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -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 dce4d8fa9..b90d56955 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -5,11 +5,24 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" 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 d3f7377fa..8731de40e 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 1a871979e..46b199563 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 3754858ec..f4e7363d3 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 000000000..ecaae6281 --- /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 00933284e..89b5c2eb1 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 f436d024b..75a8d51e3 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/gov/msgs_test.go b/x/gov/msgs_test.go index e9be4ded3..d0a629299 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" ) var ( diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 743c5b639..70e47e86d 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -12,13 +12,13 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/stake" ) // initialize the mock application for this module -func getMockApp(t *testing.T, numGenAccs int64) (*mock.App, Keeper, stake.Keeper, []sdk.Address, []crypto.PubKey, []crypto.PrivKey) { +func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.Address, []crypto.PubKey, []crypto.PrivKey) { mapp := mock.NewApp() stake.RegisterWire(mapp.Cdc) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 1540bf2ed..3671cd3e1 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -7,8 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" diff --git a/x/ibc/client/cli/README.md b/x/ibc/client/cli/README.md index e91fb55bf..ab9e8e555 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 9df58d6a8..21aa3a308 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/ibc_test.go b/x/ibc/ibc_test.go index 4cb708c1b..1d87e7806 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -53,6 +53,8 @@ func makeCodec() *wire.Codec { cdc.RegisterConcrete(&auth.BaseAccount{}, "test/ibc/Account", nil) wire.RegisterCrypto(cdc) + cdc.Seal() + return cdc } diff --git a/x/ibc/types.go b/x/ibc/types.go index a311b9869..33f2a9a8d 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 new file mode 100644 index 000000000..dd8edb102 --- /dev/null +++ b/x/mock/app.go @@ -0,0 +1,237 @@ +package mock + +import ( + "math/rand" + "os" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +const chainID = "" + +// 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 + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + + // TODO: Abstract this out from not needing to be auth specifically + AccountMapper auth.AccountMapper + FeeCollectionKeeper auth.FeeCollectionKeeper + + GenesisAccounts []auth.Account + TotalCoinsSupply sdk.Coins +} + +// NewApp partially constructs a new app on the memstore for module and genesis +// testing. +func NewApp() *App { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + + // Create the cdc with some standard codecs + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + auth.RegisterWire(cdc) + + // Create your application object + app := &App{ + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + TotalCoinsSupply: sdk.Coins{}, + } + + // Define the accountMapper + app.AccountMapper = auth.NewAccountMapper( + app.Cdc, + app.KeyAccount, + &auth.BaseAccount{}, + ) + + // Initialize the app. The chainers and blockers can be overwritten before + // calling complete setup. + app.SetInitChainer(app.InitChainer) + app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + + return app +} + +// CompleteSetup completes the application setup after the routes have been +// registered. +func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { + newKeys = append(newKeys, app.KeyMain) + newKeys = append(newKeys, app.KeyAccount) + + app.MountStoresIAVL(newKeys...) + err := app.LoadLatestVersion(app.KeyMain) + + return err +} + +// InitChainer performs custom logic for initialization. +func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + // Load the genesis accounts + for _, genacc := range app.GenesisAccounts { + acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) + acc.SetCoins(genacc.GetCoins()) + app.AccountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} +} + +// CreateGenAccounts generates genesis accounts loaded with coins, and returns +// their addresses, pubkeys, and privkeys. +func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.Address, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { + for i := 0; i < numAccs; i++ { + privKey := crypto.GenPrivKeyEd25519() + pubKey := privKey.PubKey() + addr := pubKey.Address() + + genAcc := &auth.BaseAccount{ + Address: addr, + Coins: genCoins, + } + + genAccs = append(genAccs, genAcc) + privKeys = append(privKeys, privKey) + pubKeys = append(pubKeys, pubKey) + addrs = append(addrs, addr) + } + + return +} + +// SetGenesis sets the mock app genesis accounts. +func SetGenesis(app *App, accs []auth.Account) { + // Pass the accounts in via the application (lazy) instead of through + // RequestInitChain. + app.GenesisAccounts = accs + + app.InitChain(abci.RequestInitChain{}) + app.Commit() +} + +// GenTx generates a signed mock transaction. +func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { + // Make the transaction free + fee := auth.StdFee{ + Amount: sdk.Coins{sdk.NewCoin("foocoin", 0)}, + Gas: 100000, + } + + sigs := make([]auth.StdSignature, len(priv)) + memo := "testmemotestmemo" + + for i, p := range priv { + sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo)) + 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, 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. +func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKey) []auth.StdTx { + txs := make([]auth.StdTx, numToGenerate, numToGenerate) + for i := 0; i < numToGenerate; i++ { + txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) + incrementAllSequenceNumbers(initSeqNums) + } + + return txs +} + +func incrementAllSequenceNumbers(initSeqNums []int64) { + for i := 0; i < len(initSeqNums); i++ { + initSeqNums[i]++ + } +} diff --git a/x/mock/app_test.go b/x/mock/app_test.go new file mode 100644 index 000000000..7ee82474c --- /dev/null +++ b/x/mock/app_test.go @@ -0,0 +1,102 @@ +package mock + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +const msgType = "testMsg" + +var ( + numAccts = 2 + genCoins = sdk.Coins{sdk.NewCoin("foocoin", 77)} + accs, addrs, pubKeys, privKeys = CreateGenAccounts(numAccts, genCoins) +) + +// testMsg is a mock transaction that has a validation which can fail. +type testMsg struct { + signers []sdk.Address + positiveNum int64 +} + +func (tx testMsg) Type() string { return msgType } +func (tx testMsg) GetMsg() sdk.Msg { return tx } +func (tx testMsg) GetMemo() string { return "" } +func (tx testMsg) GetSignBytes() []byte { return nil } +func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } +func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } +func (tx testMsg) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + +// getMockApp returns an initialized mock application. +func getMockApp(t *testing.T) *App { + mApp := NewApp() + + mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{})) + + return mApp +} + +func TestCheckAndDeliverGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + ctxCheck := mApp.BaseApp.NewContext(true, abci.Header{}) + + msg := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: 1} + + acct := mApp.AccountMapper.GetAccount(ctxCheck, addrs[0]) + require.Equal(t, accs[0], acct.(*auth.BaseAccount)) + + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + // Signing a tx with the wrong privKey should result in an auth error + res := SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, + false, privKeys[1], + ) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // Resigning the tx with the correct privKey should result in an OK result + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, + true, privKeys[0], + ) +} + +func TestCheckGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + + msg1 := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: 1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg1}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + msg2 := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: -1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg2}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + false, privKeys[0], + ) +} diff --git a/x/mock/doc.go b/x/mock/doc.go new file mode 100644 index 000000000..d23aac393 --- /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 000000000..a37913065 --- /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/test_utils.go b/x/mock/test_utils.go new file mode 100644 index 000000000..f9620c140 --- /dev/null +++ b/x/mock/test_utils.go @@ -0,0 +1,71 @@ +package mock + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// CheckBalance checks the balance of an account. +func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { + ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + res := app.AccountMapper.GetAccount(ctxCheck, addr) + + require.Equal(t, exp, res.GetCoins()) +} + +// CheckGenTx checks a generated signed transaction. The result of the check is +// compared against the parameter 'expPass'. A test assertion is made using the +// parameter 'expPass' against the result. A corresponding result is returned. +func CheckGenTx( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + return res +} + +// SignCheckDeliver checks a generated signed transaction and simulates a +// block commitment with the given transaction. A test assertion is made using +// the parameter 'expPass' against the result. A corresponding result is +// returned. +func SignCheckDeliver( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + // Simulate a sending a transaction and committing a block + app.BeginBlock(abci.RequestBeginBlock{}) + res = app.Deliver(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + app.EndBlock(abci.RequestEndBlock{}) + app.Commit() + + return res +} diff --git a/x/mock/types.go b/x/mock/types.go new file mode 100644 index 000000000..50957e1c4 --- /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/app_test.go b/x/slashing/app_test.go index a64d3eb0f..d6fb0d9fe 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -5,11 +5,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/require" - + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -103,10 +102,9 @@ func TestSlashingMsgs(t *testing.T) { require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) unrevokeMsg := MsgUnrevoke{ValidatorAddr: validator.PubKey.Address()} - // no signing info yet checkValidatorSigningInfo(t, mapp, keeper, addr1, false) - // unrevoke should fail with validator not revoked - res := mock.SignCheck(mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, priv1) + // unrevoke should fail with unknown validator + res := mock.CheckGenTx(t, mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, false, priv1) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), res.Code) } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index de390ce23..c73b2e162 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/handler_test.go b/x/slashing/handler_test.go index c2c5d9166..d5a6b15db 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -15,7 +15,8 @@ func TestCannotUnrevokeUnlessRevoked(t *testing.T) { slh := NewHandler(keeper) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) + msg := newTestMsgCreateValidator(addr, val, amt) + got := stake.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 561c92266..6060079a4 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/slashing/test_common.go b/x/slashing/test_common.go index edf119bc7..77b553317 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -23,16 +23,16 @@ import ( // TODO remove dependencies on staking (should only refer to validator set type from sdk) var ( - addrs = []sdk.Address{ - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), - } pks = []crypto.PubKey{ newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), } + addrs = []sdk.Address{ + pks[0].Address(), + pks[1].Address(), + pks[2].Address(), + } initCoins sdk.Int = sdk.NewInt(200) ) diff --git a/x/stake/app_test.go b/x/stake/app_test.go index d4fc5bdf0..02dd09134 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -5,11 +5,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/assert" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -22,81 +20,86 @@ var ( addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() priv4 = crypto.GenPrivKeyEd25519() addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", sdk.NewInt(0)}}, - 100000, - } + coins = sdk.NewCoin("foocoin", 10) + fee = auth.StdFee{sdk.Coins{sdk.NewCoin("foocoin", 0)}, 100000} ) -// initialize the mock application for this module +// getMockApp returns an initialized mock application for this module. func getMockApp(t *testing.T) (*mock.App, Keeper) { - mapp := mock.NewApp() + mApp := mock.NewApp() + + RegisterWire(mApp.Cdc) - RegisterWire(mapp.Cdc) keyStake := sdk.NewKVStoreKey("stake") - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - keeper := NewKeeper(mapp.Cdc, keyStake, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) - mapp.Router().AddRoute("stake", NewHandler(keeper)) + coinKeeper := bank.NewKeeper(mApp.AccountMapper) + keeper := NewKeeper(mApp.Cdc, keyStake, coinKeeper, mApp.RegisterCodespace(DefaultCodespace)) - mapp.SetEndBlocker(getEndBlocker(keeper)) - mapp.SetInitChainer(getInitChainer(mapp, keeper)) + mApp.Router().AddRoute("stake", NewHandler(keeper)) + mApp.SetEndBlocker(getEndBlocker(keeper)) + mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) - return mapp, keeper + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) + return mApp, keeper } -// stake endblocker +// getEndBlocker returns a stake endblocker. func getEndBlocker(keeper Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, } } } -// overwrite the mock init chainer +// getInitChainer initializes the chainer of the mock app and sets the genesis +// state. It returns an empty ResponseInitChain. func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) + stakeGenesis := DefaultGenesisState() stakeGenesis.Pool.LooseTokens = 100000 + InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } } -//__________________________________________________________________________________________ - -func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, - addr sdk.Address, expFound bool) Validator { - +func checkValidator( + t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.Address, expFound bool, +) Validator { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) validator, found := keeper.GetValidator(ctxCheck, addr1) + require.Equal(t, expFound, found) return validator } -func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, - validatorAddr sdk.Address, expFound bool, expShares sdk.Rat) { - +func checkDelegation( + t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, + validatorAddr sdk.Address, expFound bool, expShares sdk.Rat, +) { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) if expFound { require.True(t, found) - assert.True(sdk.RatEq(t, expShares, delegation.Shares)) + require.True(sdk.RatEq(t, expShares, delegation.Shares)) + return } + require.False(t, found) } func TestStakeMsgs(t *testing.T) { - mapp, keeper := getMockApp(t) + mApp, keeper := getMockApp(t) - genCoin := sdk.Coin{"steak", sdk.NewInt(42)} - bondCoin := sdk.Coin{"steak", sdk.NewInt(10)} + genCoin := sdk.NewCoin("steak", 42) + bondCoin := sdk.NewCoin("steak", 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -108,56 +111,51 @@ func TestStakeMsgs(t *testing.T) { } accs := []auth.Account{acc1, acc2} - mock.SetGenesis(mapp, accs) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) - - //////////////////// - // Create Validator + mock.SetGenesis(mApp, accs) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + // create validator description := NewDescription("foo_moniker", "", "", "") createValidatorMsg := NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) - mapp.BeginBlock(abci.RequestBeginBlock{}) - validator := checkValidator(t, mapp, keeper, addr1, true) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, addr1, validator.Owner) require.Equal(t, sdk.Bonded, validator.Status()) require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) // check the bond that should have been created as well - checkDelegation(t, mapp, keeper, addr1, addr1, true, sdk.NewRat(10)) - - //////////////////// - // Edit Validator + checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10)) + // edit the validator description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(addr1, description) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{1}, true, priv1) - validator = checkValidator(t, mapp, keeper, addr1, true) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{1}, true, priv1) + validator = checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, description, validator.Description) - //////////////////// - // Delegate - - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + // delegate + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{0}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) - checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) - //////////////////// - // Begin Unbonding + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{0}, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, addr1, true, sdk.NewRat(10)) + // begin unbonding beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10)) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) // delegation should exist anymore - checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) + checkDelegation(t, mApp, keeper, addr2, addr1, false, sdk.Rat{}) // balance should be the same because bonding not yet complete - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index bccfd7c2b..30f833c02 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -11,6 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // get the command to query a validator @@ -30,9 +31,10 @@ 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 := new(stake.Validator) - cdc.MustUnmarshalBinary(res, validator) + validator := types.MustUnmarshalValidator(cdc, addr, res) switch viper.Get(cli.OutputFlag) { case "text": @@ -74,9 +76,9 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the validators var validators []stake.Validator - for _, KV := range resKVs { - var validator stake.Validator - cdc.MustUnmarshalBinary(KV.Value, &validator) + for _, kv := range resKVs { + addr := kv.Key[1:] + validator := types.MustUnmarshalValidator(cdc, addr, kv.Value) validators = append(validators, validator) } @@ -117,12 +119,12 @@ 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 } - key := stake.GetDelegationKey(delAddr, valAddr, cdc) + key := stake.GetDelegationKey(delAddr, valAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -130,7 +132,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { } // parse out the delegation - delegation := new(stake.Delegation) + delegation := types.MustUnmarshalDelegation(cdc, key, res) switch viper.Get(cli.OutputFlag) { case "text": @@ -140,7 +142,6 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, delegation) output, err := wire.MarshalJSONIndent(cdc, delegation) if err != nil { return err @@ -169,7 +170,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } - key := stake.GetDelegationsKey(delegatorAddr, cdc) + key := stake.GetDelegationsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -178,9 +179,8 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the validators var delegations []stake.Delegation - for _, KV := range resKVs { - var delegation stake.Delegation - cdc.MustUnmarshalBinary(KV.Value, &delegation) + for _, kv := range resKVs { + delegation := types.MustUnmarshalDelegation(cdc, kv.Key, kv.Value) delegations = append(delegations, delegation) } @@ -214,7 +214,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co return err } - key := stake.GetUBDKey(delAddr, valAddr, cdc) + key := stake.GetUBDKey(delAddr, valAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -222,7 +222,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co } // parse out the unbonding delegation - ubd := new(stake.UnbondingDelegation) + ubd := types.MustUnmarshalUBD(cdc, key, res) switch viper.Get(cli.OutputFlag) { case "text": @@ -232,7 +232,6 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, ubd) output, err := wire.MarshalJSONIndent(cdc, ubd) if err != nil { return err @@ -261,7 +260,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C if err != nil { return err } - key := stake.GetUBDsKey(delegatorAddr, cdc) + key := stake.GetUBDsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -270,9 +269,8 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C // parse out the validators var ubds []stake.UnbondingDelegation - for _, KV := range resKVs { - var ubd stake.UnbondingDelegation - cdc.MustUnmarshalBinary(KV.Value, &ubd) + for _, kv := range resKVs { + ubd := types.MustUnmarshalUBD(cdc, kv.Key, kv.Value) ubds = append(ubds, ubd) } @@ -309,7 +307,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr, cdc) + key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -317,7 +315,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { } // parse out the unbonding delegation - red := new(stake.Redelegation) + red := types.MustUnmarshalRED(cdc, key, res) switch viper.Get(cli.OutputFlag) { case "text": @@ -327,7 +325,6 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, red) output, err := wire.MarshalJSONIndent(cdc, red) if err != nil { return err @@ -356,7 +353,7 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command if err != nil { return err } - key := stake.GetREDsKey(delegatorAddr, cdc) + key := stake.GetREDsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -365,9 +362,8 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command // parse out the validators var reds []stake.Redelegation - for _, KV := range resKVs { - var red stake.Redelegation - cdc.MustUnmarshalBinary(KV.Value, &red) + for _, kv := range resKVs { + red := types.MustUnmarshalRED(cdc, kv.Key, kv.Value) reds = append(reds, red) } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index dff765fac..9b8acb8cc 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 }, } @@ -240,7 +235,7 @@ func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercent } // make a query to get the existing delegation shares - key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) ctx := context.NewCoreContextFromViper() resQuery, err := ctx.QueryStore(key, storeName) if err != 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/client/rest/query.go b/x/stake/client/rest/query.go index afa9e3bf0..090445f0a 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -9,7 +9,9 @@ import ( "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/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) const storeName = "stake" @@ -60,7 +62,7 @@ func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF return } - key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -75,11 +77,10 @@ func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF return } - var delegation stake.Delegation - err = cdc.UnmarshalBinary(res, &delegation) + delegation, err := types.UnmarshalDelegation(cdc, key, res) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode delegation. Error: %s", err.Error()))) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) return } @@ -117,7 +118,7 @@ func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - key := stake.GetUBDKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetUBDKey(delegatorAddr, validatorAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -132,11 +133,10 @@ func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - var ubd stake.UnbondingDelegation - err = cdc.UnmarshalBinary(res, &ubd) + ubd, err := types.UnmarshalUBD(cdc, key, res) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode unbonding-delegation. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) return } @@ -182,7 +182,7 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr, cdc) + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -197,11 +197,10 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - var red stake.Redelegation - err = cdc.UnmarshalBinary(res, &red) + red, err := types.UnmarshalRED(cdc, key, res) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode redelegation. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) return } @@ -291,15 +290,19 @@ func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF // parse out the validators validators := make([]StakeValidatorOutput, len(kvs)) for i, kv := range kvs { - var validator stake.Validator - var bech32Validator StakeValidatorOutput - err = cdc.UnmarshalBinary(kv.Value, &validator) - if err == nil { - bech32Validator, err = bech32StakeValidatorOutput(validator) - } + + addr := kv.Key[1:] + validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode validator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + bech32Validator, err := bech32StakeValidatorOutput(validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) return } validators[i] = bech32Validator diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 55dd06f76..1116a4748 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,50 +1,58 @@ package stake import ( - tmtypes "github.com/tendermint/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" + tmtypes "github.com/tendermint/tendermint/types" ) -// InitGenesis - store genesis parameters +// InitGenesis sets the pool and parameters for the provided keeper and +// initializes the IntraTxCounter. For each validator in data, it sets that +// validator in the keeper along with manually setting the indexes. In +// addition, it also sets any delegations found in data. Finally, it updates +// the bonded validators. func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { keeper.SetPool(ctx, data.Pool) keeper.SetNewParams(ctx, data.Params) keeper.InitIntraTxCounter(ctx) - for _, validator := range data.Validators { - // set validator + for _, validator := range data.Validators { keeper.SetValidator(ctx, validator) - // manually set indexes for the first time + // Manually set indexes for the first time keeper.SetValidatorByPubKeyIndex(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) + if validator.Status() == sdk.Bonded { keeper.SetValidatorBondedIndex(ctx, validator) } } + for _, bond := range data.Bonds { keeper.SetDelegation(ctx, bond) } + keeper.UpdateBondedValidatorsFull(ctx) } -// WriteGenesis - output genesis parameters +// WriteGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, params, validators, and bonds found in +// the keeper. func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) validators := keeper.GetAllValidators(ctx) bonds := keeper.GetAllDelegations(ctx) + return types.GenesisState{ - pool, - params, - validators, - bonds, + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, } } -// WriteValidators - output current validator set +// WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ @@ -52,7 +60,9 @@ func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisVali Power: validator.GetPower().RoundInt64(), Name: validator.GetMoniker(), }) + return false }) + return } diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 514939e17..353154ce2 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -12,12 +12,13 @@ func (k Keeper) GetDelegation(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address) (delegation types.Delegation, found bool) { store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) - if delegatorBytes == nil { + key := GetDelegationKey(delegatorAddr, validatorAddr) + value := store.Get(key) + if value == nil { return delegation, false } - k.cdc.MustUnmarshalBinary(delegatorBytes, &delegation) + delegation = types.MustUnmarshalDelegation(k.cdc, key, value) return delegation, true } @@ -31,9 +32,7 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati if !iterator.Valid() { break } - bondBytes := iterator.Value() - var delegation types.Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) delegations = append(delegations, delegation) iterator.Next() } @@ -46,7 +45,7 @@ func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (delegations []types.Delegation) { store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) + delegatorPrefixKey := GetDelegationsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest delegations = make([]types.Delegation, maxRetrieve) @@ -55,9 +54,7 @@ func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, if !iterator.Valid() || i > int(maxRetrieve-1) { break } - bondBytes := iterator.Value() - var delegation types.Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) delegations[i] = delegation iterator.Next() } @@ -68,14 +65,14 @@ func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, // set the delegation func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(delegation) - store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc), b) + b := types.MustMarshalDelegation(k.cdc, delegation) + store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b) } // remove the delegation func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc)) + store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } //_____________________________________________________________________________________ @@ -85,51 +82,49 @@ func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, DelegatorAddr, ValidatorAddr sdk.Address) (ubd types.UnbondingDelegation, found bool) { store := ctx.KVStore(k.storeKey) - ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr, k.cdc) - bz := store.Get(ubdKey) - if bz == nil { + key := GetUBDKey(DelegatorAddr, ValidatorAddr) + value := store.Get(key) + if value == nil { return ubd, false } - k.cdc.MustUnmarshalBinary(bz, &ubd) + ubd = types.MustUnmarshalUBD(k.cdc, key, value) return ubd, true } // load all unbonding delegations from a particular validator -func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (unbondingDelegations []types.UnbondingDelegation) { +func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (ubds []types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr, k.cdc)) - i := 0 - for ; ; i++ { + iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr)) + for { if !iterator.Valid() { break } - unbondingKey := iterator.Value() - unbondingBytes := store.Get(unbondingKey) - var unbondingDelegation types.UnbondingDelegation - k.cdc.MustUnmarshalBinary(unbondingBytes, &unbondingDelegation) - unbondingDelegations = append(unbondingDelegations, unbondingDelegation) + key := GetUBDKeyFromValIndexKey(iterator.Key()) + value := store.Get(key) + ubd := types.MustUnmarshalUBD(k.cdc, key, value) + ubds = append(ubds, ubd) iterator.Next() } iterator.Close() - return unbondingDelegations + return ubds } // set the unbonding delegation and associated index func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(ubd) - ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) - store.Set(ubdKey, bz) - store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc), ubdKey) + bz := types.MustMarshalUBD(k.cdc, ubd) + key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + store.Set(key, bz) + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr), []byte{}) // index, store empty bytes } // remove the unbonding delegation object and associated index func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) - store.Delete(ubdKey) - store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)) + key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + store.Delete(key) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) } //_____________________________________________________________________________________ @@ -139,34 +134,32 @@ func (k Keeper) GetRedelegation(ctx sdk.Context, DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.Address) (red types.Redelegation, found bool) { store := ctx.KVStore(k.storeKey) - redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr, k.cdc) - bz := store.Get(redKey) - if bz == nil { + key := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr) + value := store.Get(key) + if value == nil { return red, false } - k.cdc.MustUnmarshalBinary(bz, &red) + red = types.MustUnmarshalRED(k.cdc, key, value) return red, true } // load all redelegations from a particular validator -func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (redelegations []types.Redelegation) { +func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (reds []types.Redelegation) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr, k.cdc)) - i := 0 - for ; ; i++ { + iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr)) + for { if !iterator.Valid() { break } - redelegationKey := iterator.Value() - redelegationBytes := store.Get(redelegationKey) - var redelegation types.Redelegation - k.cdc.MustUnmarshalBinary(redelegationBytes, &redelegation) - redelegations = append(redelegations, redelegation) + key := GetREDKeyFromValSrcIndexKey(iterator.Key()) + value := store.Get(key) + red := types.MustUnmarshalRED(k.cdc, key, value) + reds = append(reds, red) iterator.Next() } iterator.Close() - return redelegations + return reds } // has a redelegation @@ -174,7 +167,7 @@ func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, DelegatorAddr, ValidatorDstAddr sdk.Address) bool { store := ctx.KVStore(k.storeKey) - prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr, k.cdc) + prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr) iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest found := false @@ -189,20 +182,20 @@ func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, // set a redelegation and associated index func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(red) - redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) - store.Set(redKey, bz) - store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) - store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) + bz := types.MustMarshalRED(k.cdc, red) + key := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) + store.Set(key, bz) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) } // remove a redelegation object and associated index func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) - redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) store.Delete(redKey) - store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) - store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) } //_____________________________________________________________________________________ diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index eb318df4d..c0a3ee8c5 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -179,6 +179,36 @@ func TestUnbondDelegation(t *testing.T) { require.Equal(t, int64(4), pool.BondedTokens) } +// Make sure that that the retrieving the delegations doesn't affect the state +func TestGetRedelegationsFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + // get the redelegations one time + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + + // get the redelegations a second time, should be exactly the same + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) +} + // tests Get/Set/Remove/Has UnbondingDelegation func TestRedelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) @@ -201,7 +231,10 @@ func TestRedelegation(t *testing.T) { keeper.SetRedelegation(ctx, rd) resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) - require.True(t, rd.Equal(resBond)) + + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) // check if has the redelegation has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) @@ -211,10 +244,15 @@ func TestRedelegation(t *testing.T) { rd.SharesSrc = sdk.NewRat(21) rd.SharesDst = sdk.NewRat(21) keeper.SetRedelegation(ctx, rd) + resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) require.True(t, rd.Equal(resBond)) + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + // delete a record keeper.RemoveRedelegation(ctx, rd) _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 667f5f681..32c54f519 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -6,7 +6,6 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -29,41 +28,49 @@ var ( UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner RedelegationKey = []byte{0x0D} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by validator owner - RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by validator owner + RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by source validator owner + RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by destination validator owner ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch -// get the key for the validator with address +// get the key for the validator with address. +// VALUE: stake/types.Validator func GetValidatorKey(ownerAddr sdk.Address) []byte { return append(ValidatorsKey, ownerAddr.Bytes()...) } -// get the key for the validator with pubkey +// get the key for the validator with pubkey. +// VALUE: validator owner address ([]byte) func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) } -// get the key for the current validator group, ordered like tendermint +// get the key for the current validator group +// VALUE: none (key rearrangement with GetValKeyFromValBondedIndexKey) func GetValidatorsBondedIndexKey(ownerAddr sdk.Address) []byte { return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...) } -// get the power which is the key for the validator used in the power-store -func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { - - // NOTE the address doesn't need to be stored because counter bytes must always be different - return GetValidatorPowerRank(validator, pool) +// Get the validator owner address from ValBondedIndexKey +func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { + return IndexKey[1:] // remove prefix bytes } -// get the power of a validator -func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { +// get the validator by power index. power index is the key used in the power-store, +// and represents the relative power ranking of the validator. +// VALUE: validator owner address ([]byte) +func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { + // NOTE the address doesn't need to be stored because counter bytes must always be different + return getValidatorPowerRank(validator, pool) +} + +// get the power ranking of a validator +func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { power := validator.EquivalentBondedShares(pool) powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - // TODO ensure that the key will be a readable string.. probably should add seperators and have revokedBytes := make([]byte, 1) if validator.Revoked { revokedBytes[0] = byte(0x01) @@ -71,127 +78,162 @@ func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { revokedBytes[0] = byte(0x00) } - // TODO ensure that the key will be a readable string.. probably should add seperators and have // heightBytes and counterBytes represent strings like powerBytes does heightBytes := make([]byte, binary.MaxVarintLen64) binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) counterBytes := make([]byte, 2) binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(ValidatorsByPowerIndexKey, - append(revokedBytes, - append(powerBytes, - append(heightBytes, counterBytes...)...)...)...) + return append(append(append(append( + ValidatorsByPowerIndexKey, + revokedBytes...), + powerBytes...), + heightBytes...), + counterBytes...) } -// get the key for the accumulated update validators +// get the key for the accumulated update validators. +// VALUE: abci.Validator +// note records using these keys should never persist between blocks func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { return append(TendermintUpdatesKey, ownerAddr.Bytes()...) } //________________________________________________________________________________ -// get the key for delegator bond with validator -func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +// get the key for delegator bond with validator. +// VALUE: stake/types.Delegation +func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append(GetDelegationsKey(delegatorAddr), validatorAddr.Bytes()...) } // get the prefix for a delegator for all validators -func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(DelegationKey, res...) +func GetDelegationsKey(delegatorAddr sdk.Address) []byte { + return append(DelegationKey, delegatorAddr.Bytes()...) } //________________________________________________________________________________ -// get the key for an unbonding delegation -func GetUBDKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetUBDsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +// get the key for an unbonding delegation by delegator and validator addr. +// VALUE: stake/types.UnbondingDelegation +func GetUBDKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append( + GetUBDsKey(delegatorAddr.Bytes()), + validatorAddr.Bytes()...) } // get the index-key for an unbonding delegation, stored by validator-index -func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetUBDsByValIndexKey(validatorAddr, cdc), delegatorAddr.Bytes()...) +// VALUE: none (key rearrangement used) +func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append(GetUBDsByValIndexKey(validatorAddr), delegatorAddr.Bytes()...) +} + +// rearrange the ValIndexKey to get the UBDKey +func GetUBDKeyFromValIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + panic("unexpected key length") + } + valAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen:] + return GetUBDKey(delAddr, valAddr) } //______________ // get the prefix for all unbonding delegations from a delegator -func GetUBDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(UnbondingDelegationKey, res...) +func GetUBDsKey(delegatorAddr sdk.Address) []byte { + return append(UnbondingDelegationKey, delegatorAddr.Bytes()...) } -// get the prefix keyspace for the indexs of unbonding delegations for a validator -func GetUBDsByValIndexKey(validatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorAddr) - return append(UnbondingDelegationByValIndexKey, res...) +// get the prefix keyspace for the indexes of unbonding delegations for a validator +func GetUBDsByValIndexKey(validatorAddr sdk.Address) []byte { + return append(UnbondingDelegationByValIndexKey, validatorAddr.Bytes()...) } //________________________________________________________________________________ // get the key for a redelegation +// VALUE: stake/types.RedelegationKey func GetREDKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsKey(delegatorAddr, cdc), - append( - validatorSrcAddr.Bytes(), - validatorDstAddr.Bytes()...)..., - ) + return append(append( + GetREDsKey(delegatorAddr.Bytes()), + validatorSrcAddr.Bytes()...), + validatorDstAddr.Bytes()...) } // get the index-key for a redelegation, stored by source-validator-index +// VALUE: none (key rearrangement used) func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsFromValSrcIndexKey(validatorSrcAddr, cdc), - append( - delegatorAddr.Bytes(), - validatorDstAddr.Bytes()...)..., - ) + return append(append( + GetREDsFromValSrcIndexKey(validatorSrcAddr), + delegatorAddr.Bytes()...), + validatorDstAddr.Bytes()...) } // get the index-key for a redelegation, stored by destination-validator-index +// VALUE: none (key rearrangement used) func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsToValDstIndexKey(validatorDstAddr, cdc), - append( - delegatorAddr.Bytes(), - validatorSrcAddr.Bytes()...)..., - ) + return append(append( + GetREDsToValDstIndexKey(validatorDstAddr), + delegatorAddr.Bytes()...), + validatorSrcAddr.Bytes()...) +} + +// rearrange the ValSrcIndexKey to get the REDKey +func GetREDKeyFromValSrcIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + panic("unexpected key length") + } + valSrcAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] + valDstAddr := addrs[2*sdk.AddrLen:] + + return GetREDKey(delAddr, valSrcAddr, valDstAddr) +} + +// rearrange the ValDstIndexKey to get the REDKey +func GetREDKeyFromValDstIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + panic("unexpected key length") + } + valDstAddr := addrs[:sdk.AddrLen] + delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] + valSrcAddr := addrs[2*sdk.AddrLen:] + return GetREDKey(delAddr, valSrcAddr, valDstAddr) } //______________ // get the prefix keyspace for redelegations from a delegator -func GetREDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(RedelegationKey, res...) +func GetREDsKey(delegatorAddr sdk.Address) []byte { + return append(RedelegationKey, delegatorAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating away from a source validator -func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorSrcAddr) - return append(RedelegationByValSrcIndexKey, res...) +func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address) []byte { + return append(RedelegationByValSrcIndexKey, validatorSrcAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating towards a destination validator -func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorDstAddr) - return append(RedelegationByValDstIndexKey, res...) +func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address) []byte { + return append(RedelegationByValDstIndexKey, validatorDstAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating towards a destination validator // from a particular delegator func GetREDsByDelToValDstIndexKey(delegatorAddr sdk.Address, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { return append( - GetREDsToValDstIndexKey(validatorDstAddr, cdc), + GetREDsToValDstIndexKey(validatorDstAddr), delegatorAddr.Bytes()...) } diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 55ad658a2..b8be3e34f 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -16,9 +16,8 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) i := int64(0) for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var validator types.Validator - k.cdc.MustUnmarshalBinary(bz, &validator) + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? if stop { break @@ -34,7 +33,7 @@ func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, va iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) i := int64(0) for ; iterator.Valid(); iterator.Next() { - address := iterator.Value() + address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -87,13 +86,11 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Add // iterate through the active validator set and perform the provided function func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr, k.cdc) + key := GetDelegationsKey(delAddr) iterator := sdk.KVStorePrefixIterator(store, key) i := int64(0) for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var delegation types.Delegation - k.cdc.MustUnmarshalBinary(bz, &delegation) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? if stop { break diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 2d237e46b..2c443ec21 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -14,11 +14,11 @@ import ( // get a single validator func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) - b := store.Get(GetValidatorKey(addr)) - if b == nil { + value := store.Get(GetValidatorKey(addr)) + if value == nil { return validator, false } - k.cdc.MustUnmarshalBinary(b, &validator) + validator = types.MustUnmarshalValidator(k.cdc, addr, value) return validator, true } @@ -35,15 +35,13 @@ func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (val // set the main record holding validator details func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - // set main store - bz := k.cdc.MustMarshalBinary(validator) + bz := types.MustMarshalValidator(k.cdc, validator) store.Set(GetValidatorKey(validator.Owner), bz) } // validator index func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - // set pointer by pubkey store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) } @@ -56,7 +54,7 @@ func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Valida // validator index func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) } // used in testing @@ -75,9 +73,8 @@ func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) if !iterator.Valid() { break } - bz := iterator.Value() - var validator types.Validator - k.cdc.MustUnmarshalBinary(bz, &validator) + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) validators = append(validators, validator) iterator.Next() } @@ -96,9 +93,8 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators [] if !iterator.Valid() || i > int(maxRetrieve-1) { break } - bz := iterator.Value() - var validator types.Validator - k.cdc.MustUnmarshalBinary(bz, &validator) + addr := iterator.Key()[1:] + validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) validators[i] = validator iterator.Next() } @@ -124,7 +120,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat if i > int(maxValidators-1) { panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") } - address := iterator.Value() + address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -205,8 +201,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type // always update the main list ordered by owner address before exiting defer func() { - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(ownerAddr), bz) + k.SetValidator(ctx, validator) }() // retrieve the old validator record @@ -266,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 } @@ -388,7 +390,7 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { toKickOut := make(map[string]byte) iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) for ; iterator.Valid(); iterator.Next() { - ownerAddr := iterator.Value() + ownerAddr := GetAddressFromValBondedIndexKey(iterator.Key()) toKickOut[string(ownerAddr)] = 0 // set anything } iterator.Close() @@ -465,8 +467,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) type k.SetPool(ctx, pool) // save the now unbonded validator record - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) + k.SetValidator(ctx, validator) // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero()) @@ -493,9 +494,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. k.SetPool(ctx, pool) // save the now bonded validator record to the three referenced stores - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + k.SetValidator(ctx, validator) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 807c16012..f29fba7eb 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -675,3 +675,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/stake.go b/x/stake/stake.go index 5e4356b81..869b838ff 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -7,30 +7,29 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// keeper -type Keeper = keeper.Keeper - -var NewKeeper = keeper.NewKeeper - -// types -type Validator = types.Validator -type Description = types.Description -type Delegation = types.Delegation -type UnbondingDelegation = types.UnbondingDelegation -type Redelegation = types.Redelegation -type Params = types.Params -type Pool = types.Pool -type PoolShares = types.PoolShares -type MsgCreateValidator = types.MsgCreateValidator -type MsgEditValidator = types.MsgEditValidator -type MsgDelegate = types.MsgDelegate -type MsgBeginUnbonding = types.MsgBeginUnbonding -type MsgCompleteUnbonding = types.MsgCompleteUnbonding -type MsgBeginRedelegate = types.MsgBeginRedelegate -type MsgCompleteRedelegate = types.MsgCompleteRedelegate -type GenesisState = types.GenesisState +type ( + Keeper = keeper.Keeper + Validator = types.Validator + Description = types.Description + Delegation = types.Delegation + UnbondingDelegation = types.UnbondingDelegation + Redelegation = types.Redelegation + Params = types.Params + Pool = types.Pool + PoolShares = types.PoolShares + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgBeginUnbonding = types.MsgBeginUnbonding + MsgCompleteUnbonding = types.MsgCompleteUnbonding + MsgBeginRedelegate = types.MsgBeginRedelegate + MsgCompleteRedelegate = types.MsgCompleteRedelegate + GenesisState = types.GenesisState +) var ( + NewKeeper = keeper.NewKeeper + GetValidatorKey = keeper.GetValidatorKey GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey @@ -72,7 +71,6 @@ var ( DefaultGenesisState = types.DefaultGenesisState RegisterWire = types.RegisterWire - // messages NewMsgCreateValidator = types.NewMsgCreateValidator NewMsgEditValidator = types.NewMsgEditValidator NewMsgDelegate = types.NewMsgDelegate @@ -82,7 +80,6 @@ var ( NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate ) -// errors const ( DefaultCodespace = types.DefaultCodespace CodeInvalidValidator = types.CodeInvalidValidator @@ -126,7 +123,6 @@ var ( ErrMissingSignature = types.ErrMissingSignature ) -// tags var ( ActionCreateValidator = tags.ActionCreateValidator ActionEditValidator = tags.ActionEditValidator diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index eb38a50a7..f7d31a640 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -2,9 +2,11 @@ package types import ( "bytes" + "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" ) // Delegation represents the bond with tokens held by an account. It is @@ -17,7 +19,54 @@ type Delegation struct { Height int64 `json:"height"` // Last height bond updated } -// two are equal +type delegationValue struct { + Shares sdk.Rat + Height int64 +} + +// return the delegation without fields contained within the key for the store +func MustMarshalDelegation(cdc *wire.Codec, delegation Delegation) []byte { + val := delegationValue{ + delegation.Shares, + delegation.Height, + } + return cdc.MustMarshalBinary(val) +} + +// return the delegation without fields contained within the key for the store +func MustUnmarshalDelegation(cdc *wire.Codec, key, value []byte) Delegation { + delegation, err := UnmarshalDelegation(cdc, key, value) + if err != nil { + panic(err) + } + return delegation +} + +// return the delegation without fields contained within the key for the store +func UnmarshalDelegation(cdc *wire.Codec, key, value []byte) (delegation Delegation, err error) { + var storeValue delegationValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.Address(addrs[:sdk.AddrLen]) + valAddr := sdk.Address(addrs[sdk.AddrLen:]) + + return Delegation{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + Shares: storeValue.Shares, + Height: storeValue.Height, + }, nil +} + +// nolint func (d Delegation) Equal(d2 Delegation) bool { return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && @@ -33,16 +82,20 @@ func (d Delegation) GetDelegator() sdk.Address { return d.DelegatorAddr } func (d Delegation) GetValidator() sdk.Address { return d.ValidatorAddr } func (d Delegation) GetBondShares() sdk.Rat { return d.Shares } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// Delegation. An error is returned if the Delegation's delegator or validator +// addresses cannot be Bech32 encoded. func (d Delegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) if err != nil { return "", err } + resp := "Delegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Validator: %s\n", bechVal) @@ -50,12 +103,9 @@ func (d Delegation) HumanReadableString() (string, error) { resp += fmt.Sprintf("Height: %d", d.Height) return resp, nil - } -//__________________________________________________________________ - -// element stored to represent the passive unbonding queue +// UnbondingDelegation reflects a delegation's passive unbonding queue. type UnbondingDelegation struct { DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator ValidatorAddr sdk.Address `json:"validator_addr"` // validator unbonding from owner addr @@ -65,6 +115,59 @@ type UnbondingDelegation struct { Balance sdk.Coin `json:"balance"` // atoms to receive at completion } +type ubdValue struct { + CreationHeight int64 + MinTime int64 + InitialBalance sdk.Coin + Balance sdk.Coin +} + +// return the unbonding delegation without fields contained within the key for the store +func MustMarshalUBD(cdc *wire.Codec, ubd UnbondingDelegation) []byte { + val := ubdValue{ + ubd.CreationHeight, + ubd.MinTime, + ubd.InitialBalance, + ubd.Balance, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a unbonding delegation from a store key and value +func MustUnmarshalUBD(cdc *wire.Codec, key, value []byte) UnbondingDelegation { + ubd, err := UnmarshalUBD(cdc, key, value) + if err != nil { + panic(err) + } + return ubd +} + +// unmarshal a unbonding delegation from a store key and value +func UnmarshalUBD(cdc *wire.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { + var storeValue ubdValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 2*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.Address(addrs[:sdk.AddrLen]) + valAddr := sdk.Address(addrs[sdk.AddrLen:]) + + return UnbondingDelegation{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + CreationHeight: storeValue.CreationHeight, + MinTime: storeValue.MinTime, + InitialBalance: storeValue.InitialBalance, + Balance: storeValue.Balance, + }, nil +} + // nolint func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { bz1 := MsgCdc.MustMarshalBinary(&d) @@ -72,16 +175,20 @@ func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { return bytes.Equal(bz1, bz2) } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of an +// UnbondingDelegation. An error is returned if the UnbondingDelegation's +// delegator or validator addresses cannot be Bech32 encoded. func (d UnbondingDelegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) if err != nil { return "", err } + resp := "Unbonding Delegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Validator: %s\n", bechVal) @@ -93,9 +200,7 @@ func (d UnbondingDelegation) HumanReadableString() (string, error) { } -//__________________________________________________________________ - -// element stored to represent the passive redelegation queue +// Redelegation reflects a delegation's passive re-delegation queue. type Redelegation struct { DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` // validator redelegation source owner addr @@ -108,6 +213,67 @@ type Redelegation struct { SharesDst sdk.Rat `json:"shares_dst"` // amount of destination shares redelegating } +type redValue struct { + CreationHeight int64 + MinTime int64 + InitialBalance sdk.Coin + Balance sdk.Coin + SharesSrc sdk.Rat + SharesDst sdk.Rat +} + +// return the redelegation without fields contained within the key for the store +func MustMarshalRED(cdc *wire.Codec, red Redelegation) []byte { + val := redValue{ + red.CreationHeight, + red.MinTime, + red.InitialBalance, + red.Balance, + red.SharesSrc, + red.SharesDst, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a redelegation from a store key and value +func MustUnmarshalRED(cdc *wire.Codec, key, value []byte) Redelegation { + red, err := UnmarshalRED(cdc, key, value) + if err != nil { + panic(err) + } + return red +} + +// unmarshal a redelegation from a store key and value +func UnmarshalRED(cdc *wire.Codec, key, value []byte) (red Redelegation, err error) { + var storeValue redValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + addrs := key[1:] // remove prefix bytes + if len(addrs) != 3*sdk.AddrLen { + err = errors.New("unexpected key length") + return + } + delAddr := sdk.Address(addrs[:sdk.AddrLen]) + valSrcAddr := sdk.Address(addrs[sdk.AddrLen : 2*sdk.AddrLen]) + valDstAddr := sdk.Address(addrs[2*sdk.AddrLen:]) + + return Redelegation{ + DelegatorAddr: delAddr, + ValidatorSrcAddr: valSrcAddr, + ValidatorDstAddr: valDstAddr, + CreationHeight: storeValue.CreationHeight, + MinTime: storeValue.MinTime, + InitialBalance: storeValue.InitialBalance, + Balance: storeValue.Balance, + SharesSrc: storeValue.SharesSrc, + SharesDst: storeValue.SharesDst, + }, nil +} + // nolint func (d Redelegation) Equal(d2 Redelegation) bool { bz1 := MsgCdc.MustMarshalBinary(&d) @@ -115,20 +281,25 @@ func (d Redelegation) Equal(d2 Redelegation) bool { return bytes.Equal(bz1, bz2) } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// Redelegation. An error is returned if the UnbondingDelegation's delegator or +// validator addresses cannot be Bech32 encoded. func (d Redelegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechValSrc, err := sdk.Bech32ifyAcc(d.ValidatorSrcAddr) if err != nil { return "", err } + bechValDst, err := sdk.Bech32ifyAcc(d.ValidatorDstAddr) if err != nil { return "", err } + resp := "Redelegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Source Validator: %s\n", bechValSrc) diff --git a/x/stake/types/delegation_test.go b/x/stake/types/delegation_test.go new file mode 100644 index 000000000..640f5d979 --- /dev/null +++ b/x/stake/types/delegation_test.go @@ -0,0 +1,116 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestDelegationEqual(t *testing.T) { + d1 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + d2 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + ok := d1.Equal(d2) + require.True(t, ok) + + d2.ValidatorAddr = addr3 + d2.Shares = sdk.NewRat(200) + + ok = d1.Equal(d2) + require.False(t, ok) +} + +func TestDelegationHumanReadableString(t *testing.T) { + d := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := d.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestUnbondingDelegationEqual(t *testing.T) { + ud1 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + ud2 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + ok := ud1.Equal(ud2) + require.True(t, ok) + + ud2.ValidatorAddr = addr3 + ud2.MinTime = 20 * 20 * 2 + + ok = ud1.Equal(ud2) + require.False(t, ok) +} + +func TestUnbondingDelegationHumanReadableString(t *testing.T) { + ud := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := ud.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestRedelegationEqual(t *testing.T) { + r1 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + r2 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + + ok := r1.Equal(r2) + require.True(t, ok) + + r2.SharesDst = sdk.NewRat(10) + r2.SharesSrc = sdk.NewRat(20) + r2.MinTime = 20 * 20 * 2 + + ok = r1.Equal(r2) + require.False(t, ok) +} + +func TestRedelegationHumanReadableString(t *testing.T) { + r := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + SharesDst: sdk.NewRat(10), + SharesSrc: sdk.NewRat(20), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := r.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 2914741f4..09ed2d369 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -25,97 +25,118 @@ const ( func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") } + func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") } + func ErrValidatorAlreadyExists(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator") } + func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") } + func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator") } + func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error { msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max) return sdk.NewError(codespace, CodeInvalidValidator, msg) } + func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive") } + func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") } -// delegation func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") } + func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") } + func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0") } + func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair") } + func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address") } + func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation") } + func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares") } + func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator") } + func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares)) } + func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0") } + func ErrBadSharesPrecision(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("shares denominator must be < %s, try reducing the number of decimal points", maximumBondingRationalDenominator.String()), ) } + func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1") } -// redelegation func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error { msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v", operation, descriptor, got, min) return sdk.NewError(codespace, CodeUnauthorized, msg) } + func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") } + func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") } + func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") } + func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") } -// messages func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") } + func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided") } + func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") } diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index 878c1ba17..aa9eea8cb 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 diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index f3d8bc686..1a854a492 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -10,9 +10,9 @@ import ( ) var ( - coinPos = sdk.Coin{"steak", sdk.NewInt(1000)} - coinZero = sdk.Coin{"steak", sdk.NewInt(0)} - coinNeg = sdk.Coin{"steak", sdk.NewInt(-10000)} + coinPos = sdk.NewCoin("steak", 1000) + coinZero = sdk.NewCoin("steak", 0) + coinNeg = sdk.NewCoin("steak", -10000) ) // test ValidateBasic for MsgCreateValidator @@ -197,29 +197,3 @@ func TestMsgCompleteUnbonding(t *testing.T) { } } } - -// TODO introduce with go-amino -//func TestSerializeMsg(t *testing.T) { - -//// make sure all types construct properly -//bondAmt := 1234321 -//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - -//tests := []struct { -//tx sdk.Msg -//}{ -//{NewMsgCreateValidator(addr1, pk1, bond, Description{})}, -//{NewMsgEditValidator(addr1, Description{})}, -//{NewMsgDelegate(addr1, addr2, bond)}, -//{NewMsgUnbond(addr1, addr2, strconv.Itoa(bondAmt))}, -//} - -//for i, tc := range tests { -//var tx sdk.Tx -//bs := wire.BinaryBytes(tc.tx) -//err := wire.ReadBinaryBytes(bs, &tx) -//if require.NoError(t, err, "%d", i) { -//require.Equal(t, tc.tx, tx, "%d", i) -//} -//} -//} diff --git a/x/stake/types/params.go b/x/stake/types/params.go index 8c1b897f2..5a7dd6ef5 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,6 +6,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// defaultUnbondingTime reflects three weeks in seconds as the default +// unbonding time. +const defaultUnbondingTime int64 = 60 * 60 * 24 * 3 + // Params defines the high level settings for staking type Params struct { InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate @@ -19,21 +23,21 @@ type Params struct { BondDenom string `json:"bond_denom"` // bondable coin denomination } -// nolint +// Equal returns a boolean determining if two Param types are identical. func (p Params) Equal(p2 Params) bool { bz1 := MsgCdc.MustMarshalBinary(&p) bz2 := MsgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } -// default params +// DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), - UnbondingTime: 60 * 60 * 24 * 3, // 3 weeks in seconds + UnbondingTime: defaultUnbondingTime, MaxValidators: 100, BondDenom: "steak", } diff --git a/x/stake/types/params_test.go b/x/stake/types/params_test.go new file mode 100644 index 000000000..c18700ef4 --- /dev/null +++ b/x/stake/types/params_test.go @@ -0,0 +1,21 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsEqual(t *testing.T) { + p1 := DefaultParams() + p2 := DefaultParams() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.UnbondingTime = 60 * 60 * 24 * 2 + p2.BondDenom = "soup" + + ok = p1.Equal(p2) + require.False(t, ok) +} diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go index a62c69d46..3a52646f6 100644 --- a/x/stake/types/pool_test.go +++ b/x/stake/types/pool_test.go @@ -3,11 +3,24 @@ package types import ( "testing" - "github.com/stretchr/testify/require" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) +func TestPoolEqual(t *testing.T) { + p1 := InitialPool() + p2 := InitialPool() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.BondedTokens = 3 + p2.BondedShares = sdk.NewRat(10) + + ok = p1.Equal(p2) + require.False(t, ok) +} + func TestBondedRatio(t *testing.T) { pool := InitialPool() pool.LooseTokens = 1 @@ -62,10 +75,10 @@ func TestUnbondedShareExRate(t *testing.T) { } func TestAddTokensBonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensBonded(10) require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) @@ -78,10 +91,10 @@ func TestAddTokensBonded(t *testing.T) { } func TestRemoveSharesBonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) @@ -94,10 +107,10 @@ func TestRemoveSharesBonded(t *testing.T) { } func TestAddTokensUnbonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensUnbonded(10) require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) @@ -110,11 +123,11 @@ func TestAddTokensUnbonded(t *testing.T) { } func TestRemoveSharesUnbonded(t *testing.T) { - poolA := InitialPool() poolA.UnbondedTokens = 10 poolA.UnbondedShares = sdk.NewRat(10) require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) diff --git a/x/stake/types/shares.go b/x/stake/types/shares.go index 09301d0db..5a2cb2be6 100644 --- a/x/stake/types/shares.go +++ b/x/stake/types/shares.go @@ -4,18 +4,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// pool shares held by a validator +// PoolShares reflects the shares of a validator in a pool. type PoolShares struct { Status sdk.BondStatus `json:"status"` - Amount sdk.Rat `json:"amount"` // total shares of type ShareKind + Amount sdk.Rat `json:"amount"` } -// only the vitals - does not check bond height of IntraTxCounter +// Equal returns a boolean determining of two PoolShares are identical. func (s PoolShares) Equal(s2 PoolShares) bool { return s.Status == s2.Status && s.Amount.Equal(s2.Amount) } +// NewUnbondedShares returns a new PoolShares with a specified unbonded amount. func NewUnbondedShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Unbonded, @@ -23,6 +24,8 @@ func NewUnbondedShares(amount sdk.Rat) PoolShares { } } +// NewUnbondingShares returns a new PoolShares with a specified unbonding +// amount. func NewUnbondingShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Unbonding, @@ -30,6 +33,7 @@ func NewUnbondingShares(amount sdk.Rat) PoolShares { } } +// NewBondedShares returns a new PoolSahres with a specified bonding amount. func NewBondedShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Bonded, @@ -37,9 +41,7 @@ func NewBondedShares(amount sdk.Rat) PoolShares { } } -//_________________________________________________________________________________________________________ - -// amount of unbonded shares +// Unbonded returns the amount of unbonded shares. func (s PoolShares) Unbonded() sdk.Rat { if s.Status == sdk.Unbonded { return s.Amount @@ -47,7 +49,7 @@ func (s PoolShares) Unbonded() sdk.Rat { return sdk.ZeroRat() } -// amount of unbonding shares +// Unbonding returns the amount of unbonding shares. func (s PoolShares) Unbonding() sdk.Rat { if s.Status == sdk.Unbonding { return s.Amount @@ -55,7 +57,7 @@ func (s PoolShares) Unbonding() sdk.Rat { return sdk.ZeroRat() } -// amount of bonded shares +// Bonded returns amount of bonded shares. func (s PoolShares) Bonded() sdk.Rat { if s.Status == sdk.Bonded { return s.Amount @@ -63,64 +65,80 @@ func (s PoolShares) Bonded() sdk.Rat { return sdk.ZeroRat() } -//_________________________________________________________________________________________________________ - -// equivalent amount of shares if the shares were unbonded +// ToUnbonded returns the equivalent amount of pool shares if the shares were +// unbonded. func (s PoolShares) ToUnbonded(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: - exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr + // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) + // bondedshr*unbondedshr/bondedshr = unbondedshr + amount = s.Amount.Mul(exRate) case sdk.Unbonding: - exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr - amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr + // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr + exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) + // unbondingshr*unbondedshr/unbondingshr = unbondedshr + amount = s.Amount.Mul(exRate) case sdk.Unbonded: amount = s.Amount } + return NewUnbondedShares(amount) } -// equivalent amount of shares if the shares were unbonding +// ToUnbonding returns the equivalent amount of pool shares if the shares were +// unbonding. func (s PoolShares) ToUnbonding(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: - exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr + // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) + // bondedshr*unbondingshr/bondedshr = unbondingshr + amount = s.Amount.Mul(exRate) case sdk.Unbonding: amount = s.Amount case sdk.Unbonded: - exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr - amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr + // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) + // unbondedshr*unbondingshr/unbondedshr = unbondingshr + amount = s.Amount.Mul(exRate) } + return NewUnbondingShares(amount) } -// equivalent amount of shares if the shares were bonded +// ToBonded the equivalent amount of pool shares if the shares were bonded. func (s PoolShares) ToBonded(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: amount = s.Amount case sdk.Unbonding: - exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) case sdk.Unbonded: - exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) } + return NewUnbondedShares(amount) } -//_________________________________________________________________________________________________________ - -// TODO better tests -// get the equivalent amount of tokens contained by the shares +// Tokens returns the equivalent amount of tokens contained by the pool shares +// for a given pool. func (s PoolShares) Tokens(p Pool) sdk.Rat { switch s.Status { case sdk.Bonded: - return p.BondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + return p.BondedShareExRate().Mul(s.Amount) case sdk.Unbonding: return p.UnbondingShareExRate().Mul(s.Amount) case sdk.Unbonded: diff --git a/x/stake/types/shares_test.go b/x/stake/types/shares_test.go new file mode 100644 index 000000000..8a374606c --- /dev/null +++ b/x/stake/types/shares_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestPoolSharesTokens(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + poolShares := NewBondedShares(sdk.NewRat(50)) + tokens := poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondingShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondedShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) +} diff --git a/x/stake/types/test_common.go b/x/stake/types/test_utils.go similarity index 84% rename from x/stake/types/test_common.go rename to x/stake/types/test_utils.go index 12f11f864..7912b7fa8 100644 --- a/x/stake/types/test_common.go +++ b/x/stake/types/test_utils.go @@ -11,7 +11,6 @@ import ( ) var ( - // dummy pubkeys/addresses pk1 = crypto.GenPrivKeyEd25519().PubKey() pk2 = crypto.GenPrivKeyEd25519().PubKey() pk3 = crypto.GenPrivKeyEd25519().PubKey() @@ -23,18 +22,20 @@ var ( emptyPubkey crypto.PubKey ) -//______________________________________________________________ - -// any operation that transforms staking state -// takes in RNG instance, pool, validator -// returns updated pool, updated validator, delta tokens, descriptive message +// Operation reflects any operation that transforms staking state. It takes in +// a RNG instance, pool, validator and returns an updated pool, updated +// validator, delta tokens, and descriptive message. type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) -// operation: bond or unbond a validator depending on current status +// OpBondOrUnbond implements an operation that bonds or unbonds a validator +// depending on current status. // nolint: unparam func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - var msg string - var newStatus sdk.BondStatus + var ( + msg string + newStatus sdk.BondStatus + ) + if val.Status() == sdk.Bonded { msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) @@ -45,21 +46,27 @@ func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, in val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) newStatus = sdk.Bonded } + val, pool = val.UpdateStatus(pool, newStatus) return pool, val, 0, msg } -// operation: add a random number of tokens to a validator +// OpAddTokens implements an operation that adds a random number of tokens to a +// validator. func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - tokens := int64(r.Int31n(1000)) msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + + tokens := int64(r.Int31n(1000)) val, pool, _ = val.AddTokensFromDel(pool, tokens) msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative + + // Tokens are removed so for accounting must be negative + return pool, val, -1 * tokens, msg } -// operation: remove a random number of shares from a validator +// OpRemoveShares implements an operation that removes a random number of +// shares from a validator. func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { var shares sdk.Rat for { @@ -76,7 +83,7 @@ func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, in return pool, val, tokens, msg } -// pick a random staking operation +// RandomOperation returns a random staking operation. func RandomOperation(r *rand.Rand) Operation { operations := []Operation{ OpBondOrUnbond, @@ -86,10 +93,11 @@ func RandomOperation(r *rand.Rand) Operation { r.Shuffle(len(operations), func(i, j int) { operations[i], operations[j] = operations[j], operations[i] }) + return operations[0] } -// ensure invariants that should always be true are true +// AssertInvariants ensures invariants that should always be true are true. // nolint: unparam func AssertInvariants(t *testing.T, msg string, pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) { @@ -105,29 +113,28 @@ func AssertInvariants(t *testing.T, msg string, pOrig.UnbondedTokens, pOrig.BondedTokens, pMod.UnbondedTokens, pMod.BondedTokens, tokens) - // nonnegative bonded shares + // Nonnegative bonded shares require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", msg, pOrig, pMod, tokens) - // nonnegative unbonded shares + // Nonnegative unbonded shares require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", msg, pOrig, pMod, tokens) - // nonnegative bonded ex rate + // Nonnegative bonded ex rate require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative BondedShareExRate: %d", msg, pMod.BondedShareExRate().RoundInt64()) - // nonnegative unbonded ex rate + // Nonnegative unbonded ex rate require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d", msg, pMod.UnbondedShareExRate().RoundInt64()) for _, vMod := range vMods { - - // nonnegative ex rate + // Nonnegative ex rate require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", msg, @@ -135,7 +142,7 @@ func AssertInvariants(t *testing.T, msg string, vMod.Owner, ) - // nonnegative poolShares + // Nonnegative poolShares require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", msg, @@ -145,7 +152,7 @@ func AssertInvariants(t *testing.T, msg string, vMod.Owner, ) - // nonnegative delShares + // Nonnegative delShares require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", msg, @@ -154,27 +161,25 @@ func AssertInvariants(t *testing.T, msg string, vMod.DelegatorShareExRate(pMod), vMod.Owner, ) - } - } -//________________________________________________________________________________ -// TODO refactor this random setup +// TODO: refactor this random setup -// generate a random validator +// randomValidator generates a random validator. // nolint: unparam func randomValidator(r *rand.Rand, i int) Validator { - poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) delShares := sdk.NewRat(int64(r.Int31n(10000))) var pShares PoolShares + if r.Float64() < float64(0.5) { pShares = NewBondedShares(poolSharesAmt) } else { pShares = NewUnbondedShares(poolSharesAmt) } + return Validator{ Owner: addr1, PubKey: pk1, @@ -183,7 +188,7 @@ func randomValidator(r *rand.Rand, i int) Validator { } } -// generate a random staking state +// RandomSetup generates a random staking state. func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { pool := InitialPool() pool.LooseTokens = 100000 @@ -191,6 +196,7 @@ func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { validators := make([]Validator, numValidators) for i := 0; i < numValidators; i++ { validator := randomValidator(r, i) + if validator.Status() == sdk.Bonded { pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) pool.BondedTokens += validator.PoolShares.Bonded().RoundInt64() @@ -198,7 +204,9 @@ func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) pool.UnbondedTokens += validator.PoolShares.Unbonded().RoundInt64() } + validators[i] = validator } + return pool, validators } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index c2c19439b..480416d01 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "errors" "fmt" abci "github.com/tendermint/tendermint/abci/types" @@ -9,8 +10,11 @@ import ( tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" ) +const doNotModifyDescVal = "[do-not-modify]" + // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this @@ -60,6 +64,84 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti } } +// what's kept in the store value +type validatorValue struct { + PubKey crypto.PubKey + Revoked bool + PoolShares PoolShares + DelegatorShares sdk.Rat + Description Description + BondHeight int64 + BondIntraTxCounter int16 + ProposerRewardPool sdk.Coins + Commission sdk.Rat + CommissionMax sdk.Rat + CommissionChangeRate sdk.Rat + CommissionChangeToday sdk.Rat + PrevBondedShares sdk.Rat +} + +// return the redelegation without fields contained within the key for the store +func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte { + val := validatorValue{ + PubKey: validator.PubKey, + Revoked: validator.Revoked, + PoolShares: validator.PoolShares, + DelegatorShares: validator.DelegatorShares, + Description: validator.Description, + BondHeight: validator.BondHeight, + BondIntraTxCounter: validator.BondIntraTxCounter, + ProposerRewardPool: validator.ProposerRewardPool, + Commission: validator.Commission, + CommissionMax: validator.CommissionMax, + CommissionChangeRate: validator.CommissionChangeRate, + CommissionChangeToday: validator.CommissionChangeToday, + PrevBondedShares: validator.PrevBondedShares, + } + return cdc.MustMarshalBinary(val) +} + +// unmarshal a redelegation from a store key and value +func MustUnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) Validator { + validator, err := UnmarshalValidator(cdc, ownerAddr, value) + if err != nil { + panic(err) + } + + return validator +} + +// unmarshal a redelegation from a store key and value +func UnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) (validator Validator, err error) { + var storeValue validatorValue + err = cdc.UnmarshalBinary(value, &storeValue) + if err != nil { + return + } + + if len(ownerAddr) != 20 { + err = errors.New("unexpected address length") + return + } + + return Validator{ + Owner: ownerAddr, + PubKey: storeValue.PubKey, + Revoked: storeValue.Revoked, + PoolShares: storeValue.PoolShares, + DelegatorShares: storeValue.DelegatorShares, + Description: storeValue.Description, + BondHeight: storeValue.BondHeight, + BondIntraTxCounter: storeValue.BondIntraTxCounter, + ProposerRewardPool: storeValue.ProposerRewardPool, + Commission: storeValue.Commission, + CommissionMax: storeValue.CommissionMax, + CommissionChangeRate: storeValue.CommissionChangeRate, + CommissionChangeToday: storeValue.CommissionChangeToday, + PrevBondedShares: storeValue.PrevBondedShares, + }, nil +} + // only the vitals - does not check bond height of IntraTxCounter func (v Validator) Equal(c2 Validator) bool { return v.PubKey.Equals(c2.PubKey) && @@ -67,8 +149,6 @@ func (v Validator) Equal(c2 Validator) bool { v.PoolShares.Equal(c2.PoolShares) && v.DelegatorShares.Equal(c2.DelegatorShares) && v.Description == c2.Description && - //v.BondHeight == c2.BondHeight && - //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && v.Commission.Equal(c2.Commission) && v.CommissionMax.Equal(c2.CommissionMax) && @@ -85,6 +165,7 @@ type Description struct { Details string `json:"details"` // optional details } +// NewDescription returns a new Description with the provided values. func NewDescription(moniker, identity, website, details string) Description { return Description{ Moniker: moniker, @@ -94,20 +175,22 @@ func NewDescription(moniker, identity, website, details string) Description { } } -// update the description based on input +// UpdateDescription updates the fields of a given description. An error is +// returned if the resulting description contains an invalid length. func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { - if d.Moniker == "[do-not-modify]" { + if d.Moniker == doNotModifyDescVal { d2.Moniker = d.Moniker } - if d.Identity == "[do-not-modify]" { + if d.Identity == doNotModifyDescVal { d2.Identity = d.Identity } - if d.Website == "[do-not-modify]" { + if d.Website == doNotModifyDescVal { d2.Website = d.Website } - if d.Details == "[do-not-modify]" { + if d.Details == doNotModifyDescVal { d2.Details = d.Details } + return Description{ Moniker: d2.Moniker, Identity: d2.Identity, @@ -116,7 +199,7 @@ func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) }.EnsureLength() } -// ensure the length of the description +// EnsureLength ensures the length of a validator's description. func (d Description) EnsureLength() (Description, sdk.Error) { if len(d.Moniker) > 70 { return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) @@ -130,10 +213,11 @@ func (d Description) EnsureLength() (Description, sdk.Error) { if len(d.Details) > 280 { return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) } + return d, nil } -// abci validator from stake validator type +// ABCIValidator returns an abci.Validator from a staked validator type. func (v Validator) ABCIValidator() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), @@ -141,8 +225,8 @@ func (v Validator) ABCIValidator() abci.Validator { } } -// abci validator from stake validator type -// with zero power used for validator updates +// ABCIValidatorZero returns an abci.Validator from a staked validator type +// with with zero power used for validator updates. func (v Validator) ABCIValidatorZero() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), @@ -150,12 +234,13 @@ func (v Validator) ABCIValidatorZero() abci.Validator { } } -// abci validator from stake validator type +// Status returns the validator's bond status inferred from the pool shares. func (v Validator) Status() sdk.BondStatus { return v.PoolShares.Status } -// update the location of the shares within a validator if its bond status has changed +// UpdateStatus updates the location of the shares within a validator if it's +// bond status has changed. func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { var tokens int64 @@ -173,7 +258,8 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) case sdk.Bonded: - if NewStatus == sdk.Bonded { // return if nothing needs switching + if NewStatus == sdk.Bonded { + // Return if nothing needs switching return v, pool } pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) @@ -187,14 +273,16 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, case sdk.Bonded: pool, v.PoolShares = pool.addTokensBonded(tokens) } + return v, pool } -// Remove pool shares -// Returns corresponding tokens, which could be burned (e.g. when slashing -// a validator) or redistributed elsewhere +// RemovePoolShares removes pool shares from a validator. It returns +// corresponding tokens, which could be burned (e.g. when slashing a validator) +// or redistributed elsewhere. func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { var tokens int64 + switch v.Status() { case sdk.Unbonded: pool, tokens = pool.removeSharesUnbonded(poolShares) @@ -203,29 +291,33 @@ func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, P case sdk.Bonded: pool, tokens = pool.removeSharesBonded(poolShares) } + v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) return v, pool, tokens } -// TODO remove should only be tokens -// get the power or potential power for a validator -// if bonded, the power is the BondedShares -// if not bonded, the power is the amount of bonded shares which the -// the validator would have it was bonded +// EquivalentBondedShares ... +// +// TODO: Remove should only be tokens get the power or potential power for a +// validator if bonded, the power is the BondedShares if not bonded, the power +// is the amount of bonded shares which the the validator would have it was +// bonded. func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { return v.PoolShares.ToBonded(pool).Amount } //_________________________________________________________________________________________________________ -// add tokens to a validator -func (v Validator) AddTokensFromDel(pool Pool, - amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { +// AddTokensFromDel adds tokens to a validator +func (v Validator) AddTokensFromDel(pool Pool, amount int64) (Validator, Pool, sdk.Rat) { + var ( + poolShares PoolShares + equivalentBondedShares sdk.Rat + ) - exRate := v.DelegatorShareExRate(pool) // bshr/delshr + // bondedShare/delegatedShare + exRate := v.DelegatorShareExRate(pool) - var poolShares PoolShares - var equivalentBondedShares sdk.Rat switch v.Status() { case sdk.Unbonded: pool, poolShares = pool.addTokensUnbonded(amount) @@ -237,21 +329,24 @@ func (v Validator) AddTokensFromDel(pool Pool, v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) equivalentBondedShares = poolShares.ToBonded(pool).Amount - issuedDelegatorShares = equivalentBondedShares.Quo(exRate) // bshr/(bshr/delshr) = delshr + // bondedShare/(bondedShare/delegatedShare) = delegatedShare + issuedDelegatorShares := equivalentBondedShares.Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) return v, pool, issuedDelegatorShares } -// remove delegator shares from a validator -// NOTE this function assumes the shares have already been updated for the validator status -func (v Validator) RemoveDelShares(pool Pool, - delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { - +// RemoveDelShares removes delegator shares from a validator. +// +// NOTE: This function assumes the shares have already been updated for the +// validator status. +func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (Validator, Pool, int64) { amount := v.DelegatorShareExRate(pool).Mul(delShares) eqBondedSharesToRemove := NewBondedShares(amount) v.DelegatorShares = v.DelegatorShares.Sub(delShares) + var createdCoins int64 + switch v.Status() { case sdk.Unbonded: unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount @@ -265,15 +360,17 @@ func (v Validator) RemoveDelShares(pool Pool, pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) } + return v, pool, createdCoins } -// get the exchange rate of tokens over delegator shares +// DelegatorShareExRate gets the exchange rate of tokens over delegator shares. // UNITS: eq-val-bonded-shares/delegator-shares func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { if v.DelegatorShares.IsZero() { return sdk.OneRat() } + eqBondedShares := v.PoolShares.ToBonded(pool).Amount return eqBondedShares.Quo(v.DelegatorShares) } @@ -293,16 +390,20 @@ func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares } func (v Validator) GetBondHeight() int64 { return v.BondHeight } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// validator. An error is returned if the owner or the owner's public key +// cannot be converted to Bech32 format. func (v Validator) HumanReadableString() (string, error) { bechOwner, err := sdk.Bech32ifyAcc(v.Owner) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyValPub(v.PubKey) if err != nil { return "", err } + resp := "Validator \n" resp += fmt.Sprintf("Owner: %s\n", bechOwner) resp += fmt.Sprintf("Validator: %s\n", bechVal) diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 4fcfa6e17..f978a6d6e 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -5,12 +5,89 @@ import ( "math/rand" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) +func TestValidatorEqual(t *testing.T) { + val1 := NewValidator(addr1, pk1, Description{}) + val2 := NewValidator(addr1, pk1, Description{}) + + ok := val1.Equal(val2) + require.True(t, ok) + + val2 = NewValidator(addr2, pk2, Description{}) + + ok = val1.Equal(val2) + require.False(t, ok) +} + +func TestUpdateDescription(t *testing.T) { + d1 := Description{ + Moniker: doNotModifyDescVal, + Identity: doNotModifyDescVal, + Website: doNotModifyDescVal, + Details: doNotModifyDescVal, + } + d2 := Description{ + Website: "https://validator.cosmos", + Details: "Test validator", + } + + d, err := d1.UpdateDescription(d2) + require.Nil(t, err) + require.Equal(t, d, d1) +} + +func TestABCIValidator(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidator() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, val.PoolShares.Bonded().RoundInt64(), abciVal.Power) +} + +func TestABCIValidatorZero(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidatorZero() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, int64(0), abciVal.Power) +} + +func TestRemovePoolShares(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, tk := val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(90), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(90), pool.BondedTokens) + require.Equal(t, int64(90), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(20), pool.LooseTokens) + require.Equal(t, int64(10), tk) + + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, tk = val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(80), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens) + require.Equal(t, int64(0), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(30), pool.LooseTokens) + require.Equal(t, int64(10), tk) +} + func TestAddTokensValidatorBonded(t *testing.T) { pool := InitialPool() pool.LooseTokens = 10 @@ -230,3 +307,13 @@ func TestMultiValidatorIntegrationInvariants(t *testing.T) { } } } + +func TestHumanReadableString(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := val.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +}