diff --git a/.circleci/config.yml b/.circleci/config.yml index cd930d52b..ceb9e78a8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,6 +85,22 @@ jobs: export PATH="$GOBIN:$PATH" make test_unit + test_cli: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - restore_cache: + key: v1-pkg-cache + - restore_cache: + key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: + name: Test cli + command: | + export PATH="$GOBIN:$PATH" + make test_cli + test_cover: <<: *defaults parallelism: 4 @@ -138,6 +154,9 @@ workflows: - lint: requires: - setup_dependencies + - test_cli: + requires: + - setup_dependencies - test_unit: requires: - setup_dependencies diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 902469b41..9d234c326 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,3 +4,5 @@ * [ ] Updated all code comments where relevant * [ ] Wrote tests * [ ] Updated CHANGELOG.md +* [ ] Updated Basecoin / other examples +* [ ] Squashed related commits and prefixed with PR number per [coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr) diff --git a/.gitignore b/.gitignore index b0684f207..494e72452 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,11 @@ baseapp/data/* coverage.txt profile.out -### Vagrant ### +# Vagrant .vagrant/ *.box *.log vagrant + +# Graphviz +dependency-graph.png \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 38294120c..6d6541691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,18 @@ # Changelog -## 0.18.1 - BREAKING CHANGES -* [x/auth] move stuff specific to auth anteHandler to the auth module rather than the types folder. This includes: - * StdTx (and its related stuff i.e. StdSignDoc, etc) - * StdFee - * StdSignature - * Account interface - * Related to this organization, I also: -* [x/auth] got rid of AccountMapper interface (in favor of the struct already in auth module) -* [x/auth] removed the FeeHandler function from the AnteHandler, Replaced with FeeKeeper -* [x/auth] Removed GetSignatures() from Tx interface (as different Tx styles might use something different than StdSignature) -* [store] Removed SubspaceIterator and ReverseSubspaceIterator from KVStore interface and replaced them with helper functions in /types -* Switch to bech32cosmos on all human readable inputs and outputs +FEATURES -BUG FIXES +IMPROVEMENTS +* export command now writes current validator set for Tendermint -* auto-sequencing transactions correctly -* query sequence via account store -* fixed duplicate pub_key in stake.Validator +FIXES +* [lcd] Switch to bech32 for addresses on all human readable inputs and outputs ## 0.18.0 -_TBD_ +_2018-06-05_ BREAKING CHANGES @@ -43,6 +31,20 @@ BREAKING CHANGES * Introduction of Unbonding fields, lowlevel logic throughout (not fully implemented with queue) * Introduction of PoolShares type within validators, replaces three rational fields (BondedShares, UnbondingShares, UnbondedShares +* [x/auth] move stuff specific to auth anteHandler to the auth module rather than the types folder. This includes: + * StdTx (and its related stuff i.e. StdSignDoc, etc) + * StdFee + * StdSignature + * Account interface + * Related to this organization, I also: +* [x/auth] got rid of AccountMapper interface (in favor of the struct already in auth module) +* [x/auth] removed the FeeHandler function from the AnteHandler, Replaced with FeeKeeper +* [x/auth] Removed GetSignatures() from Tx interface (as different Tx styles might use something different than StdSignature) +* [store] Removed SubspaceIterator and ReverseSubspaceIterator from KVStore interface and replaced them with helper functions in /types +* [cli] rearranged commands under subcommands +* [stake] remove Tick and add EndBlocker +* Switch to bech32cosmos on all human readable inputs and outputs + FEATURES @@ -56,14 +58,25 @@ FEATURES * [stake] Creation of a validator/delegation generics in `/types` * [stake] Helper Description of the store in x/stake/store.md * [stake] removed use of caches in the stake keeper +* [stake] Added REST API * [Makefile] Added terraform/ansible playbooks to easily create remote testnets on Digital Ocean + BUG FIXES -* Auto-sequencing now works correctly * [stake] staking delegator shares exchange rate now relative to equivalent-bonded-tokens the validator has instead of bonded tokens ^ this is important for unbonded validators in the power store! +* [cli] fixed cli-bash tests +* [ci] added cli-bash tests +* [basecoin] updated basecoin for stake and slashing +* [docs] fixed references to old cli commands * [docs] Downgraded Swagger to v2 for downstream compatibility +* auto-sequencing transactions correctly +* query sequence via account store +* fixed duplicate pub_key in stake.Validator +* Auto-sequencing now works correctly + + ## 0.17.2 @@ -87,7 +100,7 @@ FEATURES * [gaiacli] Support queries for candidates, delegator-bonds * [gaiad] Added `gaiad export` command to export current state to JSON * [x/bank] Tx tags with sender/recipient for indexing & later retrieval -* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy +* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit validator IMPROVEMENTS @@ -103,6 +116,7 @@ BUG FIXES * Auto-sequencing now works correctly + ## 0.16.0 (May 14th, 2018) BREAKING CHANGES @@ -121,7 +135,7 @@ BREAKING CHANGES FEATURES: -* Gaia stake commands include, DeclareCandidacy, EditCandidacy, Delegate, Unbond +* Gaia stake commands include, CreateValidator, EditValidator, Delegate, Unbond * MountStoreWithDB without providing a custom store works. * Repo is now lint compliant / GoMetaLinter with tendermint-lint integrated into CI * Better key output, pubkey go-amino hex bytes now output by default @@ -137,12 +151,14 @@ BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia + ## 0.15.1 (April 29, 2018) IMPROVEMENTS: * Update Tendermint to v0.19.1 (includes many rpc fixes) + ## 0.15.0 (April 29, 2018) NOTE: v0.15.0 is a large breaking change that updates the encoding scheme to use diff --git a/Gopkg.lock b/Gopkg.lock index 9ca4de685..3e7d8c9d9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,13 +11,13 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "1432d294a5b055c297457c25434efbf13384cc46" + revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" [[projects]] branch = "master" - name = "github.com/cosmos/bech32cosmos" - packages = ["go"] - revision = "efca97cd8c0852c44d96dfdcc70565c306eddff0" + name = "github.com/btcsuite/btcutil" + packages = ["bech32"] + revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] name = "github.com/davecgh/go-spew" @@ -256,7 +256,7 @@ "leveldb/table", "leveldb/util" ] - revision = "e6d6b529196422703d54ff5c40e79809ec2020b3" + revision = "5d6fca44a948d2be89a9702de7717f0168403d3d" [[projects]] name = "github.com/tendermint/abci" @@ -267,8 +267,8 @@ "server", "types" ] - revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" - version = "v0.10.3" + revision = "9af8b7a7c87478869f8c280ed9539470b8f470b4" + version = "v0.11.0-rc4" [[projects]] branch = "master" @@ -298,17 +298,14 @@ revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19" version = "v0.6.2" -[[projects]] - name = "github.com/tendermint/go-wire" - packages = ["."] - revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c" - version = "v0.7.3" - [[projects]] name = "github.com/tendermint/iavl" - packages = ["."] - revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787" - version = "v0.7.0" + packages = [ + ".", + "sha256truncated" + ] + revision = "c9206995e8f948e99927f5084a88a7e94ca256da" + version = "v0.8.0-rc0" [[projects]] name = "github.com/tendermint/tendermint" @@ -319,6 +316,9 @@ "consensus", "consensus/types", "evidence", + "libs/events", + "libs/pubsub", + "libs/pubsub/query", "lite", "lite/client", "lite/errors", @@ -347,13 +347,15 @@ "types/priv_validator", "version" ] - revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2" - version = "v0.19.5-rc1" + revision = "b5baab0238c9ec26e3b2d229b0243f9ff9220bdb" + version = "v0.20.0-rc3" [[projects]] + branch = "develop" name = "github.com/tendermint/tmlibs" packages = [ "autofile", + "bech32", "cli", "cli/flags", "clist", @@ -362,11 +364,9 @@ "flowrate", "log", "merkle", - "pubsub", - "pubsub/query" + "merkle/tmhash" ] - revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd" - version = "v0.8.3-rc0" + revision = "44f1bdb0d55cc6527e38d0a7aab406e2580f56a4" [[projects]] branch = "master" @@ -382,7 +382,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "1a580b3eff7814fc9b40602fd35256c63b50f491" + revision = "5ba7f63082460102a45837dbd1827e10f9479ac0" [[projects]] branch = "master" @@ -396,13 +396,13 @@ "internal/timeseries", "trace" ] - revision = "57065200b4b034a1c8ad54ff77069408c2218ae6" + revision = "1e491301e022f8f977054da4c2d852decd59571f" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b" + revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f" [[projects]] name = "golang.org/x/text" @@ -463,6 +463,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f0c6224dc5f30c1a7dea716d619665831ea0932b0eb9afc6ac897dbc459134fa" + inputs-digest = "ccb2ab7644a38c2d0326280582f758256e37fc98c3ef0403581e3b85cff42188" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a1adc79ac..05b14be60 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -54,43 +54,33 @@ [[constraint]] name = "github.com/tendermint/abci" - version = "~0.10.3" + version = "=0.11.0-rc4" [[constraint]] name = "github.com/tendermint/go-crypto" version = "~0.6.2" -[[override]] - name = "github.com/tendermint/go-wire" - version = "0.7.3" - [[constraint]] name = "github.com/tendermint/go-amino" - version = "~0.9.9" + version = "=0.9.9" [[constraint]] name = "github.com/tendermint/iavl" - version = "~0.7.0" + version = "0.8.0-rc0" [[constraint]] name = "github.com/tendermint/tendermint" - version = "0.19.5-rc1" + version = "=0.20.0-rc3" [[override]] name = "github.com/tendermint/tmlibs" - version = "~0.8.3-rc0" - + branch = "develop" + # this got updated and broke, so locked to an old working commit ... [[override]] name = "google.golang.org/genproto" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" - [[constraint]] - name = "github.com/cosmos/bech32cosmos" - branch = "master" - - [prune] go-tests = true unused-packages = true - diff --git a/Makefile b/Makefile index 7d2d23ad6..f7e2083c7 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ get_vendor_deps: draw_deps: @# requires brew install graphviz or apt-get install graphviz go get github.com/RobotsAndPencils/goviz - @goviz -i github.com/tendermint/tendermint/cmd/tendermint -d 3 | dot -Tpng -o dependency-graph.png + @goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png ######################################## diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 4ce8a05d9..4d32b92f5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -65,9 +65,10 @@ type BaseApp struct { // See methods setCheckState and setDeliverState. // .valUpdates accumulate in DeliverTx and are reset in BeginBlock. // QUESTION: should we put valUpdates in the deliverState.ctx? - checkState *state // for CheckTx - deliverState *state // for DeliverTx - valUpdates []abci.Validator // cached validator changes from DeliverTx + checkState *state // for CheckTx + deliverState *state // for DeliverTx + valUpdates []abci.Validator // cached validator changes from DeliverTx + signedValidators []abci.SigningValidator // absent validators from begin block } var _ abci.Application = (*BaseApp)(nil) @@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) } + // set the signed validators for addition to context in deliverTx + app.signedValidators = req.Validators return } @@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk ctx = app.checkState.ctx.WithTxBytes(txBytes) } else { ctx = app.deliverState.ctx.WithTxBytes(txBytes) + ctx = ctx.WithSigningValidators(app.signedValidators) } // Simulate a DeliverTx for gas calculation diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 61498b1b1..532d39f1e 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -1,7 +1,6 @@ package baseapp import ( - "bytes" "encoding/json" "fmt" "os" @@ -12,6 +11,7 @@ import ( abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -83,18 +83,36 @@ func TestLoadVersion(t *testing.T) { header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) res := app.Commit() - commitID := sdk.CommitID{1, res.Data} + commitID1 := sdk.CommitID{1, res.Data} + header = abci.Header{Height: 2} + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + res = app.Commit() + commitID2 := sdk.CommitID{2, res.Data} - // reload + // reload with LoadLatestVersion app = NewBaseApp(name, nil, logger, db) app.MountStoresIAVL(capKey) - err = app.LoadLatestVersion(capKey) // needed to make stores non-nil + err = app.LoadLatestVersion(capKey) assert.Nil(t, err) + testLoadVersionHelper(t, app, int64(2), commitID2) - lastHeight = app.LastBlockHeight() - lastID = app.LastCommitID() - assert.Equal(t, int64(1), lastHeight) - assert.Equal(t, commitID, lastID) + // reload with LoadVersion, see if you can commit the same block and get + // the same result + app = NewBaseApp(name, nil, logger, db) + app.MountStoresIAVL(capKey) + err = app.LoadVersion(1, capKey) + assert.Nil(t, err) + testLoadVersionHelper(t, app, int64(1), commitID1) + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + app.Commit() + testLoadVersionHelper(t, app, int64(2), commitID2) +} + +func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) { + lastHeight := app.LastBlockHeight() + lastID := app.LastCommitID() + assert.Equal(t, expectedHeight, lastHeight) + assert.Equal(t, expectedID, lastID) } // Test that the app hash is static @@ -206,11 +224,91 @@ func TestInitChainer(t *testing.T) { assert.Equal(t, value, res.Value) } +func getStateCheckingHandler(t *testing.T, capKey *sdk.KVStoreKey, txPerHeight int, checkHeader bool) func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + counter := 0 + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + store := ctx.KVStore(capKey) + // Checking state gets updated between checkTx's / DeliverTx's + // on the store within a block. + if counter > 0 { + // check previous value in store + counterBytes := []byte{byte(counter - 1)} + prevBytes := store.Get(counterBytes) + assert.Equal(t, counterBytes, prevBytes) + } + + // set the current counter in the store + counterBytes := []byte{byte(counter)} + store.Set(counterBytes, counterBytes) + + // check that we can see the current header + // wrapped in an if, so it can be reused between CheckTx and DeliverTx tests. + if checkHeader { + thisHeader := ctx.BlockHeader() + height := int64((counter / txPerHeight) + 1) + assert.Equal(t, height, thisHeader.Height) + } + + counter++ + return sdk.Result{} + } +} + +// A mock transaction that has a validation which can fail. +type testTx struct { + positiveNum int64 +} + +const msgType2 = "testTx" + +func (tx testTx) Type() string { return msgType2 } +func (tx testTx) GetMsg() sdk.Msg { return tx } +func (tx testTx) GetSignBytes() []byte { return nil } +func (tx testTx) GetSigners() []sdk.Address { return nil } +func (tx testTx) GetSignatures() []auth.StdSignature { return nil } +func (tx testTx) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + // Test that successive CheckTx can see each others' effects // on the store within a block, and that the CheckTx state // gets reset to the latest Committed state during Commit func TestCheckTx(t *testing.T) { - // TODO + // Initialize an app for testing + app := newBaseApp(t.Name()) + // make a cap key and mount the store + capKey := sdk.NewKVStoreKey("main") + app.MountStoresIAVL(capKey) + err := app.LoadLatestVersion(capKey) // needed to make stores non-nil + assert.Nil(t, err) + app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) + + txPerHeight := 3 + app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, false)). + AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + tx := testUpdatePowerTx{} // doesn't matter + for i := 0; i < txPerHeight; i++ { + app.Check(tx) + } + // If it gets to this point, then successive CheckTx's can see the effects of + // other CheckTx's on the block. The following checks that if another block + // is committed, the CheckTx State will reset. + app.BeginBlock(abci.RequestBeginBlock{}) + tx2 := testTx{} + for i := 0; i < txPerHeight; i++ { + app.Deliver(tx2) + } + app.EndBlock(abci.RequestEndBlock{}) + app.Commit() + + checkStateStore := app.checkState.ctx.KVStore(capKey) + for i := 0; i < txPerHeight; i++ { + storedValue := checkStateStore.Get([]byte{byte(i)}) + assert.Nil(t, storedValue) + } } // Test that successive DeliverTx can see each others' effects @@ -224,30 +322,9 @@ func TestDeliverTx(t *testing.T) { err := app.LoadLatestVersion(capKey) // needed to make stores non-nil assert.Nil(t, err) - counter := 0 txPerHeight := 2 app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) - app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - store := ctx.KVStore(capKey) - if counter > 0 { - // check previous value in store - counterBytes := []byte{byte(counter - 1)} - prevBytes := store.Get(counterBytes) - assert.Equal(t, prevBytes, counterBytes) - } - - // set the current counter in the store - counterBytes := []byte{byte(counter)} - store.Set(counterBytes, counterBytes) - - // check we can see the current header - thisHeader := ctx.BlockHeader() - height := int64((counter / txPerHeight) + 1) - assert.Equal(t, height, thisHeader.Height) - - counter++ - return sdk.Result{} - }) + app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, true)) tx := testUpdatePowerTx{} // doesn't matter header := abci.Header{AppHash: []byte("apphash")} @@ -325,6 +402,27 @@ func TestSimulateTx(t *testing.T) { } } +func TestRunInvalidTransaction(t *testing.T) { + // Initialize an app for testing + app := newBaseApp(t.Name()) + // make a cap key and mount the store + capKey := sdk.NewKVStoreKey("main") + app.MountStoresIAVL(capKey) + err := app.LoadLatestVersion(capKey) // needed to make stores non-nil + assert.Nil(t, err) + app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return }) + app.Router().AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + app.BeginBlock(abci.RequestBeginBlock{}) + // Transaction where validate fails + invalidTx := testTx{-1} + err1 := app.Deliver(invalidTx) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), err1.Code) + // Transaction with no known route + unknownRouteTx := testUpdatePowerTx{} + err2 := app.Deliver(unknownRouteTx) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), err2.Code) +} + // Test that transactions exceeding gas limits fail func TestTxGasLimits(t *testing.T) { logger := defaultLogger() @@ -510,15 +608,20 @@ func TestValidatorChange(t *testing.T) { // Assert that validator updates are correct. for _, val := range valSet { + + pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey) // Sanity - assert.NotEqual(t, len(val.PubKey), 0) + assert.Nil(t, err) // Find matching update and splice it out. - for j := 0; j < len(valUpdates); { + for j := 0; j < len(valUpdates); j++ { valUpdate := valUpdates[j] + updatePubkey, err := tmtypes.PB2TM.PubKey(valUpdate.PubKey) + assert.Nil(t, err) + // Matched. - if bytes.Equal(valUpdate.PubKey, val.PubKey) { + if updatePubkey.Equals(pubkey) { assert.Equal(t, valUpdate.Power, val.Power+1) if j < len(valUpdates)-1 { // Splice it out. @@ -528,7 +631,6 @@ func TestValidatorChange(t *testing.T) { } // Not matched. - j++ } } assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected") @@ -542,7 +644,7 @@ func randPower() int64 { func makeVal(secret string) abci.Validator { return abci.Validator{ - PubKey: makePubKey(secret).Bytes(), + PubKey: tmtypes.TM2PB.PubKey(makePubKey(secret)), Power: randPower(), } } diff --git a/client/context/helpers.go b/client/context/helpers.go index f4686befd..f47cc7ff4 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -59,7 +59,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName // Query from Tendermint with the provided storename and path func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { - path := fmt.Sprintf("/store/%s/key", storeName) + path := fmt.Sprintf("/store/%s/%s", storeName, endPath) node, err := ctx.GetNode() if err != nil { return res, err diff --git a/client/keys/add.go b/client/keys/add.go index da368a3a6..7ad9474ce 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -102,12 +102,6 @@ func runAddCmd(cmd *cobra.Command, args []string) error { return nil } -// addOutput lets us json format the data -type addOutput struct { - Key keys.Info `json:"key"` - Seed string `json:"seed"` -} - func printCreate(info keys.Info, seed string) { output := viper.Get(cli.OutputFlag) switch output { @@ -121,7 +115,10 @@ func printCreate(info keys.Info, seed string) { fmt.Println(seed) } case "json": - out := addOutput{Key: info} + out, err := Bech32KeyOutput(info) + if err != nil { + panic(err) + } if !viper.GetBool(flagNoBackup) { out.Seed = seed } diff --git a/client/keys/list.go b/client/keys/list.go index 9af511e5c..22f163f1d 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" ) @@ -54,9 +53,11 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("[]")) return } - keysOutput := make([]KeyOutput, len(infos)) - for i, info := range infos { - keysOutput[i] = KeyOutput{Name: info.Name, Address: sdk.Address(info.PubKey.Address().Bytes())} + keysOutput, err := Bech32KeysOutput(infos) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return } output, err := json.MarshalIndent(keysOutput, "", " ") if err != nil { diff --git a/client/keys/show.go b/client/keys/show.go index c7be9cc9d..9051aba16 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" keys "github.com/tendermint/go-crypto/keys" @@ -51,7 +50,12 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - keyOutput := KeyOutput{Name: info.Name, Address: sdk.Address(info.PubKey.Address())} + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } output, err := json.MarshalIndent(keyOutput, "", " ") if err != nil { w.WriteHeader(500) diff --git a/client/keys/utils.go b/client/keys/utils.go index 1a358cfc9..d810dfa1f 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -1,13 +1,11 @@ package keys import ( - "encoding/json" "fmt" "path/filepath" "github.com/spf13/viper" - crypto "github.com/tendermint/go-crypto" keys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" @@ -48,35 +46,53 @@ func SetKeyBase(kb keys.Keybase) { // used for outputting keys.Info over REST type KeyOutput struct { - Name string `json:"name"` - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` + Name string `json:"name"` + Address string `json:"address"` + PubKey string `json:"pub_key"` + Seed string `json:"seed,omitempty"` } -func NewKeyOutput(info keys.Info) KeyOutput { - return KeyOutput{ - Name: info.Name, - Address: sdk.Address(info.PubKey.Address().Bytes()), - PubKey: info.PubKey, - } -} - -func NewKeyOutputs(infos []keys.Info) []KeyOutput { +// create a list of KeyOutput in bech32 format +func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) { kos := make([]KeyOutput, len(infos)) for i, info := range infos { - kos[i] = NewKeyOutput(info) + ko, err := Bech32KeyOutput(info) + if err != nil { + return nil, err + } + kos[i] = ko } - return kos + return kos, nil +} + +// create a KeyOutput in bech32 format +func Bech32KeyOutput(info keys.Info) (KeyOutput, error) { + bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes())) + if err != nil { + return KeyOutput{}, err + } + bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey) + if err != nil { + return KeyOutput{}, err + } + return KeyOutput{ + Name: info.Name, + Address: bechAccount, + PubKey: bechPubKey, + }, nil } func printInfo(info keys.Info) { - ko := NewKeyOutput(info) + ko, err := Bech32KeyOutput(info) + if err != nil { + panic(err) + } switch viper.Get(cli.OutputFlag) { case "text": fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") printKeyOutput(ko) case "json": - out, err := json.MarshalIndent(ko, "", "\t") + out, err := MarshalJSON(ko) if err != nil { panic(err) } @@ -85,7 +101,10 @@ func printInfo(info keys.Info) { } func printInfos(infos []keys.Info) { - kos := NewKeyOutputs(infos) + kos, err := Bech32KeysOutput(infos) + if err != nil { + panic(err) + } switch viper.Get(cli.OutputFlag) { case "text": fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") @@ -93,7 +112,7 @@ func printInfos(infos []keys.Info) { printKeyOutput(ko) } case "json": - out, err := json.MarshalIndent(kos, "", "\t") + out, err := MarshalJSON(kos) if err != nil { panic(err) } @@ -102,13 +121,5 @@ func printInfos(infos []keys.Info) { } func printKeyOutput(ko KeyOutput) { - bechAccount, err := sdk.Bech32CosmosifyAcc(ko.Address) - if err != nil { - panic(err) - } - bechPubKey, err := sdk.Bech32CosmosifyAccPub(ko.PubKey) - if err != nil { - panic(err) - } - fmt.Printf("%s\t%s\t%s\n", ko.Name, bechAccount, bechPubKey) + fmt.Printf("%s\t%s\t%s\n", ko.Name, ko.Address, ko.PubKey) } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 840312ef6..47df51e7e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,6 +2,7 @@ package lcd import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" cryptoKeys "github.com/tendermint/go-crypto/keys" tmcfg "github.com/tendermint/tendermint/config" nm "github.com/tendermint/tendermint/node" @@ -31,21 +33,30 @@ import ( client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - bapp "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - btypes "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + rpc "github.com/cosmos/cosmos-sdk/client/rpc" + gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/server" tests "github.com/cosmos/cosmos-sdk/tests" 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/stake" + stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) var ( - coinDenom = "mycoin" + coinDenom = "steak" coinAmount = int64(10000000) + validatorAddr1Hx = "" + validatorAddr2Hx = "" + validatorAddr1 = "" + validatorAddr2 = "" + // XXX bad globals name = "test" password = "0123456789" - port string // XXX: but it's the int ... + port string seed string sendAddr string ) @@ -92,13 +103,13 @@ func TestKeys(t *testing.T) { err = cdc.UnmarshalJSON([]byte(body), &m) require.Nil(t, err) - sendAddrAcc, _ := sdk.GetAccAddressHex(sendAddr) addrAcc, _ := sdk.GetAccAddressHex(addr) + addrBech32, _ := sdk.Bech32ifyAcc(addrAcc) - assert.Equal(t, m[0].Name, name, "Did not serve keys name correctly") - assert.Equal(t, m[0].Address, sendAddrAcc, "Did not serve keys Address correctly") - assert.Equal(t, m[1].Name, newName, "Did not serve keys name correctly") - assert.Equal(t, m[1].Address, addrAcc, "Did not serve keys Address correctly") + assert.Equal(t, name, m[0].Name, "Did not serve keys name correctly") + assert.Equal(t, sendAddr, m[0].Address, "Did not serve keys Address correctly") + assert.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") + assert.Equal(t, addrBech32, m[1].Address, "Did not serve keys Address correctly") // select key keyEndpoint := fmt.Sprintf("/keys/%s", newName) @@ -109,7 +120,7 @@ func TestKeys(t *testing.T) { require.Nil(t, err) assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly") - assert.Equal(t, addrAcc, m2.Address, "Did not serve keys Address correctly") + assert.Equal(t, addrBech32, m2.Address, "Did not serve keys Address correctly") // update key jsonStr = []byte(fmt.Sprintf(`{"old_password":"%s", "new_password":"12345678901"}`, newPassword)) @@ -191,7 +202,7 @@ func TestBlock(t *testing.T) { func TestValidators(t *testing.T) { - var resultVals ctypes.ResultValidators + var resultVals rpc.ResultValidatorsOutput res, body := request(t, port, "GET", "/validatorsets/latest", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -199,7 +210,10 @@ func TestValidators(t *testing.T) { err := cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) + assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + + assert.Contains(t, resultVals.Validators[0].Address, "cosmosvaladdr") + assert.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalpub") // -- @@ -209,7 +223,7 @@ func TestValidators(t *testing.T) { err = cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) + assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) // -- @@ -218,9 +232,11 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { + bz, _ := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + someFakeAddr, _ := sdk.Bech32ifyAcc(bz) // query empty - res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) + res, body := request(t, port, "GET", "/accounts/"+someFakeAddr, nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, sendAddr) @@ -309,6 +325,62 @@ func TestTxs(t *testing.T) { // assert.NotEqual(t, "[]", body) } +func TestValidatorsQuery(t *testing.T) { + validators := getValidators(t) + assert.Equal(t, len(validators), 2) + + // make sure all the validators were found (order unknown because sorted by owner addr) + foundVal1, foundVal2 := false, false + if validators[0].Owner == validatorAddr1 || validators[1].Owner == validatorAddr1 { + foundVal1 = true + } + if validators[0].Owner == validatorAddr2 || validators[1].Owner == validatorAddr2 { + foundVal2 = true + } + assert.True(t, foundVal1, "validatorAddr1 %v, owner1 %v, owner2 %v", validatorAddr1, validators[0].Owner, validators[1].Owner) + assert.True(t, foundVal2, "validatorAddr2 %v, owner1 %v, owner2 %v", validatorAddr2, validators[0].Owner, validators[1].Owner) +} + +func TestBond(t *testing.T) { + + // create bond TX + resultTx := doBond(t, port, seed) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was commited + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc := getAccount(t, sendAddr) + coins := acc.GetCoins() + assert.Equal(t, int64(87), coins.AmountOf(coinDenom)) + + // query candidate + bond := getDelegation(t, sendAddr, validatorAddr1) + assert.Equal(t, "10/1", bond.Shares.String()) +} + +func TestUnbond(t *testing.T) { + + // create unbond TX + resultTx := doUnbond(t, port, seed) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was commited + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc := getAccount(t, sendAddr) + coins := acc.GetCoins() + assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + + // query candidate + bond := getDelegation(t, sendAddr, validatorAddr1) + assert.Equal(t, "9/1", bond.Shares.String()) +} + //__________________________________________________________ // helpers @@ -324,26 +396,18 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { if err != nil { return nil, nil, err } - var info cryptoKeys.Info - info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed - if err != nil { - return nil, nil, err - } - - pubKey := info.PubKey - sendAddr = pubKey.Address().String() // XXX global config := GetConfig() config.Consensus.TimeoutCommit = 1000 config.Consensus.SkipTimeoutCommit = false logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - // logger = log.NewFilter(logger, log.AllowError()) + logger = log.NewFilter(logger, log.AllowError()) privValidatorFile := config.PrivValidatorFile() privVal := pvm.LoadOrGenFilePV(privValidatorFile) db := dbm.NewMemDB() - app := bapp.NewBasecoinApp(logger, db) - cdc = bapp.MakeCodec() // XXX + app := gapp.NewGaiaApp(logger, db) + cdc = gapp.MakeCodec() // XXX genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) @@ -351,25 +415,63 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { return nil, nil, err } - coins := sdk.Coins{{coinDenom, coinAmount}} - appState := map[string]interface{}{ - "accounts": []*btypes.GenesisAccount{ - { - Name: "tester", - Address: pubKey.Address(), - Coins: coins, - }, + genDoc.Validators = append(genDoc.Validators, + tmtypes.GenesisValidator{ + PubKey: crypto.GenPrivKeyEd25519().PubKey(), + Power: 1, + Name: "val", }, - } - stateBytes, err := json.Marshal(appState) + ) + + pk1 := genDoc.Validators[0].PubKey + pk2 := genDoc.Validators[1].PubKey + validatorAddr1Hx = hex.EncodeToString(pk1.Address()) + validatorAddr2Hx = hex.EncodeToString(pk2.Address()) + validatorAddr1, _ = sdk.Bech32ifyVal(pk1.Address()) + validatorAddr2, _ = sdk.Bech32ifyVal(pk2.Address()) + + // NOTE it's bad practice to reuse pk address for the owner address but doing in the + // test for simplicity + var appGenTxs [2]json.RawMessage + appGenTxs[0], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk1, pk1.Address(), "test_val1", true) if err != nil { return nil, nil, err } - genDoc.AppStateJSON = stateBytes + appGenTxs[1], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk2, pk2.Address(), "test_val2", true) + if err != nil { + return nil, nil, err + } + + genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:]) + if err != nil { + return nil, nil, err + } + + // add the sendAddr to genesis + var info cryptoKeys.Info + info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed + if err != nil { + return nil, nil, err + } + sendAddrHex, _ := sdk.GetAccAddressHex(info.PubKey.Address().String()) + sendAddr, _ = sdk.Bech32ifyAcc(sendAddrHex) // XXX global + accAuth := auth.NewBaseAccountWithAddress(info.PubKey.Address()) + accAuth.Coins = sdk.Coins{{"steak", 100}} + acc := gapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) + + appState, err := wire.MarshalJSONIndent(cdc, genesisState) + if err != nil { + return nil, nil, err + } + genDoc.AppStateJSON = appState // LCD listen address - port = fmt.Sprintf("%d", 17377) // XXX - listenAddr := fmt.Sprintf("tcp://localhost:%s", port) // XXX + var listenAddr string + listenAddr, port, err = server.FreeTCPAddr() + if err != nil { + return nil, nil, err + } // XXX: need to set this so LCD knows the tendermint node address! viper.Set(client.FlagNode, config.RPC.ListenAddress) @@ -379,7 +481,7 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { if err != nil { return nil, nil, err } - lcd, err := startLCD(logger, listenAddr) + lcd, err := startLCD(logger, listenAddr, cdc) if err != nil { return nil, nil, err } @@ -418,7 +520,7 @@ func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, p } // start the LCD. note this blocks! -func startLCD(logger log.Logger, listenAddr string) (net.Listener, error) { +func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { handler := createHandler(cdc) return tmrpc.StartHTTPServer(listenAddr, handler, logger) } @@ -456,7 +558,7 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype kb := client.MockKeyBase() receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) require.Nil(t, err) - receiveAddr = receiveInfo.PubKey.Address().String() + receiveAddr, _ = sdk.Bech32ifyAcc(receiveInfo.PubKey.Address()) acc := getAccount(t, sendAddr) sequence := acc.GetSequence() @@ -473,12 +575,11 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype } func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { - // create receive address kb := client.MockKeyBase() receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) require.Nil(t, err) - receiveAddr := receiveInfo.PubKey.Address().String() + receiveAddr, _ := sdk.Bech32ifyAcc(receiveInfo.PubKey.Address()) // get the account to get the sequence acc := getAccount(t, sendAddr) @@ -494,3 +595,81 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad return resultTx } + +func getDelegation(t *testing.T, delegatorAddr, candidateAddr string) stake.Delegation { + // get the account to get the sequence + res, body := request(t, port, "GET", "/stake/"+delegatorAddr+"/bonding_status/"+candidateAddr, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var bond stake.Delegation + err := cdc.UnmarshalJSON([]byte(body), &bond) + require.Nil(t, err) + return bond +} + +func doBond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, sendAddr) + sequence := acc.GetSequence() + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "sequence": %d, + "delegate": [ + { + "delegator_addr": "%s", + "validator_addr": "%s", + "bond": { "denom": "%s", "amount": 10 } + } + ], + "unbond": [] + }`, name, password, sequence, sendAddr, validatorAddr1, coinDenom)) + res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doUnbond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, sendAddr) + sequence := acc.GetSequence() + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "sequence": %d, + "bond": [], + "unbond": [ + { + "delegator_addr": "%s", + "validator_addr": "%s", + "shares": "1" + } + ] + }`, name, password, sequence, sendAddr, validatorAddr1)) + res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func getValidators(t *testing.T) []stakerest.StakeValidatorOutput { + // get the account to get the sequence + res, body := request(t, port, "GET", "/stake/validators", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var validators []stakerest.StakeValidatorOutput + err := cdc.UnmarshalJSON([]byte(body), &validators) + require.Nil(t, err) + return validators +} diff --git a/client/lcd/root.go b/client/lcd/root.go index a7be5079b..507134302 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -22,6 +22,7 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" + stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) const ( @@ -55,6 +56,7 @@ func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) if err != nil { return err } + logger.Info("REST server started") // Wait forever and cleanup cmn.TrapSignal(func() { @@ -83,5 +85,6 @@ func createHandler(cdc *wire.Codec) http.Handler { auth.RegisterRoutes(ctx, r, cdc, "acc") bank.RegisterRoutes(ctx, r, cdc, kb) ibc.RegisterRoutes(ctx, r, cdc, kb) + stake.RegisterRoutes(ctx, r, cdc, kb) return r } diff --git a/client/rpc/block.go b/client/rpc/block.go index 354727019..693298bb8 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -16,7 +16,8 @@ const ( flagSelect = "select" ) -func blockCommand() *cobra.Command { +//BlockCommand returns the verified block data for a given heights +func BlockCommand() *cobra.Command { cmd := &cobra.Command{ Use: "block [height]", Short: "Get verified data for a the block at given height", diff --git a/client/rpc/root.go b/client/rpc/root.go index f779bd729..e89972c3c 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -26,8 +26,6 @@ func AddCommands(cmd *cobra.Command) { cmd.AddCommand( initClientCommand(), statusCommand(), - blockCommand(), - validatorCommand(), ) } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 892d131ac..f8835d737 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -10,14 +10,17 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) // TODO these next two functions feel kinda hacky based on their placement -func validatorCommand() *cobra.Command { +//ValidatorCommand returns the validator set for a given height +func ValidatorCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "validatorset [height]", - Short: "Get the full validator set at given height", + Use: "validator-set [height]", + Short: "Get the full tendermint validator set at given height", Args: cobra.MaximumNArgs(1), RunE: printValidators, } @@ -27,6 +30,38 @@ func validatorCommand() *cobra.Command { return cmd } +// Validator output in bech32 format +type ValidatorOutput struct { + Address string `json:"address"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Accum int64 `json:"accum"` + VotingPower int64 `json:"voting_power"` +} + +// Validators at a certain height output in bech32 format +type ResultValidatorsOutput struct { + BlockHeight int64 `json:"block_height"` + Validators []ValidatorOutput `json:"validators"` +} + +func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) { + bechAddress, err := sdk.Bech32ifyVal(validator.Address) + if err != nil { + return ValidatorOutput{}, err + } + bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) + if err != nil { + return ValidatorOutput{}, err + } + + return ValidatorOutput{ + Address: bechAddress, + PubKey: bechValPubkey, + Accum: validator.Accum, + VotingPower: validator.VotingPower, + }, nil +} + func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { // get the node node, err := ctx.GetNode() @@ -34,12 +69,23 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { return nil, err } - res, err := node.Validators(height) + validatorsRes, err := node.Validators(height) if err != nil { return nil, err } - output, err := cdc.MarshalJSON(res) + outputValidatorsRes := ResultValidatorsOutput{ + BlockHeight: validatorsRes.BlockHeight, + Validators: make([]ValidatorOutput, len(validatorsRes.Validators)), + } + for i := 0; i < len(validatorsRes.Validators); i++ { + outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i]) + if err != nil { + return nil, err + } + } + + output, err := cdc.MarshalJSON(outputValidatorsRes) if err != nil { return nil, err } @@ -95,6 +141,7 @@ func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } + w.Write(output) } } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index dbecada00..f06ed2be3 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -5,6 +5,7 @@ import ( "os" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -15,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -34,10 +36,11 @@ type GaiaApp struct { cdc *wire.Codec // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - keyStake *sdk.KVStoreKey + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -45,6 +48,7 @@ type GaiaApp struct { coinKeeper bank.Keeper ibcMapper ibc.Mapper stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper } func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { @@ -52,12 +56,13 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { // create your application object var app = &GaiaApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), - cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - keyStake: sdk.NewKVStoreKey("stake"), + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), } // define the accountMapper @@ -71,6 +76,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.coinKeeper = bank.NewKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). @@ -80,9 +86,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { // initialize BaseApp app.SetInitChainer(app.initChainer) - app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -97,15 +104,35 @@ func MakeCodec() *wire.Codec { ibc.RegisterWire(cdc) bank.RegisterWire(cdc) stake.RegisterWire(cdc) + slashing.RegisterWire(cdc) auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) return cdc } +// application updates every end block +func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } +} + // custom logic for gaia initialization func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? var genesisState GenesisState err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) @@ -126,8 +153,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci return abci.ResponseInitChain{} } -// export the state of gaia for a genesis f -func (app *GaiaApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +// export the state of gaia for a genesis file +func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -143,5 +170,10 @@ func (app *GaiaApp) ExportAppStateJSON() (appState json.RawMessage, err error) { Accounts: accounts, StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, nil } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index b6f1c9e03..0fe1cfe5b 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -1,7 +1,6 @@ package app import ( - "encoding/json" "fmt" "os" "testing" @@ -115,7 +114,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { // Initialize the chain vals := []abci.Validator{} - gapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + gapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) gapp.Commit() return nil @@ -139,30 +138,6 @@ func TestMsgs(t *testing.T) { } } -func setGenesisAccounts(gapp *GaiaApp, accs ...*auth.BaseAccount) error { - genaccs := make([]GenesisAccount, len(accs)) - for i, acc := range accs { - genaccs[i] = NewGenesisAccount(acc) - } - - genesisState := GenesisState{ - Accounts: genaccs, - StakeData: stake.DefaultGenesisState(), - } - - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - if err != nil { - return err - } - - // Initialize the chain - vals := []abci.Validator{} - gapp.InitChain(abci.RequestInitChain{vals, stateBytes}) - gapp.Commit() - - return nil -} - func TestGenesis(t *testing.T) { logger, dbs := loggerAndDB() gapp := NewGaiaApp(logger, dbs) @@ -178,7 +153,7 @@ func TestGenesis(t *testing.T) { } err = setGenesis(gapp, baseAcc) - assert.Nil(t, err) + require.Nil(t, err) // A checkTx context ctx := gapp.BaseApp.NewContext(true, abci.Header{}) @@ -394,13 +369,13 @@ func TestStakeMsgs(t *testing.T) { require.Equal(t, acc1, res1) require.Equal(t, acc2, res2) - // Declare Candidacy + // Create Validator description := stake.NewDescription("foo_moniker", "", "", "") - declareCandidacyMsg := stake.NewMsgDeclareCandidacy( + createValidatorMsg := stake.NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - SignCheckDeliver(t, gapp, declareCandidacyMsg, []int64{0}, true, priv1) + SignCheckDeliver(t, gapp, createValidatorMsg, []int64{0}, true, priv1) ctxDeliver := gapp.BaseApp.NewContext(false, abci.Header{}) res1 = gapp.accountMapper.GetAccount(ctxDeliver, addr1) @@ -415,13 +390,13 @@ func TestStakeMsgs(t *testing.T) { bond, found := gapp.stakeKeeper.GetDelegation(ctxDeliver, addr1, addr1) require.True(sdk.RatEq(t, sdk.NewRat(10), bond.Shares)) - // Edit Candidacy + // Edit Validator description = stake.NewDescription("bar_moniker", "", "", "") - editCandidacyMsg := stake.NewMsgEditCandidacy( + editValidatorMsg := stake.NewMsgEditValidator( addr1, description, ) - SignDeliver(t, gapp, editCandidacyMsg, []int64{1}, true, priv1) + SignDeliver(t, gapp, editValidatorMsg, []int64{1}, true, priv1) validator, found = gapp.stakeKeeper.GetValidator(ctxDeliver, addr1) require.True(t, found) @@ -455,6 +430,42 @@ func TestStakeMsgs(t *testing.T) { require.False(t, found) } +func TestExportValidators(t *testing.T) { + gapp := newGaiaApp() + + genCoins, err := sdk.ParseCoins("42steak") + require.Nil(t, err) + bondCoin, err := sdk.ParseCoin("10steak") + require.Nil(t, err) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: genCoins, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: genCoins, + } + + err = setGenesis(gapp, acc1, acc2) + require.Nil(t, err) + + // Create Validator + description := stake.NewDescription("foo_moniker", "", "", "") + createValidatorMsg := stake.NewMsgCreateValidator( + addr1, priv1.PubKey(), bondCoin, description, + ) + SignCheckDeliver(t, gapp, createValidatorMsg, []int64{0}, true, priv1) + gapp.Commit() + + // Export validator set + _, validators, err := gapp.ExportAppStateAndValidators() + require.Nil(t, err) + require.Equal(t, 1, len(validators)) // 1 validator + require.Equal(t, priv1.PubKey(), validators[0].PubKey) + require.Equal(t, int64(10), validators[0].Power) +} + //____________________________________________________________________________________ func CheckBalance(t *testing.T, gapp *GaiaApp, addr sdk.Address, balExpected string) { diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 8cf45e84a..813796c0d 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -65,7 +65,7 @@ func GaiaAppInit() server.AppInit { fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError) fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) - fsAppGenTx.String(flagName, "", "validator moniker, if left blank, do not add validator") + fsAppGenTx.String(flagName, "", "validator moniker, required") fsAppGenTx.String(flagClientHome, DefaultCLIHome, "home directory for the client, used for key generation") fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created") @@ -74,7 +74,7 @@ func GaiaAppInit() server.AppInit { FlagsAppGenState: fsAppGenState, FlagsAppGenTx: fsAppGenTx, AppGenTx: GaiaAppGenTx, - AppGenState: GaiaAppGenState, + AppGenState: GaiaAppGenStateJSON, } } @@ -85,19 +85,35 @@ type GaiaGenTx struct { PubKey crypto.PubKey `json:"pub_key"` } -// Generate a gaia genesis transaction +// Generate a gaia genesis transaction with flags func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - var addr sdk.Address - var secret string clientRoot := viper.GetString(flagClientHome) overwrite := viper.GetBool(flagOWK) name := viper.GetString(flagName) + if name == "" { + return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)") + } + + var addr sdk.Address + var secret string addr, secret, err = server.GenerateSaveCoinKey(clientRoot, name, "1234567890", overwrite) if err != nil { return } + mm := map[string]string{"secret": secret} + var bz []byte + bz, err = cdc.MarshalJSON(mm) + if err != nil { + return + } + cliPrint = json.RawMessage(bz) + return GaiaAppGenTxNF(cdc, pk, addr, name, overwrite) +} + +// Generate a gaia genesis transaction without flags +func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name string, overwrite bool) ( + appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { var bz []byte gaiaGenTx := GaiaGenTx{ @@ -111,13 +127,6 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( } appGenTx = json.RawMessage(bz) - mm := map[string]string{"secret": secret} - bz, err = cdc.MarshalJSON(mm) - if err != nil { - return - } - cliPrint = json.RawMessage(bz) - validator = tmtypes.GenesisValidator{ PubKey: pk, Power: freeFermionVal, @@ -127,7 +136,7 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey -func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { if len(appGenTxs) == 0 { err = errors.New("must provide at least genesis transaction") @@ -171,10 +180,21 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso } // create the final app state - genesisState := GenesisState{ + genesisState = GenesisState{ Accounts: genaccs, StakeData: stakeData, } + return +} + +// GaiaAppGenState but with JSON +func GaiaAppGenStateJSON(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { + + // create the final app state + genesisState, err := GaiaAppGenState(cdc, appGenTxs) + if err != nil { + return nil, err + } appState, err = wire.MarshalJSONIndent(cdc, genesisState) return } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 0f549d6d6..1868452a6 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -30,46 +30,44 @@ func TestGaiaCLISend(t *testing.T) { executeWrite(t, "gaiacli keys add bar", pass) // get a free port, also setup some common flags - servAddr := server.FreeTCPAddr(t) + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) // start gaiad server - cmd, _, _ := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) - defer cmd.Process.Kill() + proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + defer proc.Stop(false) + tests.WaitForStart(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) - fooBech, err := sdk.Bech32CosmosifyAcc(fooAddr) - if err != nil { - t.Error(err) - } - barBech, err := sdk.Bech32CosmosifyAcc(barAddr) - if err != nil { - t.Error(err) - } - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooBech, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) assert.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak")) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barAddr), pass) - time.Sleep(time.Second * 3) // waiting for some blocks to pass + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + time.Sleep(time.Second * 2) // waiting for some blocks to pass - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barBech, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak")) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooBech, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barAddr), pass) - time.Sleep(time.Second * 3) // waiting for some blocks to pass + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) + time.Sleep(time.Second * 2) // waiting for some blocks to pass - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barBech, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) assert.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak")) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooBech, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) assert.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak")) } -func TestGaiaCLIDeclareCandidacy(t *testing.T) { +func TestGaiaCLICreateValidator(t *testing.T) { tests.ExecuteT(t, "gaiad unsafe_reset_all") pass := "1234567890" @@ -79,95 +77,79 @@ func TestGaiaCLIDeclareCandidacy(t *testing.T) { executeWrite(t, "gaiacli keys add bar", pass) // get a free port, also setup some common flags - servAddr := server.FreeTCPAddr(t) + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID) // start gaiad server - cmd, _, _ := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) - defer cmd.Process.Kill() + proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr)) + defer proc.Stop(false) + tests.WaitForStart(port) fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json") - barAddr, _ := executeGetAddrPK(t, "gaiacli keys show bar --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) - fooBech, err := sdk.Bech32CosmosifyAcc(fooAddr) - if err != nil { - t.Error(err) - } - barBech, err := sdk.Bech32CosmosifyAcc(barAddr) - if err != nil { - t.Error(err) - } - valPrivkey := crypto.GenPrivKeyEd25519() - valAddr := sdk.Address((valPrivkey.PubKey().Address())) - bechVal, err := sdk.Bech32CosmosifyVal(valAddr) - - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barBech), pass) + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barCech), pass) time.Sleep(time.Second * 3) // waiting for some blocks to pass - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooBech, flags)) - assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barBech, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak")) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags)) + assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) - // declare candidacy - declStr := fmt.Sprintf("gaiacli create-validator %v", flags) - declStr += fmt.Sprintf(" --name=%v", "bar") - declStr += fmt.Sprintf(" --validator-address=%v", bechVal) - declStr += fmt.Sprintf(" --amount=%v", "3steak") - declStr += fmt.Sprintf(" --moniker=%v", "bar-vally") - fmt.Printf("debug declStr: %v\n", declStr) - executeWrite(t, declStr, pass) - time.Sleep(time.Second) // waiting for some blocks to pass - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barAddr, flags)) - assert.Equal(t, int64(7), barAcc.GetCoins().AmountOf("steak")) - candidate := executeGetCandidate(t, fmt.Sprintf("gaiacli candidate %v --address-candidate=%v", flags, barAddr)) - assert.Equal(t, candidate.Owner.String(), barAddr) - assert.Equal(t, int64(3), candidate.PoolShares) + // create validator + cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags) + cvStr += fmt.Sprintf(" --name=%v", "bar") + cvStr += fmt.Sprintf(" --address-validator=%v", barCech) + cvStr += fmt.Sprintf(" --pubkey=%v", barCeshPubKey) + cvStr += fmt.Sprintf(" --amount=%v", "2steak") + cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally") + + executeWrite(t, cvStr, pass) + time.Sleep(time.Second * 3) // waiting for some blocks to pass + + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) + require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc) + + validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %v --output=json %v", barCech, flags)) + assert.Equal(t, validator.Owner, barAddr) + assert.Equal(t, "2/1", validator.PoolShares.Amount.String()) - // TODO timeout issues if not connected to the internet // unbond a single share - //unbondStr := fmt.Sprintf("gaiacli unbond %v", flags) - //unbondStr += fmt.Sprintf(" --name=%v", "bar") - //unbondStr += fmt.Sprintf(" --address-candidate=%v", barAddr) - //unbondStr += fmt.Sprintf(" --address-delegator=%v", barAddr) - //unbondStr += fmt.Sprintf(" --shares=%v", "1") - //unbondStr += fmt.Sprintf(" --sequence=%v", "1") - //fmt.Printf("debug unbondStr: %v\n", unbondStr) - //executeWrite(t, unbondStr, pass) - //time.Sleep(time.Second * 3) // waiting for some blocks to pass - //barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barAddr, flags)) - //assert.Equal(t, int64(99998), barAcc.GetCoins().AmountOf("steak")) - //candidate = executeGetCandidate(t, fmt.Sprintf("gaiacli candidate %v --address-candidate=%v", flags, barAddr)) - //assert.Equal(t, int64(2), candidate.BondedShares.Evaluate()) + unbondStr := fmt.Sprintf("gaiacli stake unbond %v", flags) + unbondStr += fmt.Sprintf(" --name=%v", "bar") + unbondStr += fmt.Sprintf(" --address-validator=%v", barCech) + unbondStr += fmt.Sprintf(" --address-delegator=%v", barCech) + unbondStr += fmt.Sprintf(" --shares=%v", "1") + unbondStr += fmt.Sprintf(" --sequence=%v", "1") + t.Log(fmt.Sprintf("debug unbondStr: %v\n", unbondStr)) + + executeWrite(t, unbondStr, pass) + time.Sleep(time.Second * 3) // waiting for some blocks to pass + + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) + require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak"), "%v", barAcc) + validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %v --output=json %v", barCech, flags)) + assert.Equal(t, "1/1", validator.PoolShares.Amount.String()) } +//___________________________________________________________________________________ +// executors + func executeWrite(t *testing.T, cmdStr string, writes ...string) { - cmd, wc, _ := tests.GoExecuteT(t, cmdStr) + proc := tests.GoExecuteT(t, cmdStr) for _, write := range writes { - _, err := wc.Write([]byte(write + "\n")) - if err != nil { - fmt.Println(err) - } + _, err := proc.StdinPipe.Write([]byte(write + "\n")) require.NoError(t, err) } - fmt.Printf("debug waiting cmdStr: %v\n", cmdStr) - cmd.Wait() -} - -func executeWritePrint(t *testing.T, cmdStr string, writes ...string) { - cmd, wc, rc := tests.GoExecuteT(t, cmdStr) - - for _, write := range writes { - _, err := wc.Write([]byte(write + "\n")) - require.NoError(t, err) - } - fmt.Printf("debug waiting cmdStr: %v\n", cmdStr) - cmd.Wait() - - bz := make([]byte, 100000) - rc.Read(bz) - fmt.Printf("debug read: %v\n", string(bz)) + proc.Wait() } func executeInit(t *testing.T, cmdStr string) (chainID string) { @@ -187,7 +169,14 @@ func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.Address, crypto.PubKey) out := tests.ExecuteT(t, cmdStr) var ko keys.KeyOutput keys.UnmarshalJSON([]byte(out), &ko) - return ko.Address, ko.PubKey + + address, err := sdk.GetAccAddressBech32(ko.Address) + require.NoError(t, err) + + pk, err := sdk.GetAccPubKeyBech32(ko.PubKey) + require.NoError(t, err) + + return address, pk } func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { @@ -204,11 +193,11 @@ func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { return acc } -func executeGetCandidate(t *testing.T, cmdStr string) stake.Validator { +func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { out := tests.ExecuteT(t, cmdStr) - var candidate stake.Validator + var validator stake.Validator cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &candidate) - require.NoError(t, err, "out %v, err %v", out, err) - return candidate + err := cdc.UnmarshalJSON([]byte(out), &validator) + require.NoError(t, err, "out %v\n, err %v", out, err) + return validator } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 2f36201b2..2e24842e3 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -14,6 +14,7 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" + slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" @@ -35,36 +36,83 @@ func main() { // the below functions and eliminate global vars, like we do // with the cdc - // add standard rpc, and tx commands + // add standard rpc commands rpc.AddCommands(rootCmd) - rootCmd.AddCommand(client.LineBreak) - tx.AddCommands(rootCmd, cdc) - rootCmd.AddCommand(client.LineBreak) - // add query/post commands (custom to binary) + //Add state commands + tendermintCmd := &cobra.Command{ + Use: "tendermint", + Short: "Tendermint state querying subcommands", + } + tendermintCmd.AddCommand( + rpc.BlockCommand(), + rpc.ValidatorCommand(), + ) + tx.AddCommands(tendermintCmd, cdc) + + //Add IBC commands + ibcCmd := &cobra.Command{ + Use: "ibc", + Short: "Inter-Blockchain Communication subcommands", + } + ibcCmd.AddCommand( + client.PostCommands( + ibccmd.IBCTransferCmd(cdc), + ibccmd.IBCRelayCmd(cdc), + )...) + + advancedCmd := &cobra.Command{ + Use: "advanced", + Short: "Advanced subcommands", + } + + advancedCmd.AddCommand( + tendermintCmd, + ibcCmd, + lcd.ServeCommand(cdc), + ) rootCmd.AddCommand( + advancedCmd, + client.LineBreak, + ) + + //Add stake commands + stakeCmd := &cobra.Command{ + Use: "stake", + Short: "Stake and validation subcommands", + } + stakeCmd.AddCommand( client.GetCommands( - authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), stakecmd.GetCmdQueryValidator("stake", cdc), stakecmd.GetCmdQueryValidators("stake", cdc), stakecmd.GetCmdQueryDelegation("stake", cdc), stakecmd.GetCmdQueryDelegations("stake", cdc), + slashingcmd.GetCmdQuerySigningInfo("slashing", cdc), + )...) + stakeCmd.AddCommand( + client.PostCommands( + stakecmd.GetCmdCreateValidator(cdc), + stakecmd.GetCmdEditValidator(cdc), + stakecmd.GetCmdDelegate(cdc), + stakecmd.GetCmdUnbond(cdc), + slashingcmd.GetCmdUnrevoke(cdc), + )...) + rootCmd.AddCommand( + stakeCmd, + ) + + //Add auth and bank commands + rootCmd.AddCommand( + client.GetCommands( + authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), )...) rootCmd.AddCommand( client.PostCommands( bankcmd.SendTxCmd(cdc), - ibccmd.IBCTransferCmd(cdc), - ibccmd.IBCRelayCmd(cdc), - stakecmd.GetCmdDeclareCandidacy(cdc), - stakecmd.GetCmdEditCandidacy(cdc), - stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), )...) // add proxy, version and key info rootCmd.AddCommand( - client.LineBreak, - lcd.ServeCommand(cdc), keys.Commands(), client.LineBreak, version.VersionCmd, diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index f64498d85..5d0eb9030 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -17,6 +18,7 @@ import ( func main() { cdc := app.MakeCodec() ctx := server.NewDefaultContext() + cobra.EnableCommandSorting = false rootCmd := &cobra.Command{ Use: "gaiad", Short: "Gaia Daemon (server)", @@ -25,7 +27,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, app.GaiaAppInit(), server.ConstructAppCreator(newApp, "gaia"), - server.ConstructAppExporter(exportAppState, "gaia")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "gaia")) // prepare and add flags executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome) @@ -36,7 +38,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewGaiaApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { gapp := app.NewGaiaApp(logger, db) - return gapp.ExportAppStateJSON() + return gapp.ExportAppStateAndValidators() } diff --git a/docs/sdk/lcd-rest-api.yaml b/docs/sdk/lcd-rest-api.yaml index 34ccaa0ac..3008d7f73 100644 --- a/docs/sdk/lcd-rest-api.yaml +++ b/docs/sdk/lcd-rest-api.yaml @@ -2,9 +2,9 @@ swagger: '2.0' info: version: '1.1.0' title: Light client daemon to interface with Cosmos baseserver via REST - description: Specification for the LCD provided by `gaiacli rest-server` + description: Specification for the LCD provided by `gaiacli advanced rest-server` + - securityDefinitions: kms: type: basic @@ -58,7 +58,7 @@ paths: responses: 200: description: '"true" or "false"' - + /keys: get: summary: List of accounts stored locally @@ -102,7 +102,7 @@ paths: - application/json responses: 200: - description: 12 word Seed + description: 16 word Seed schema: type: string /keys/{name}: @@ -199,12 +199,12 @@ paths: # description: Tx was send and will probably be added to the next block # 400: # description: The Tx was malformated - + /accounts/{address}: parameters: - in: path name: address - description: Account address + description: Account address in bech32 format required: true type: string get: @@ -222,7 +222,7 @@ paths: parameters: - in: path name: address - description: Account address + description: Account address in bech32 format required: true type: string post: @@ -255,18 +255,6 @@ paths: description: Tx was send and will probably be added to the next block 400: description: The Tx was malformated - /accounts/{address}/nonce: - parameters: - - in: path - name: address - description: Account address - required: true - type: string - get: - summary: Get the nonce for a certain account - responses: - 200: - description: Plaintext nonce i.e. "4" defaults to "0" /blocks/latest: get: summary: Get the latest block @@ -304,9 +292,14 @@ paths: 200: description: The validator set at the latest block height schema: - type: array - items: - $ref: "#/definitions/Delegate" + type: object + properties: + block_height: + type: number + validators: + type: array + items: + $ref: "#/definitions/Validator" /validatorsets/{height}: parameters: - in: path @@ -322,9 +315,14 @@ paths: 200: description: The validator set at a specific block height schema: - type: array - items: - $ref: "#/definitions/Delegate" + type: object + properties: + block_height: + type: number + validators: + type: array + items: + $ref: "#/definitions/Validator" 404: description: Block at height not available # /txs: @@ -545,11 +543,24 @@ paths: # description: Tx was send and will probably be added to the next block # 400: # description: The Tx was malformated - + definitions: Address: type: string - example: DF096FDE8D380FA5B2AD20DB2962C82DDEA1ED9B + description: bech32 encoded addres + example: cosmosaccaddr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + ValidatorAddress: + type: string + description: bech32 encoded addres + example: cosmosvaladdr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + PubKey: + type: string + description: bech32 encoded public key + example: cosmosaccpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + ValidatorPubKey: + type: string + description: bech32 encoded public key + example: cosmosvalpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq Coins: type: object properties: @@ -630,7 +641,7 @@ definitions: Sig: type: string default: '' - Pubkey: + Pubkey: type: string default: '' TxSigned: @@ -652,16 +663,6 @@ definitions: example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958 Pubkey: $ref: "#/definitions/PubKey" - PubKey: - type: object - properties: - type: - type: string - enum: - - ed25519 - data: - type: string - example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958 Account: type: object properties: @@ -704,17 +705,17 @@ definitions: properties: header: type: object - properties: + properties: chain_id: type: string example: gaia-2 - height: + height: type: number example: 1 - time: + time: type: string example: '2017-12-30T05:53:09.287+01:00' - num_txs: + num_txs: type: number example: 0 last_block_id: @@ -753,17 +754,19 @@ definitions: type: array items: type: object - Delegate: + Validator: type: object properties: + address: + $ref: '#/definitions/ValidatorAddress' pub_key: - $ref: "#/definitions/PubKey" + $ref: "#/definitions/ValidatorPubKey" power: type: number example: 1000 - name: - type: string - example: "159.89.3.34" + accum: + type: number + example: 1000 # Added by API Auto Mocking Plugin host: virtserver.swaggerhub.com basePath: /faboweb1/Cosmos-LCD-2/1.0.0 diff --git a/docs/staking/intro.rst b/docs/staking/intro.rst index 7b5acb986..00a68811a 100644 --- a/docs/staking/intro.rst +++ b/docs/staking/intro.rst @@ -203,7 +203,7 @@ where the ``--sequence`` flag is to be incremented for each transaction, the ``- :: - Please enter passphrase for alice: + Please enter passphrase for alice: { "check_tx": { "gas": 30 @@ -250,7 +250,7 @@ First, we need the pub_key data: :: - cat $HOME/.gaia2/priv_validator.json + cat $HOME/.gaia2/priv_validator.json the first part will look like: @@ -260,17 +260,17 @@ the first part will look like: and you want the ``pub_key`` ``data`` that starts with ``96864CE``. -Now ``bob`` can declare candidacy to that pubkey: +Now ``bob`` can create a validator with that pubkey. :: - gaiacli declare-candidacy --amount=10mycoin --name=bob --pubkey= --moniker=bobby + gaiacli stake create-validator --amount=10mycoin --name=bob --address-validator=
--pub-key= --moniker=bobby with an output like: :: - Please enter passphrase for bob: + Please enter passphrase for bob: { "check_tx": { "gas": 30 @@ -285,7 +285,7 @@ We should see ``bob``'s account balance decrease by 10 mycoin: :: - gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985 + gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985 To confirm for certain the new validator is active, ask the tendermint node: @@ -306,19 +306,19 @@ First let's have ``alice`` send some coins to ``charlie``: :: - gaiacli tx --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF + gaiacli send --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF Then ``charlie`` will delegate some mycoin to ``bob``: :: - gaiacli tx delegate --amount=10mycoin --name=charlie --pubkey= + gaiacli stake delegate --amount=10mycoin --address-delegator= --address-validator= --name=charlie You'll see output like: :: - Please enter passphrase for charlie: + Please enter passphrase for charlie: { "check_tx": { "gas": 30 @@ -334,7 +334,7 @@ To get more information about the candidate, try: :: - gaiacli query candidate --pubkey= + gaiacli stake validator
and you'll see output similar to: @@ -367,7 +367,7 @@ It's also possible the query the delegator's bond like so: :: - gaiacli query delegator-bond --delegator-address 48F74F48281C89E5E4BE9092F735EA519768E8EF --pubkey 52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B + gaiacli stake delegation --address-delegator=
--address-validator=
with an output similar to: @@ -383,9 +383,9 @@ with an output similar to: "Shares": 20 } } - -where the ``--delegator-address`` is ``charlie``'s address and the ``-pubkey`` is the same as we've been using. + +where the ``--address-delegator`` is ``charlie``'s address and the ``--address-validator`` is ``bob``'s address. Unbonding @@ -396,7 +396,7 @@ your VotingPower reduce and your account balance increase. :: - gaiacli unbond --amount=5mycoin --name=charlie --pubkey= + gaiacli stake unbond --amount=5mycoin --name=charlie --address-delegator=
--address-validator=
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF -See the bond decrease with ``gaiacli query delegator-bond`` like above. +See the bond decrease with ``gaiacli stake delegation`` like above. diff --git a/docs/staking/testnet.rst b/docs/staking/testnet.rst index 92aa93eb2..4fca09c4a 100644 --- a/docs/staking/testnet.rst +++ b/docs/staking/testnet.rst @@ -16,7 +16,7 @@ First, generate a couple of genesis transactions to be incorparated into the gen gaiacli keys list **Note:** If you've already run these tests you may need to overwrite keys using the ``--OWK`` flag -When you list the keys you should see two addresses, we'll need these later so take note. +When you list the keys you should see two addresses, we'll need these later so take note. Now let's actually create the genesis files for both nodes: :: @@ -44,7 +44,7 @@ Nice. We can also lookup the validator set: :: - gaiacli validatorset + gaiacli advanced tendermint validator-set Then, we try to transfer some ``steak`` to another account: @@ -72,7 +72,7 @@ Finally, to relinquish all your power, unbond some coins. You should see your Vo :: - gaiacli unbond --chain-id= --name=test + gaiacli stake unbond --chain-id= --name=test That's it! diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 086fa32b3..fdb0638b6 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -4,6 +4,7 @@ import ( "encoding/json" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -14,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/examples/basecoin/types" @@ -29,10 +31,11 @@ type BasecoinApp struct { cdc *wire.Codec // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - keyStake *sdk.KVStoreKey + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -40,6 +43,7 @@ type BasecoinApp struct { coinKeeper bank.Keeper ibcMapper ibc.Mapper stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper } func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { @@ -49,12 +53,13 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // Create your application object. var app = &BasecoinApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), - cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - keyStake: sdk.NewKVStoreKey("stake"), + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), } // Define the accountMapper. @@ -68,6 +73,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { app.coinKeeper = bank.NewKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). @@ -78,8 +84,10 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // Initialize BaseApp. app.SetInitChainer(app.initChainer) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -94,6 +102,7 @@ func MakeCodec() *wire.Codec { sdk.RegisterWire(cdc) // Register Msgs bank.RegisterWire(cdc) stake.RegisterWire(cdc) + slashing.RegisterWire(cdc) ibc.RegisterWire(cdc) // register custom AppAccount @@ -102,6 +111,24 @@ func MakeCodec() *wire.Codec { return cdc } +// application updates every end block +func (app *BasecoinApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +func (app *BasecoinApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } +} + // Custom logic for basecoin initialization func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { stateJSON := req.AppStateBytes @@ -121,11 +148,15 @@ func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) } app.accountMapper.SetAccount(ctx, acc) } + + // load the initial stake information + stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + return abci.ResponseInitChain{} } // Custom logic for state export -func (app *BasecoinApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -143,5 +174,10 @@ func (app *BasecoinApp) ExportAppStateJSON() (appState json.RawMessage, err erro genState := types.GenesisState{ Accounts: accounts, } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, err } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index e297288d3..3027a8470 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -11,9 +11,11 @@ import ( "github.com/cosmos/cosmos-sdk/examples/basecoin/types" 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" "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -85,6 +87,30 @@ var ( } ) +func setGenesis(bapp *BasecoinApp, accs ...auth.BaseAccount) error { + genaccs := make([]*types.GenesisAccount, len(accs)) + for i, acc := range accs { + genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, accName}) + } + + genesisState := types.GenesisState{ + Accounts: genaccs, + StakeData: stake.DefaultGenesisState(), + } + + stateBytes, err := wire.MarshalJSONIndent(bapp.cdc, genesisState) + if err != nil { + return err + } + + // Initialize the chain + vals := []abci.Validator{} + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) + bapp.Commit() + + return nil +} + func loggerAndDB() (log.Logger, dbm.DB) { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() @@ -96,33 +122,11 @@ func newBasecoinApp() *BasecoinApp { return NewBasecoinApp(logger, db) } -func setGenesisAccounts(bapp *BasecoinApp, accs ...auth.BaseAccount) error { - genaccs := make([]*types.GenesisAccount, len(accs)) - for i, acc := range accs { - genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, accName}) - } - - genesisState := types.GenesisState{ - Accounts: genaccs, - } - - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - if err != nil { - return err - } - - // Initialize the chain - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) - bapp.Commit() - - return nil -} - //_______________________________________________________________________ func TestMsgs(t *testing.T) { bapp := newBasecoinApp() + require.Nil(t, setGenesis(bapp)) msgs := []struct { msg sdk.Msg @@ -161,7 +165,7 @@ func TestSortGenesis(t *testing.T) { // Initialize the chain vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, []byte(genState)}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: []byte(genState)}) bapp.Commit() // Unsorted coins means invalid @@ -193,8 +197,8 @@ func TestGenesis(t *testing.T) { } acc := &types.AppAccount{baseAcc, "foobart"} - err = setGenesisAccounts(bapp, baseAcc) - assert.Nil(t, err) + err = setGenesis(bapp, baseAcc) + require.Nil(t, err) // A checkTx context ctx := bapp.BaseApp.NewContext(true, abci.Header{}) @@ -222,8 +226,9 @@ func TestMsgChangePubKey(t *testing.T) { } // Construct genesis state - err = setGenesisAccounts(bapp, baseAcc) - assert.Nil(t, err) + err = setGenesis(bapp, baseAcc) + require.Nil(t, err) + // A checkTx context (true) ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) @@ -276,8 +281,9 @@ func TestMsgSendWithAccounts(t *testing.T) { } // Construct genesis state - err = setGenesisAccounts(bapp, baseAcc) - assert.Nil(t, err) + err = setGenesis(bapp, baseAcc) + require.Nil(t, err) + // A checkTx context (true) ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) @@ -320,8 +326,9 @@ func TestMsgSendMultipleOut(t *testing.T) { Coins: genCoins, } - err = setGenesisAccounts(bapp, acc1, acc2) - assert.Nil(t, err) + // Construct genesis state + err = setGenesis(bapp, acc1, acc2) + require.Nil(t, err) // Simulate a Block SignCheckDeliver(t, bapp, sendMsg2, []int64{0}, true, priv1) @@ -353,7 +360,7 @@ func TestSengMsgMultipleInOut(t *testing.T) { Coins: genCoins, } - err = setGenesisAccounts(bapp, acc1, acc2, acc4) + err = setGenesis(bapp, acc1, acc2, acc4) assert.Nil(t, err) // CheckDeliver @@ -377,7 +384,11 @@ func TestMsgSendDependent(t *testing.T) { Coins: genCoins, } - err = setGenesisAccounts(bapp, acc1) + // Construct genesis state + err = setGenesis(bapp, acc1) + require.Nil(t, err) + + err = setGenesis(bapp, acc1) assert.Nil(t, err) // CheckDeliver @@ -416,7 +427,7 @@ func TestMsgQuiz(t *testing.T) { // Initialize the chain (nil) vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context (true) @@ -438,8 +449,9 @@ func TestIBCMsgs(t *testing.T) { } acc1 := &types.AppAccount{baseAcc, "foobart"} - err := setGenesisAccounts(bapp, baseAcc) + err := setGenesis(bapp, baseAcc) assert.Nil(t, err) + // A checkTx context (true) ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index f5385b559..6540af38a 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -59,8 +59,8 @@ func main() { bankcmd.SendTxCmd(cdc), ibccmd.IBCTransferCmd(cdc), ibccmd.IBCRelayCmd(cdc), - stakecmd.GetCmdDeclareCandidacy(cdc), - stakecmd.GetCmdEditCandidacy(cdc), + stakecmd.GetCmdCreateValidator(cdc), + stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), stakecmd.GetCmdUnbond(cdc), )...) diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 4e6a30a08..4a6498e1b 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -27,7 +28,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit, server.ConstructAppCreator(newApp, "basecoin"), - server.ConstructAppExporter(exportAppState, "basecoin")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "basecoin")) // prepare and add flags rootDir := os.ExpandEnv("$HOME/.basecoind") @@ -39,7 +40,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewBasecoinApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { bapp := app.NewBasecoinApp(logger, db) - return bapp.ExportAppStateJSON() + return bapp.ExportAppStateAndValidators() } diff --git a/examples/basecoin/types/account.go b/examples/basecoin/types/account.go index 223e0b9eb..43a8e2e38 100644 --- a/examples/basecoin/types/account.go +++ b/examples/basecoin/types/account.go @@ -4,6 +4,7 @@ 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/stake" ) var _ auth.Account = (*AppAccount)(nil) @@ -41,7 +42,8 @@ func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { // State to Unmarshal type GenesisState struct { - Accounts []*GenesisAccount `json:"accounts"` + Accounts []*GenesisAccount `json:"accounts"` + StakeData stake.GenesisState `json:"stake"` } // GenesisAccount doesn't need pubkey or sequence diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 2075a64da..dc7c3c9d8 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -4,6 +4,7 @@ import ( "encoding/json" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -154,7 +155,7 @@ func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keep } // Custom logic for state export -func (app *DemocoinApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -174,5 +175,9 @@ func (app *DemocoinApp) ExportAppStateJSON() (appState json.RawMessage, err erro POWGenesis: pow.WriteGenesis(ctx, app.powKeeper), CoolGenesis: cool.WriteGenesis(ctx, app.coolKeeper), } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + return appState, validators, nil } diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index e025c5062..ba041bcff 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -142,7 +142,7 @@ func TestGenesis(t *testing.T) { stateBytes, err := json.MarshalIndent(genesisState, "", "\t") vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context @@ -184,7 +184,7 @@ func TestMsgSendWithAccounts(t *testing.T) { // Initialize the chain vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context (true) @@ -262,7 +262,7 @@ func TestMsgMine(t *testing.T) { // Initialize the chain (nil) vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context (true) @@ -309,7 +309,7 @@ func TestMsgQuiz(t *testing.T) { // Initialize the chain (nil) vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context (true) @@ -356,7 +356,7 @@ func TestHandler(t *testing.T) { } stateBytes, err := json.MarshalIndent(genesisState, "", "\t") require.Nil(t, err) - bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) + bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) bapp.Commit() // A checkTx context (true) diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 3283da58a..76d29075e 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -46,9 +47,9 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewDemocoinApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { dapp := app.NewDemocoinApp(logger, db) - return dapp.ExportAppStateJSON() + return dapp.ExportAppStateAndValidators() } func main() { @@ -63,7 +64,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, CoolAppInit, server.ConstructAppCreator(newApp, "democoin"), - server.ConstructAppExporter(exportAppState, "democoin")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "democoin")) // prepare and add flags rootDir := os.ExpandEnv("$HOME/.democoind") diff --git a/examples/democoin/x/simplestake/handler.go b/examples/democoin/x/simplestake/handler.go index a84e8b07b..a94da3d5e 100644 --- a/examples/democoin/x/simplestake/handler.go +++ b/examples/democoin/x/simplestake/handler.go @@ -2,6 +2,7 @@ package simplestake import ( abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -27,7 +28,7 @@ func handleMsgBond(ctx sdk.Context, k Keeper, msg MsgBond) sdk.Result { } valSet := abci.Validator{ - PubKey: msg.PubKey.Bytes(), + PubKey: tmtypes.TM2PB.PubKey(msg.PubKey), Power: power, } @@ -44,7 +45,7 @@ func handleMsgUnbond(ctx sdk.Context, k Keeper, msg MsgUnbond) sdk.Result { } valSet := abci.Validator{ - PubKey: pubKey.Bytes(), + PubKey: tmtypes.TM2PB.PubKey(pubKey), Power: int64(0), } diff --git a/examples/democoin/x/simplestake/keeper_test.go b/examples/democoin/x/simplestake/keeper_test.go index 515c19cc5..15bd14c79 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/examples/democoin/x/simplestake/keeper_test.go @@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) addr := sdk.Address([]byte("some-address")) bi := stakeKeeper.getBondInfo(ctx, addr) diff --git a/examples/democoin/x/simplestake/msgs.go b/examples/democoin/x/simplestake/msgs.go index 01797a65b..605f6f4e2 100644 --- a/examples/democoin/x/simplestake/msgs.go +++ b/examples/democoin/x/simplestake/msgs.go @@ -26,7 +26,7 @@ func NewMsgBond(addr sdk.Address, stake sdk.Coin, pubKey crypto.PubKey) MsgBond } //nolint -func (msg MsgBond) Type() string { return moduleName } //TODO update "stake/declarecandidacy" +func (msg MsgBond) Type() string { return moduleName } //TODO update "stake/createvalidator" func (msg MsgBond) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } // basic validation of the bond message @@ -65,7 +65,7 @@ func NewMsgUnbond(addr sdk.Address) MsgUnbond { } //nolint -func (msg MsgUnbond) Type() string { return moduleName } //TODO update "stake/declarecandidacy" +func (msg MsgUnbond) Type() string { return moduleName } //TODO update "stake/createvalidator" func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil } diff --git a/server/constructors.go b/server/constructors.go index 3d6950062..c91c67a18 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -5,6 +5,7 @@ import ( "path/filepath" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" ) @@ -13,8 +14,8 @@ import ( // and other flags (?) to start type AppCreator func(string, log.Logger) (abci.Application, error) -// AppExporter dumps all app state to JSON-serializable structure -type AppExporter func(home string, log log.Logger) (json.RawMessage, error) +// AppExporter dumps all app state to JSON-serializable structure and returns the current validator set +type AppExporter func(home string, log log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error) // ConstructAppCreator returns an application generation function func ConstructAppCreator(appFn func(log.Logger, dbm.DB) abci.Application, name string) AppCreator { @@ -30,12 +31,12 @@ func ConstructAppCreator(appFn func(log.Logger, dbm.DB) abci.Application, name s } // ConstructAppExporter returns an application export function -func ConstructAppExporter(appFn func(log.Logger, dbm.DB) (json.RawMessage, error), name string) AppExporter { - return func(rootDir string, logger log.Logger) (json.RawMessage, error) { +func ConstructAppExporter(appFn func(log.Logger, dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error), name string) AppExporter { + return func(rootDir string, logger log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error) { dataDir := filepath.Join(rootDir, "data") db, err := dbm.NewGoLevelDB(name, dataDir) if err != nil { - return nil, err + return nil, nil, err } return appFn(logger, db) } diff --git a/server/export.go b/server/export.go index dad2df9cc..794235f62 100644 --- a/server/export.go +++ b/server/export.go @@ -18,7 +18,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co Short: "Export state to JSON", RunE: func(cmd *cobra.Command, args []string) error { home := viper.GetString("home") - appState, err := appExporter(home, ctx.Logger) + appState, validators, err := appExporter(home, ctx.Logger) if err != nil { return errors.Errorf("Error exporting state: %v\n", err) } @@ -27,6 +27,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co return err } doc.AppStateJSON = appState + doc.Validators = validators encoded, err := wire.MarshalJSONIndent(cdc, doc) if err != nil { return err diff --git a/server/start_test.go b/server/start_test.go index 3bf2eac7e..454f2d492 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -37,7 +37,9 @@ func TestStartStandAlone(t *testing.T) { app, err := mock.NewApp(home, logger) require.Nil(t, err) - svr, err := server.NewServer(FreeTCPAddr(t), "socket", app) + svrAddr, _, err := FreeTCPAddr() + require.Nil(t, err) + svr, err := server.NewServer(svrAddr, "socket", app) require.Nil(t, err, "Error creating listener") svr.SetLogger(logger.With("module", "abci-server")) svr.Start() @@ -69,7 +71,9 @@ func TestStartWithTendermint(t *testing.T) { // set up app and start up viper.Set(flagWithTendermint, true) startCmd := StartCmd(ctx, mock.NewApp) - startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address + svrAddr, _, err := FreeTCPAddr() + require.NoError(t, err) + startCmd.Flags().Set(flagAddress, svrAddr) // set to a new free address timeout := time.Duration(5) * time.Second close(RunOrTimeout(startCmd, timeout, t)) diff --git a/server/test_helpers.go b/server/test_helpers.go index 382c77878..b1050bc6f 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -16,14 +16,17 @@ import ( // Get a free address for a test tendermint server // protocol is either tcp, http, etc -func FreeTCPAddr(t *testing.T) string { +func FreeTCPAddr() (addr, port string, err error) { l, err := net.Listen("tcp", "0.0.0.0:0") defer l.Close() - require.Nil(t, err) + if err != nil { + return "", "", err + } - port := l.Addr().(*net.TCPAddr).Port - addr := fmt.Sprintf("tcp://0.0.0.0:%d", port) - return addr + portI := l.Addr().(*net.TCPAddr).Port + port = fmt.Sprintf("%d", portI) + addr = fmt.Sprintf("tcp://0.0.0.0:%s", port) + return } // setupViper creates a homedir to run inside, diff --git a/server/tm_cmds.go b/server/tm_cmds.go index fcec57f09..60eb5a2fa 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -53,11 +53,11 @@ func ShowValidatorCmd(ctx *Context) *cobra.Command { fmt.Println(string(pubKeyJSONBytes)) return nil } - addr, err := sdk.Bech32CosmosifyValPub(valPubKey) + pubkey, err := sdk.Bech32ifyValPub(valPubKey) if err != nil { return err } - fmt.Println(addr) + fmt.Println(pubkey) return nil }, } diff --git a/server/util.go b/server/util.go index f44cc2d72..9e705f879 100644 --- a/server/util.go +++ b/server/util.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" @@ -72,13 +73,24 @@ func AddCommands( rootCmd.PersistentFlags().String("log_level", ctx.Config.LogLevel, "Log level") + tendermintCmd := &cobra.Command{ + Use: "tendermint", + Short: "Tendermint subcommands", + } + + tendermintCmd.AddCommand( + ShowNodeIDCmd(ctx), + ShowValidatorCmd(ctx), + ) + rootCmd.AddCommand( InitCmd(ctx, cdc, appInit), StartCmd(ctx, appCreator), UnsafeResetAllCmd(ctx), - ShowNodeIDCmd(ctx), - ShowValidatorCmd(ctx), + client.LineBreak, + tendermintCmd, ExportCmd(ctx, cdc, appExport), + client.LineBreak, version.VersionCmd, ) } diff --git a/tests/gobash.go b/tests/gobash.go index baa84f417..f46bad3c1 100644 --- a/tests/gobash.go +++ b/tests/gobash.go @@ -1,51 +1,91 @@ package tests import ( - "io" - "os/exec" "strings" "testing" - "time" "github.com/stretchr/testify/require" + cmn "github.com/tendermint/tmlibs/common" ) -func getCmd(t *testing.T, command string) *exec.Cmd { +// Execute the command, return stdout, logging stdout/err to t. +func ExecuteT(t *testing.T, cmd string) (out string) { + t.Log("Running", cmn.Cyan(cmd)) - //split command into command and args - split := strings.Split(command, " ") + // Split cmd to name and args. + split := strings.Split(cmd, " ") require.True(t, len(split) > 0, "no command provided") - - var cmd *exec.Cmd - if len(split) == 1 { - cmd = exec.Command(split[0]) - } else { - cmd = exec.Command(split[0], split[1:]...) + name, args := split[0], []string(nil) + if len(split) > 1 { + args = split[1:] } - return cmd -} -// Execute the command, return standard output and error, try a few times if requested -func ExecuteT(t *testing.T, command string) (out string) { - cmd := getCmd(t, command) - bz, err := cmd.CombinedOutput() - if err != nil { - panic(err) + // Start process and wait. + proc, err := StartProcess("", name, args, nil, nil) + require.NoError(t, err) + proc.Wait() + + // Get the output. + outbz := proc.StdoutBuffer.Bytes() + errbz := proc.StderrBuffer.Bytes() + + // Log output. + if len(outbz) > 0 { + t.Log("Stdout:", cmn.Green(string(outbz))) } - require.NoError(t, err, string(bz)) - out = strings.Trim(string(bz), "\n") //trim any new lines - time.Sleep(time.Second) + if len(errbz) > 0 { + t.Log("Stderr:", cmn.Red(string(errbz))) + } + + // Collect STDOUT output. + out = strings.Trim(string(outbz), "\n") //trim any new lines return out } -// Asynchronously execute the command, return standard output and error -func GoExecuteT(t *testing.T, command string) (cmd *exec.Cmd, pipeIn io.WriteCloser, pipeOut io.ReadCloser) { - cmd = getCmd(t, command) - pipeIn, err := cmd.StdinPipe() +// Execute the command, launch goroutines to log stdout/err to t. +// Caller should wait for .Wait() or .Stop() to terminate. +func GoExecuteT(t *testing.T, cmd string) (proc *Process) { + t.Log("Running", cmn.Cyan(cmd)) + + // Split cmd to name and args. + split := strings.Split(cmd, " ") + require.True(t, len(split) > 0, "no command provided") + name, args := split[0], []string(nil) + if len(split) > 1 { + args = split[1:] + } + + // Start process. + proc, err := StartProcess("", name, args, nil, nil) require.NoError(t, err) - pipeOut, err = cmd.StdoutPipe() - require.NoError(t, err) - cmd.Start() - time.Sleep(time.Second) - return cmd, pipeIn, pipeOut + + // Run goroutines to log stdout. + go func() { + buf := make([]byte, 10240) // TODO Document the effects. + for { + n, err := proc.StdoutBuffer.Read(buf) + if err != nil { + return + } + if n > 0 { + t.Log("Stdout:", cmn.Green(string(buf[:n]))) + } + } + }() + + // Run goroutines to log stderr. + go func() { + buf := make([]byte, 10240) // TODO Document the effects. + for { + n, err := proc.StderrBuffer.Read(buf) + if err != nil { + return + } + if n > 0 { + t.Log("Stderr:", cmn.Red(string(buf[:n]))) + } + } + }() + + return proc } diff --git a/tests/process.go b/tests/process.go new file mode 100644 index 000000000..d2459a0b5 --- /dev/null +++ b/tests/process.go @@ -0,0 +1,110 @@ +package tests + +import ( + "bytes" + "io" + "os" + "os/exec" + "time" +) + +// execution process +type Process struct { + ExecPath string + Args []string + Pid int + StartTime time.Time + EndTime time.Time + Cmd *exec.Cmd `json:"-"` + ExitState *os.ProcessState `json:"-"` + WaitCh chan struct{} `json:"-"` + StdinPipe io.WriteCloser `json:"-"` + StdoutBuffer *bytes.Buffer `json:"-"` + StderrBuffer *bytes.Buffer `json:"-"` +} + +// dir: The working directory. If "", os.Getwd() is used. +// name: Command name +// args: Args to command. (should not include name) +// outFile, errFile: If not nil, will use, otherwise new Buffers will be +// allocated. Either way, Process.Cmd.StdoutPipe and Process.Cmd.StderrPipe will be nil +// respectively. +func StartProcess(dir string, name string, args []string, outFile, errFile io.WriteCloser) (*Process, error) { + var cmd = exec.Command(name, args...) // is not yet started. + // cmd dir + if dir == "" { + pwd, err := os.Getwd() + if err != nil { + panic(err) + } + cmd.Dir = pwd + } else { + cmd.Dir = dir + } + // cmd stdin + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // cmd stdout, stderr + var outBuffer, errBuffer *bytes.Buffer + if outFile != nil { + cmd.Stdout = outFile + } else { + outBuffer = bytes.NewBuffer(nil) + cmd.Stdout = outBuffer + } + if errFile != nil { + cmd.Stderr = errFile + } else { + errBuffer = bytes.NewBuffer(nil) + cmd.Stderr = errBuffer + } + // cmd start + if err := cmd.Start(); err != nil { + return nil, err + } + proc := &Process{ + ExecPath: name, + Args: args, + Pid: cmd.Process.Pid, + StartTime: time.Now(), + Cmd: cmd, + ExitState: nil, + WaitCh: make(chan struct{}), + StdinPipe: stdin, + } + if outBuffer != nil { + proc.StdoutBuffer = outBuffer + } + if errBuffer != nil { + proc.StderrBuffer = errBuffer + } + go func() { + err := proc.Cmd.Wait() + if err != nil { + // fmt.Printf("Process exit: %v\n", err) + if exitError, ok := err.(*exec.ExitError); ok { + proc.ExitState = exitError.ProcessState + } + } + proc.ExitState = proc.Cmd.ProcessState + proc.EndTime = time.Now() // TODO make this goroutine-safe + close(proc.WaitCh) + }() + return proc, nil +} + +// stop the process +func (proc *Process) Stop(kill bool) error { + if kill { + // fmt.Printf("Killing process %v\n", proc.Cmd.Process) + return proc.Cmd.Process.Kill() + } + return proc.Cmd.Process.Signal(os.Interrupt) +} + +// wait for the process +func (proc *Process) Wait() { + <-proc.WaitCh +} diff --git a/tests/tests.go b/tests/tests.go index 845ac6925..b4435659b 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -9,7 +9,6 @@ import ( "path" "path/filepath" "strings" - //"strings" "testing" "time" @@ -239,7 +238,9 @@ func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { // expects TestInitBaseCoin to have been run func StartLCDServerForTest(t *testing.T, home, chainID string) (cmd *exec.Cmd, port string) { cmdName := whereIsBasecli() - port = strings.Split(server.FreeTCPAddr(t), ":")[2] + var err error + _, port, err = server.FreeTCPAddr() + require.NoError(t, err) cmdArgs := []string{ "rest-server", "--home", @@ -252,7 +253,7 @@ func StartLCDServerForTest(t *testing.T, home, chainID string) (cmd *exec.Cmd, p cmd = exec.Command(cmdName, cmdArgs...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Start() + err = cmd.Start() require.Nil(t, err) time.Sleep(time.Second * 2) // TODO: LOL return cmd, port diff --git a/tests/util.go b/tests/util.go index a6f026f24..292cbab06 100644 --- a/tests/util.go +++ b/tests/util.go @@ -11,16 +11,22 @@ import ( rpcclient "github.com/tendermint/tendermint/rpc/lib/client" ) -// TODO: these functions just print to Stdout. -// consider using the logger. - // Uses localhost func WaitForHeight(height int64, port string) { for { - var resultBlock ctypes.ResultBlock - url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest") - res, err := http.Get(url) + url := fmt.Sprintf("http://localhost:%v/blocks/latest", port) + + // get url, try a few times + var res *http.Response + var err error + for i := 0; i < 5; i++ { + res, err = http.Get(url) + if err == nil { + break + } + time.Sleep(time.Second) + } if err != nil { panic(err) } @@ -31,6 +37,7 @@ func WaitForHeight(height int64, port string) { } res.Body.Close() + var resultBlock ctypes.ResultBlock err = cdc.UnmarshalJSON([]byte(body), &resultBlock) if err != nil { fmt.Println("RES", res) @@ -45,45 +52,35 @@ func WaitForHeight(height int64, port string) { } } -// wait for 2 blocks. -// uses localhost +// wait for tendermint to start func WaitForStart(port string) { - waitHeight := int64(2) - for { + var err error + for i := 0; i < 5; i++ { time.Sleep(time.Second) - url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest") - res, err := http.Get(url) - if err != nil { - panic(err) + url := fmt.Sprintf("http://localhost:%v/blocks/latest", port) + + // get url, try a few times + var res *http.Response + res, err = http.Get(url) + if err == nil || res == nil { + continue } // waiting for server to start ... if res.StatusCode != http.StatusOK { res.Body.Close() - continue - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - panic(err) - } - res.Body.Close() - - resultBlock := new(ctypes.ResultBlock) - err = cdc.UnmarshalJSON([]byte(body), &resultBlock) - if err != nil { - fmt.Println("RES", res) - fmt.Println("BODY", string(body)) - panic(err) - } - - if resultBlock.Block.Height >= waitHeight { return } } + if err != nil { + panic(err) + } } +// TODO: these functions just print to Stdout. +// consider using the logger. + // Wait for the RPC server to respond to /status func WaitForRPC(laddr string) { fmt.Println("LADDR", laddr) diff --git a/types/account.go b/types/account.go index b593dfb53..00d180a1c 100644 --- a/types/account.go +++ b/types/account.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - bech32cosmos "github.com/cosmos/bech32cosmos/go" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/bech32" cmn "github.com/tendermint/tmlibs/common" ) @@ -21,24 +21,24 @@ const ( Bech32PrefixValPub = "cosmosvalpub" ) -// Bech32CosmosifyAcc takes Address and returns the Bech32Cosmos encoded string -func Bech32CosmosifyAcc(addr Address) (string, error) { - return bech32cosmos.ConvertAndEncode(Bech32PrefixAccAddr, addr.Bytes()) +// Bech32ifyAcc takes Address and returns the bech32 encoded string +func Bech32ifyAcc(addr Address) (string, error) { + return bech32.ConvertAndEncode(Bech32PrefixAccAddr, addr.Bytes()) } -// Bech32CosmosifyAccPub takes AccountPubKey and returns the Bech32Cosmos encoded string -func Bech32CosmosifyAccPub(pub crypto.PubKey) (string, error) { - return bech32cosmos.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes()) +// Bech32ifyAccPub takes AccountPubKey and returns the bech32 encoded string +func Bech32ifyAccPub(pub crypto.PubKey) (string, error) { + return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes()) } -// Bech32CosmosifyVal returns the Bech32Cosmos encoded string for a validator address -func Bech32CosmosifyVal(addr Address) (string, error) { - return bech32cosmos.ConvertAndEncode(Bech32PrefixValAddr, addr.Bytes()) +// Bech32ifyVal returns the bech32 encoded string for a validator address +func Bech32ifyVal(addr Address) (string, error) { + return bech32.ConvertAndEncode(Bech32PrefixValAddr, addr.Bytes()) } -// Bech32CosmosifyValPub returns the Bech32Cosmos encoded string for a validator pubkey -func Bech32CosmosifyValPub(pub crypto.PubKey) (string, error) { - return bech32cosmos.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) +// Bech32ifyValPub returns the bech32 encoded string for a validator pubkey +func Bech32ifyValPub(pub crypto.PubKey) (string, error) { + return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) } // create an Address from a string @@ -54,14 +54,29 @@ func GetAccAddressHex(address string) (addr Address, err error) { } // create an Address from a string -func GetAccAddressBech32Cosmos(address string) (addr Address, err error) { - bz, err := getFromBech32Cosmos(address, Bech32PrefixAccAddr) +func GetAccAddressBech32(address string) (addr Address, err error) { + bz, err := getFromBech32(address, Bech32PrefixAccAddr) if err != nil { return nil, err } return Address(bz), nil } +// create a Pubkey from a string +func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) { + bz, err := getFromBech32(address, Bech32PrefixAccPub) + if err != nil { + return nil, err + } + + pk, err = crypto.PubKeyFromBytes(bz) + if err != nil { + return nil, err + } + + return pk, nil +} + // create an Address from a hex string func GetValAddressHex(address string) (addr Address, err error) { if len(address) == 0 { @@ -74,9 +89,9 @@ func GetValAddressHex(address string) (addr Address, err error) { return Address(bz), nil } -// create an Address from a bech32cosmos string -func GetValAddressBech32Cosmos(address string) (addr Address, err error) { - bz, err := getFromBech32Cosmos(address, Bech32PrefixValAddr) +// create an Address from a bech32 string +func GetValAddressBech32(address string) (addr Address, err error) { + bz, err := getFromBech32(address, Bech32PrefixValAddr) if err != nil { return nil, err } @@ -84,8 +99,8 @@ func GetValAddressBech32Cosmos(address string) (addr Address, err error) { } //Decode a validator publickey into a public key -func GetValPubKeyBech32Cosmos(pubkey string) (pk crypto.PubKey, err error) { - bz, err := getFromBech32Cosmos(pubkey, Bech32PrefixValPub) +func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { + bz, err := getFromBech32(pubkey, Bech32PrefixValPub) if err != nil { return nil, err } @@ -98,11 +113,11 @@ func GetValPubKeyBech32Cosmos(pubkey string) (pk crypto.PubKey, err error) { return pk, nil } -func getFromBech32Cosmos(bech32, prefix string) ([]byte, error) { - if len(bech32) == 0 { +func getFromBech32(bech32str, prefix string) ([]byte, error) { + if len(bech32str) == 0 { return nil, errors.New("must provide non-empty string") } - hrp, bz, err := bech32cosmos.DecodeAndConvert(bech32) + hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { return nil, err } diff --git a/types/context.go b/types/context.go index 4ab0a5d09..c4fc385c7 100644 --- a/types/context.go +++ b/types/context.go @@ -31,6 +31,7 @@ type Context struct { // create a new context func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context { + c := Context{ Context: context.Background(), pst: newThePast(), @@ -43,6 +44,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt c = c.WithIsCheckTx(isCheckTx) c = c.WithTxBytes(txBytes) c = c.WithLogger(logger) + c = c.WithSigningValidators(nil) c = c.WithGasMeter(NewInfiniteGasMeter()) return c } @@ -128,6 +130,7 @@ const ( contextKeyIsCheckTx contextKeyTxBytes contextKeyLogger + contextKeySigningValidators contextKeyGasMeter ) @@ -157,6 +160,9 @@ func (c Context) TxBytes() []byte { func (c Context) Logger() log.Logger { return c.Value(contextKeyLogger).(log.Logger) } +func (c Context) SigningValidators() []abci.SigningValidator { + return c.Value(contextKeySigningValidators).([]abci.SigningValidator) +} func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) } @@ -182,6 +188,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context { func (c Context) WithLogger(logger log.Logger) Context { return c.withValue(contextKeyLogger, logger) } +func (c Context) WithSigningValidators(SigningValidators []abci.SigningValidator) Context { + return c.withValue(contextKeySigningValidators, SigningValidators) +} func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) } diff --git a/types/stake.go b/types/stake.go index 6a1a3a95f..7484295cc 100644 --- a/types/stake.go +++ b/types/stake.go @@ -3,6 +3,7 @@ package types import ( abci "github.com/tendermint/abci/types" "github.com/tendermint/go-crypto" + tmtypes "github.com/tendermint/tendermint/types" ) // status of a validator @@ -31,6 +32,7 @@ func BondStatusToString(b BondStatus) string { // validator for a delegated proof of stake system type Validator interface { + GetMoniker() string // moniker of the validator GetStatus() BondStatus // status of the validator GetOwner() Address // owner address to receive/return validators coins GetPubKey() crypto.PubKey // validation pubkey @@ -41,7 +43,7 @@ type Validator interface { // validator which fulfills abci validator interface for use in Tendermint func ABCIValidator(v Validator) abci.Validator { return abci.Validator{ - PubKey: v.GetPubKey().Bytes(), + PubKey: tmtypes.TM2PB.PubKey(v.GetPubKey()), Power: v.GetPower().Evaluate(), } } @@ -56,8 +58,11 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, Address) Validator // get a particular validator by owner address - TotalPower(Context) Rat // total power of the validator set + Validator(Context, Address) Validator // get a particular validator by owner address + TotalPower(Context) Rat // total power of the validator set + Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction + Revoke(Context, crypto.PubKey) // revoke a validator + Unrevoke(Context, crypto.PubKey) // unrevoke a validator } //_______________________________________________________________________________ diff --git a/types/tags.go b/types/tags.go index 95a826fd7..5a8eb1f47 100644 --- a/types/tags.go +++ b/types/tags.go @@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags { return append(t, a...) } +// Turn tags into KVPair list +func (t Tags) ToKVPairs() []cmn.KVPair { + return []cmn.KVPair(t) +} + // New variadic tags, must be k string, v []byte repeating func NewTags(tags ...interface{}) Tags { var ret Tags diff --git a/types/tx_msg.go b/types/tx_msg.go index 186cf9b24..240db3106 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -35,7 +35,7 @@ type Tx interface { //__________________________________________________________ -// TxDeocder unmarshals transaction bytes +// TxDecoder unmarshals transaction bytes type TxDecoder func(txBytes []byte) (Tx, Error) //__________________________________________________________ diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index 4006baa14..b5af6a686 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -40,7 +40,7 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode // find the key to look up the account addr := args[0] - key, err := sdk.GetAccAddressBech32Cosmos(addr) + key, err := sdk.GetAccAddressBech32(addr) if err != nil { return err } diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index a60ce5cdb..bcae59c20 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -1,7 +1,6 @@ package rest import ( - "encoding/hex" "fmt" "net/http" @@ -26,17 +25,16 @@ func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, sto func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - addr := vars["address"] + bech32addr := vars["address"] - bz, err := hex.DecodeString(addr) + addr, err := sdk.GetAccAddressBech32(bech32addr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - key := sdk.Address(bz) - res, err := ctx.Query(key, storeName) + res, err := ctx.Query(addr, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) diff --git a/x/auth/msgs.go b/x/auth/msgs.go index c449b837b..3eb5cc8ba 100644 --- a/x/auth/msgs.go +++ b/x/auth/msgs.go @@ -1,8 +1,6 @@ package auth import ( - "encoding/json" - sdk "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" ) @@ -30,7 +28,7 @@ func (msg MsgChangeKey) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgChangeKey) GetSignBytes() []byte { - b, err := json.Marshal(msg) // XXX: ensure some canonical form + b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form if err != nil { panic(err) } diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index bc01b0149..4858ae0b4 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -1,8 +1,6 @@ package auth import ( - "encoding/json" - sdk "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" ) @@ -70,7 +68,7 @@ func (fee StdFee) Bytes() []byte { if len(fee.Amount) == 0 { fee.Amount = sdk.Coins{} } - bz, err := json.Marshal(fee) // TODO + bz, err := msgCdc.MarshalJSON(fee) // TODO if err != nil { panic(err) } @@ -95,7 +93,7 @@ type StdSignDoc struct { // StdSignBytes returns the bytes to sign for a transaction. // TODO: change the API to just take a chainID and StdTx ? func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg sdk.Msg) []byte { - bz, err := json.Marshal(StdSignDoc{ + bz, err := msgCdc.MarshalJSON(StdSignDoc{ ChainID: chainID, Sequences: sequences, FeeBytes: fee.Bytes(), diff --git a/x/auth/wire.go b/x/auth/wire.go index 42b34b96d..309464c86 100644 --- a/x/auth/wire.go +++ b/x/auth/wire.go @@ -10,3 +10,10 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) cdc.RegisterConcrete(MsgChangeKey{}, "auth/ChangeKey", nil) } + +var msgCdc = wire.NewCodec() + +func init() { + RegisterWire(msgCdc) + wire.RegisterCrypto(msgCdc) +} diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 5ed56a85b..817c9a174 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -34,7 +34,7 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { toStr := viper.GetString(flagTo) - to, err := sdk.GetAccAddressBech32Cosmos(toStr) + to, err := sdk.GetAccAddressBech32(toStr) if err != nil { return err } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 916aa08fe..83ab3b843 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -11,6 +11,7 @@ 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/bank" "github.com/cosmos/cosmos-sdk/x/bank/client" ) @@ -29,12 +30,25 @@ type sendBody struct { Sequence int64 `json:"sequence"` } +var msgCdc = wire.NewCodec() + +func init() { + bank.RegisterWire(msgCdc) +} + // SendRequestHandlerFn - http request handler to send coins to a address func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // collect data vars := mux.Vars(r) - address := vars["address"] + bech32addr := vars["address"] + + address, err := sdk.GetAccAddressBech32(bech32addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } var m sendBody body, err := ioutil.ReadAll(r.Body) @@ -43,7 +57,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont w.Write([]byte(err.Error())) return } - err = json.Unmarshal(body, &m) + err = msgCdc.UnmarshalJSON(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -57,7 +71,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } - to, err := sdk.GetAccAddressHex(address) + to, err := sdk.GetAccAddressHex(address.String()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) diff --git a/x/bank/msgs.go b/x/bank/msgs.go index de7f2a8b1..7836056de 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -1,8 +1,6 @@ package bank import ( - "encoding/json" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -55,7 +53,7 @@ func (msg MsgSend) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgSend) GetSignBytes() []byte { - b, err := json.Marshal(msg) // XXX: ensure some canonical form + b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form if err != nil { panic(err) } @@ -104,7 +102,7 @@ func (msg MsgIssue) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgIssue) GetSignBytes() []byte { - b, err := json.Marshal(msg) // XXX: ensure some canonical form + b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form if err != nil { panic(err) } diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 78de01f40..8f9791c8d 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -186,8 +186,14 @@ func TestMsgSendGetSignBytes(t *testing.T) { Outputs: []Output{NewOutput(addr2, coins)}, } res := msg.GetSignBytes() + + unmarshaledMsg := &MsgSend{} + msgCdc.UnmarshalJSON(res, unmarshaledMsg) + assert.Equal(t, &msg, unmarshaledMsg) + // TODO bad results - assert.Equal(t, string(res), `{"inputs":[{"address":"696E707574","coins":[{"denom":"atom","amount":10}]}],"outputs":[{"address":"6F7574707574","coins":[{"denom":"atom","amount":10}]}]}`) + expected := `{"type":"EAFDE32A2C87F8","value":{"inputs":[{"address":"696E707574","coins":[{"denom":"atom","amount":10}]}],"outputs":[{"address":"6F7574707574","coins":[{"denom":"atom","amount":10}]}]}}` + assert.Equal(t, expected, string(res)) } func TestMsgSendGetSigners(t *testing.T) { @@ -255,8 +261,14 @@ func TestMsgIssueGetSignBytes(t *testing.T) { Outputs: []Output{NewOutput(addr, coins)}, } res := msg.GetSignBytes() + + unmarshaledMsg := &MsgIssue{} + msgCdc.UnmarshalJSON(res, unmarshaledMsg) + assert.Equal(t, &msg, unmarshaledMsg) + // TODO bad results - assert.Equal(t, string(res), `{"banker":"696E707574","outputs":[{"address":"6C6F616E2D66726F6D2D62616E6B","coins":[{"denom":"atom","amount":10}]}]}`) + expected := `{"type":"72E617C06ABAD0","value":{"banker":"696E707574","outputs":[{"address":"6C6F616E2D66726F6D2D62616E6B","coins":[{"denom":"atom","amount":10}]}]}}` + assert.Equal(t, expected, string(res)) } func TestMsgIssueGetSigners(t *testing.T) { diff --git a/x/bank/wire.go b/x/bank/wire.go index fdb6c252b..f468d3e53 100644 --- a/x/bank/wire.go +++ b/x/bank/wire.go @@ -9,3 +9,9 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/Send", nil) cdc.RegisterConcrete(MsgIssue{}, "cosmos-sdk/Issue", nil) } + +var msgCdc = wire.NewCodec() + +func init() { + RegisterWire(msgCdc) +} diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 48a29ee80..d897c6e4f 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -35,7 +35,14 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core // collect data vars := mux.Vars(r) destChainID := vars["destchain"] - address := vars["address"] + bech32addr := vars["address"] + + address, err := sdk.GetAccAddressBech32(bech32addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } var m transferBody body, err := ioutil.ReadAll(r.Body) @@ -58,7 +65,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core return } - bz, err := hex.DecodeString(address) + bz, err := hex.DecodeString(address.String()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) diff --git a/x/slashing/client/cli/flags.go b/x/slashing/client/cli/flags.go new file mode 100644 index 000000000..43f8fa90a --- /dev/null +++ b/x/slashing/client/cli/flags.go @@ -0,0 +1,6 @@ +package cli + +// nolint +const ( + FlagAddressValidator = "address-validator" +) diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go new file mode 100644 index 000000000..948e75667 --- /dev/null +++ b/x/slashing/client/cli/query.go @@ -0,0 +1,57 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/tmlibs/cli" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" // XXX fix + "github.com/cosmos/cosmos-sdk/x/slashing" +) + +// get the command to query signing info +func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "signing-info [validator-pubkey]", + Short: "Query a validator's signing information", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + pk, err := sdk.GetValPubKeyBech32(args[0]) + if err != nil { + return err + } + key := slashing.GetValidatorSigningInfoKey(pk.Address()) + ctx := context.NewCoreContextFromViper() + res, err := ctx.Query(key, storeName) + if err != nil { + return err + } + signingInfo := new(slashing.ValidatorSigningInfo) + cdc.MustUnmarshalBinary(res, signingInfo) + + switch viper.Get(cli.OutputFlag) { + + case "text": + human := signingInfo.HumanReadableString() + fmt.Println(human) + + case "json": + // parse out the signing info + output, err := wire.MarshalJSONIndent(cdc, signingInfo) + if err != nil { + return err + } + fmt.Println(string(output)) + } + + return nil + }, + } + + return cmd +} diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go new file mode 100644 index 000000000..19b1225ba --- /dev/null +++ b/x/slashing/client/cli/tx.go @@ -0,0 +1,42 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/slashing" +) + +// create unrevoke command +func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unrevoke", + Args: cobra.ExactArgs(1), + Short: "unrevoke validator previously revoked for downtime", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + validatorAddr, err := sdk.GetAccAddressBech32(args[0]) + if err != nil { + return err + } + + msg := slashing.NewMsgUnrevoke(validatorAddr) + + // build and sign the transaction, then broadcast to Tendermint + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + return cmd +} diff --git a/x/slashing/errors.go b/x/slashing/errors.go new file mode 100644 index 000000000..087dc0314 --- /dev/null +++ b/x/slashing/errors.go @@ -0,0 +1,52 @@ +//nolint +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Local code type +type CodeType = sdk.CodeType + +const ( + // Default slashing codespace + DefaultCodespace sdk.CodespaceType = 10 + + // Invalid validator + CodeInvalidValidator CodeType = 201 + // Validator jailed + CodeValidatorJailed CodeType = 202 +) + +func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator") +} +func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") +} +func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") +} + +func codeToDefaultMsg(code CodeType) string { + switch code { + case CodeInvalidValidator: + return "Invalid Validator" + case CodeValidatorJailed: + return "Validator Jailed" + default: + return sdk.CodeToDefaultMsg(code) + } +} + +func msgOrDefaultMsg(msg string, code CodeType) string { + if msg != "" { + return msg + } + return codeToDefaultMsg(code) +} + +func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { + msg = msgOrDefaultMsg(msg, code) + return sdk.NewError(codespace, code, msg) +} diff --git a/x/slashing/handler.go b/x/slashing/handler.go new file mode 100644 index 000000000..5994bb8f1 --- /dev/null +++ b/x/slashing/handler.go @@ -0,0 +1,58 @@ +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + // NOTE msg already has validate basic run + switch msg := msg.(type) { + case MsgUnrevoke: + return handleMsgUnrevoke(ctx, msg, k) + default: + return sdk.ErrTxDecode("invalid message parse in staking module").Result() + } + } +} + +// Validators must submit a transaction to unrevoke itself after +// having been revoked (and thus unbonded) for downtime +func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { + + // Validator must exist + validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr) + if validator == nil { + return ErrNoValidatorForAddress(k.codespace).Result() + } + + addr := validator.GetPubKey().Address() + + // Signing info must exist + info, found := k.getValidatorSigningInfo(ctx, addr) + if !found { + return ErrNoValidatorForAddress(k.codespace).Result() + } + + // Cannot be unrevoked until out of jail + if ctx.BlockHeader().Time < info.JailedUntil { + return ErrValidatorJailed(k.codespace).Result() + } + + if ctx.IsCheckTx() { + return sdk.Result{} + } + + // Update the starting height (so the validator can't be immediately revoked again) + info.StartHeight = ctx.BlockHeight() + k.setValidatorSigningInfo(ctx, addr, info) + + // Unrevoke the validator + k.validatorSet.Unrevoke(ctx, validator.GetPubKey()) + + tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes()) + + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go new file mode 100644 index 000000000..d558cc04b --- /dev/null +++ b/x/slashing/keeper.go @@ -0,0 +1,90 @@ +package slashing + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + crypto "github.com/tendermint/go-crypto" +) + +// Keeper of the slashing store +type Keeper struct { + storeKey sdk.StoreKey + cdc *wire.Codec + validatorSet sdk.ValidatorSet + + // codespace + codespace sdk.CodespaceType +} + +// NewKeeper creates a slashing keeper +func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + validatorSet: vs, + codespace: codespace, + } + return keeper +} + +// handle a validator signing two blocks at the same height +func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, pubkey crypto.PubKey) { + logger := ctx.Logger().With("module", "x/slashing") + age := ctx.BlockHeader().Time - timestamp + + // Double sign too old + if age > MaxEvidenceAge { + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) + return + } + + // Double sign confirmed + logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) + k.validatorSet.Slash(ctx, pubkey, height, SlashFractionDoubleSign) +} + +// handle a validator signature, must be called once per validator per block +func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signed bool) { + logger := ctx.Logger().With("module", "x/slashing") + height := ctx.BlockHeight() + if !signed { + logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height)) + } + address := pubkey.Address() + + // Local index, so counts blocks validator *should* have signed + // Will use the 0-value default signing info if not present + signInfo, _ := k.getValidatorSigningInfo(ctx, address) + index := signInfo.IndexOffset % SignedBlocksWindow + signInfo.IndexOffset++ + + // Update signed block bit array & counter + // This counter just tracks the sum of the bit array + // That way we avoid needing to read/write the whole array each time + previous := k.getValidatorSigningBitArray(ctx, address, index) + if previous == signed { + // Array value at this index has not changed, no need to update counter + } else if previous && !signed { + // Array value has changed from signed to unsigned, decrement counter + k.setValidatorSigningBitArray(ctx, address, index, false) + signInfo.SignedBlocksCounter-- + } else if !previous && signed { + // Array value has changed from unsigned to signed, increment counter + k.setValidatorSigningBitArray(ctx, address, index, true) + signInfo.SignedBlocksCounter++ + } + + minHeight := signInfo.StartHeight + SignedBlocksWindow + if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { + // Downtime confirmed, slash, revoke, and jail the validator + logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, MinSignedPerWindow)) + k.validatorSet.Slash(ctx, pubkey, height, SlashFractionDowntime) + k.validatorSet.Revoke(ctx, pubkey) + signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration + } + + // Set the updated signing info + k.setValidatorSigningInfo(ctx, address, signInfo) +} diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go new file mode 100644 index 000000000..25ae1686d --- /dev/null +++ b/x/slashing/keeper_test.go @@ -0,0 +1,131 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +func TestHandleDoubleSign(t *testing.T) { + + // initial setup + ctx, ck, sk, keeper := createTestInput(t) + addr, val, amt := addrs[0], pks[0], int64(100) + got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) + require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + + // double sign less than max age + keeper.handleDoubleSign(ctx, 0, 0, val) + require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) + ctx = ctx.WithBlockHeader(abci.Header{Time: 300}) + + // double sign past max age + keeper.handleDoubleSign(ctx, 0, 0, val) + require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) +} + +func TestHandleAbsentValidator(t *testing.T) { + + // initial setup + ctx, ck, sk, keeper := createTestInput(t) + addr, val, amt := addrs[0], pks[0], int64(100) + sh := stake.NewHandler(sk) + slh := NewHandler(keeper) + got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) + require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + info, found := keeper.getValidatorSigningInfo(ctx, val.Address()) + require.False(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.IndexOffset) + require.Equal(t, int64(0), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.JailedUntil) + height := int64(0) + + // 1000 first blocks OK + for ; height < 1000; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, true) + } + info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, SignedBlocksWindow, info.SignedBlocksCounter) + + // 50 blocks missed + for ; height < 1050; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + } + info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter) + + // validator should be bonded still + validator, _ := sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool := sk.GetPool(ctx) + require.Equal(t, int64(100), pool.BondedTokens) + + // 51st block missed + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) + + // validator should have been revoked + validator, _ = sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Unbonded, validator.GetStatus()) + + // unrevocation should fail prior to jail expiration + got = slh(ctx, NewMsgUnrevoke(addr)) + require.False(t, got.IsOK()) + + // unrevocation should succeed after jail expiration + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)}) + got = slh(ctx, NewMsgUnrevoke(addr)) + require.True(t, got.IsOK()) + + // validator should be rebonded now + validator, _ = sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // validator should have been slashed + pool = sk.GetPool(ctx) + require.Equal(t, int64(99), pool.BondedTokens) + + // validator start height should have been changed + info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) + require.True(t, found) + require.Equal(t, height, info.StartHeight) + require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) + + // validator should not be immediately revoked again + height++ + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + validator, _ = sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // validator should be revoked again after 100 unsigned blocks + nextHeight := height + 100 + for ; height <= nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + } + validator, _ = sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Unbonded, validator.GetStatus()) +} diff --git a/x/slashing/msg.go b/x/slashing/msg.go new file mode 100644 index 000000000..d2676af81 --- /dev/null +++ b/x/slashing/msg.go @@ -0,0 +1,46 @@ +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +var cdc = wire.NewCodec() + +// name to identify transaction types +const MsgType = "slashing" + +// verify interface at compile time +var _ sdk.Msg = &MsgUnrevoke{} + +// MsgUnrevoke - struct for unrevoking revoked validator +type MsgUnrevoke struct { + ValidatorAddr sdk.Address `json:"address"` // address of the validator owner +} + +func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke { + return MsgUnrevoke{ + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgUnrevoke) Type() string { return MsgType } +func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgUnrevoke) GetSignBytes() []byte { + b, err := cdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgUnrevoke) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return ErrBadValidatorAddr(DefaultCodespace) + } + return nil +} diff --git a/x/slashing/params.go b/x/slashing/params.go new file mode 100644 index 000000000..3bba85fa6 --- /dev/null +++ b/x/slashing/params.go @@ -0,0 +1,37 @@ +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // MaxEvidenceAge - Max age for evidence - 21 days (3 weeks) + // TODO Should this be a governance parameter or just modifiable with SoftwareUpgradeProposals? + // MaxEvidenceAge = 60 * 60 * 24 * 7 * 3 + // TODO Temporarily set to 2 minutes for testnets. + MaxEvidenceAge int64 = 60 * 2 + + // SignedBlocksWindow - sliding window for downtime slashing + // TODO Governance parameter? + // TODO Temporarily set to 100 blocks for testnets + SignedBlocksWindow int64 = 100 + + // Downtime slashing threshold - 50% + // TODO Governance parameter? + MinSignedPerWindow int64 = SignedBlocksWindow / 2 + + // Downtime unbond duration + // TODO Governance parameter? + // TODO Temporarily set to 10 minutes for testnets + DowntimeUnbondDuration int64 = 60 * 10 +) + +var ( + // SlashFractionDoubleSign - currently 5% + // TODO Governance parameter? + SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20)) + + // SlashFractionDowntime - currently 1% + // TODO Governance parameter? + SlashFractionDowntime = sdk.NewRat(1).Quo(sdk.NewRat(100)) +) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go new file mode 100644 index 000000000..a2df0505a --- /dev/null +++ b/x/slashing/signing_info.go @@ -0,0 +1,74 @@ +package slashing + +import ( + "encoding/binary" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Stored by *validator* address (not owner address) +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info ValidatorSigningInfo, found bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetValidatorSigningInfoKey(address)) + if bz == nil { + found = false + return + } + k.cdc.MustUnmarshalBinary(bz, &info) + found = true + return +} + +// Stored by *validator* address (not owner address) +func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, info ValidatorSigningInfo) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(info) + store.Set(GetValidatorSigningInfoKey(address), bz) +} + +// Stored by *validator* address (not owner address) +func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64) (signed bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) + if bz == nil { + // lazy: treat empty key as unsigned + signed = false + return + } + k.cdc.MustUnmarshalBinary(bz, &signed) + return +} + +// Stored by *validator* address (not owner address) +func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64, signed bool) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(signed) + store.Set(GetValidatorSigningBitArrayKey(address, index), bz) +} + +// Signing info for a validator +type ValidatorSigningInfo struct { + StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unrevoked + IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array + JailedUntil int64 `json:"jailed_until"` // timestamp validator cannot be unrevoked until + SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time) +} + +// Return human readable signing info +func (i ValidatorSigningInfo) HumanReadableString() string { + return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %d, signed blocks counter: %d", + i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter) +} + +// Stored by *validator* address (not owner address) +func GetValidatorSigningInfoKey(v sdk.Address) []byte { + return append([]byte{0x01}, v.Bytes()...) +} + +// Stored by *validator* address (not owner address) +func GetValidatorSigningBitArrayKey(v sdk.Address, i int64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(i)) + return append([]byte{0x02}, append(v.Bytes(), b...)...) +} diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go new file mode 100644 index 000000000..26113e715 --- /dev/null +++ b/x/slashing/signing_info_test.go @@ -0,0 +1,35 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetSetValidatorSigningInfo(t *testing.T) { + ctx, _, _, keeper := createTestInput(t) + info, found := keeper.getValidatorSigningInfo(ctx, addrs[0]) + require.False(t, found) + newInfo := ValidatorSigningInfo{ + StartHeight: int64(4), + IndexOffset: int64(3), + JailedUntil: int64(2), + SignedBlocksCounter: int64(10), + } + keeper.setValidatorSigningInfo(ctx, addrs[0], newInfo) + info, found = keeper.getValidatorSigningInfo(ctx, addrs[0]) + require.True(t, found) + require.Equal(t, info.StartHeight, int64(4)) + require.Equal(t, info.IndexOffset, int64(3)) + require.Equal(t, info.JailedUntil, int64(2)) + require.Equal(t, info.SignedBlocksCounter, int64(10)) +} + +func TestGetSetValidatorSigningBitArray(t *testing.T) { + ctx, _, _, keeper := createTestInput(t) + signed := keeper.getValidatorSigningBitArray(ctx, addrs[0], 0) + require.False(t, signed) // treat empty key as unsigned + keeper.setValidatorSigningBitArray(ctx, addrs[0], 0, true) + signed = keeper.getValidatorSigningBitArray(ctx, addrs[0], 0) + require.True(t, signed) // now should be signed +} diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go new file mode 100644 index 000000000..94d323d23 --- /dev/null +++ b/x/slashing/test_common.go @@ -0,0 +1,97 @@ +package slashing + +import ( + "encoding/hex" + "os" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + "github.com/cosmos/cosmos-sdk/store" + 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" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +var ( + addrs = []sdk.Address{ + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), + } + pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), + } + initCoins int64 = 200 +) + +func createTestCodec() *wire.Codec { + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + auth.RegisterWire(cdc) + bank.RegisterWire(cdc) + stake.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + return cdc +} + +func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keeper) { + keyAcc := sdk.NewKVStoreKey("acc") + keyStake := sdk.NewKVStoreKey("stake") + keySlashing := sdk.NewKVStoreKey("slashing") + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewTMLogger(os.Stdout)) + cdc := createTestCodec() + accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{}) + ck := bank.NewKeeper(accountMapper) + sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) + genesis := stake.DefaultGenesisState() + genesis.Pool.LooseUnbondedTokens = initCoins * int64(len(addrs)) + stake.InitGenesis(ctx, sk, genesis) + for _, addr := range addrs { + ck.AddCoins(ctx, addr, sdk.Coins{ + {sk.GetParams(ctx).BondDenom, initCoins}, + }) + } + keeper := NewKeeper(cdc, keySlashing, sk, DefaultCodespace) + return ctx, ck, sk, keeper +} + +func newPubKey(pk string) (res crypto.PubKey) { + pkBytes, err := hex.DecodeString(pk) + if err != nil { + panic(err) + } + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd +} + +func testAddr(addr string) sdk.Address { + res := []byte(addr) + return res +} + +func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) stake.MsgCreateValidator { + return stake.MsgCreateValidator{ + Description: stake.Description{}, + ValidatorAddr: address, + PubKey: pubKey, + Bond: sdk.Coin{"steak", amt}, + } +} diff --git a/x/slashing/tick.go b/x/slashing/tick.go new file mode 100644 index 000000000..526baece0 --- /dev/null +++ b/x/slashing/tick.go @@ -0,0 +1,44 @@ +package slashing + +import ( + "encoding/binary" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// slashing begin block functionality +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags sdk.Tags) { + // Tag the height + heightBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height)) + tags = sdk.NewTags("height", heightBytes) + + // Deal with any equivocation evidence + for _, evidence := range req.ByzantineValidators { + pk, err := tmtypes.PB2TM.PubKey(evidence.Validator.PubKey) + if err != nil { + panic(err) + } + switch string(evidence.Type) { + case tmtypes.ABCIEvidenceTypeDuplicateVote: + sk.handleDoubleSign(ctx, evidence.Height, evidence.Time, pk) + default: + ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("Ignored unknown evidence type: %s", string(evidence.Type))) + } + } + + // Iterate over all the validators which *should* have signed this block + for _, validator := range req.Validators { + present := validator.SignedLastBlock + pubkey, err := tmtypes.PB2TM.PubKey(validator.Validator.PubKey) + if err != nil { + panic(err) + } + sk.handleValidatorSignature(ctx, pubkey, present) + } + + return +} diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go new file mode 100644 index 000000000..adf2e5e5b --- /dev/null +++ b/x/slashing/tick_test.go @@ -0,0 +1,77 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +func TestBeginBlocker(t *testing.T) { + ctx, ck, sk, keeper := createTestInput(t) + addr, pk, amt := addrs[2], pks[2], int64(100) + + // bond the validator + got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, pk, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) + require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + + val := abci.Validator{ + PubKey: tmtypes.TM2PB.PubKey(pk), + Power: amt, + } + + // mark the validator as having signed + req := abci.RequestBeginBlock{ + Validators: []abci.SigningValidator{{ + Validator: val, + SignedLastBlock: true, + }}, + } + BeginBlocker(ctx, req, keeper) + + info, found := keeper.getValidatorSigningInfo(ctx, pk.Address()) + require.True(t, found) + require.Equal(t, ctx.BlockHeight(), info.StartHeight) + require.Equal(t, int64(1), info.IndexOffset) + require.Equal(t, int64(0), info.JailedUntil) + require.Equal(t, int64(1), info.SignedBlocksCounter) + + height := int64(0) + + // for 50 blocks, mark the validator as having signed + for ; height < 50; height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + Validators: []abci.SigningValidator{{ + Validator: val, + SignedLastBlock: true, + }}, + } + BeginBlocker(ctx, req, keeper) + } + + // for 51 blocks, mark the validator as having not signed + for ; height < 102; height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + Validators: []abci.SigningValidator{{ + Validator: val, + SignedLastBlock: false, + }}, + } + BeginBlocker(ctx, req, keeper) + } + + // validator should be revoked + validator, found := sk.GetValidatorByPubKey(ctx, pk) + require.True(t, found) + require.Equal(t, sdk.Unbonded, validator.GetStatus()) +} diff --git a/x/slashing/wire.go b/x/slashing/wire.go new file mode 100644 index 000000000..465a06587 --- /dev/null +++ b/x/slashing/wire.go @@ -0,0 +1,12 @@ +package slashing + +import ( + "github.com/cosmos/cosmos-sdk/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil) +} + +var cdcEmpty = wire.NewCodec() diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 491ad1a7b..eac39b9ef 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -21,7 +21,7 @@ func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32Cosmos(args[0]) + addr, err := sdk.GetAccAddressBech32(args[0]) if err != nil { return err } @@ -72,25 +72,25 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { return err } - // parse out the candidates - var candidates []stake.Validator + // parse out the validators + var validators []stake.Validator for _, KV := range resKVs { var validator stake.Validator cdc.MustUnmarshalBinary(KV.Value, &validator) - candidates = append(candidates, validator) + validators = append(validators, validator) } switch viper.Get(cli.OutputFlag) { case "text": - for _, candidate := range candidates { - resp, err := candidate.HumanReadableString() + for _, validator := range validators { + resp, err := validator.HumanReadableString() if err != nil { return err } fmt.Println(resp) } case "json": - output, err := wire.MarshalJSONIndent(cdc, candidates) + output, err := wire.MarshalJSONIndent(cdc, validators) if err != nil { return err } @@ -112,7 +112,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { Short: "Query a delegations bond based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressValidator)) + addr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -157,7 +157,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query all the candidates bonded to a delegation +// get the command to query all the validators bonded to a delegation func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", @@ -165,7 +165,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.GetAccAddressBech32Cosmos(args[0]) + delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) if err != nil { return err } @@ -176,7 +176,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { return err } - // parse out the candidates + // parse out the validators var delegations []stake.Delegation for _, KV := range resKVs { var delegation stake.Delegation diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index dc88bfc20..daa4dd9ef 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -13,8 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -// create declare candidacy command -func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { +// create create validator command +func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "create-validator", Short: "create new validator initialized with a self-delegation to it", @@ -25,7 +25,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - validatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressValidator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -34,7 +34,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { if len(pkStr) == 0 { return fmt.Errorf("must use --pubkey flag") } - pk, err := sdk.GetValPubKeyBech32Cosmos(pkStr) + pk, err := sdk.GetValPubKeyBech32(pkStr) if err != nil { return err } @@ -47,7 +47,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgDeclareCandidacy(validatorAddr, pk, amount, description) + msg := stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) @@ -67,14 +67,14 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit candidacy command -func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { +// create edit validator command +func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "edit-validator", Short: "edit and existing validator account", RunE: func(cmd *cobra.Command, args []string) error { - validatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressValidator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -84,7 +84,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgEditCandidacy(validatorAddr, description) + msg := stake.NewMsgEditValidator(validatorAddr, description) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -104,7 +104,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit candidacy command +// create edit validator command func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", @@ -115,8 +115,8 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { return err } - delegatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressDelegator)) - validatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressValidator)) + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -142,7 +142,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit candidacy command +// create edit validator command func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbond", @@ -163,8 +163,8 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { } } - delegatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressDelegator)) - validatorAddr, err := sdk.GetAccAddressBech32Cosmos(viper.GetString(FlagAddressValidator)) + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 609390293..0da3260cb 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -1,12 +1,10 @@ package rest import ( - "encoding/hex" "fmt" "net/http" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,34 +12,39 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -// RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/stake/{delegator}/bonding_status/{validator}", BondingStatusHandlerFn("stake", cdc, kb, ctx)).Methods("GET") +func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc( + "/stake/{delegator}/bonding_status/{validator}", + bondingStatusHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + r.HandleFunc( + "/stake/validators", + validatorsHandlerFn(ctx, "stake", cdc), + ).Methods("GET") } -// BondingStatusHandlerFn - http request handler to query delegator bonding status -func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +// http request handler to query delegator bonding status +func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + // read parameters vars := mux.Vars(r) - delegator := vars["delegator"] - validator := vars["validator"] + bech32delegator := vars["delegator"] + bech32validator := vars["validator"] - bz, err := hex.DecodeString(delegator) + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - delegatorAddr := sdk.Address(bz) - bz, err = hex.DecodeString(validator) + validatorAddr, err := sdk.GetValAddressBech32(bech32validator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - validatorAddr := sdk.Address(bz) key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) @@ -76,3 +79,103 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, w.Write(output) } } + +// TODO inherit from Validator +type StakeValidatorOutput struct { + Owner string `json:"owner"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + + PoolShares stake.PoolShares `json:"pool_shares"` // total shares for tokens held in the pool + DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators + + Description stake.Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change + ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators + CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge + CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission + CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + + // fee related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools +} + +func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput, error) { + bechOwner, err := sdk.Bech32ifyVal(validator.Owner) + if err != nil { + return StakeValidatorOutput{}, err + } + bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) + if err != nil { + return StakeValidatorOutput{}, err + } + + return StakeValidatorOutput{ + Owner: bechOwner, + PubKey: bechValPubkey, + 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, + }, nil +} + +// TODO bech32 +// http request handler to query list of validators +func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + kvs, err := ctx.QuerySubspace(cdc, stake.ValidatorsKey, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't query validators. Error: %s", err.Error()))) + return + } + + // the query will return empty if there are no validators + if len(kvs) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + // 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) + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validators[i] = bech32Validator + } + + output, err := cdc.MarshalJSON(validators) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go new file mode 100644 index 000000000..1f3a2957d --- /dev/null +++ b/x/stake/client/rest/rest.go @@ -0,0 +1,15 @@ +package rest + +import ( + "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/wire" +) + +// RegisterRoutes registers staking-related REST handlers to a router +func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + registerQueryRoutes(ctx, r, cdc) + registerTxRoutes(ctx, r, cdc, kb) +} diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go new file mode 100644 index 000000000..33fe73c69 --- /dev/null +++ b/x/stake/client/rest/tx.go @@ -0,0 +1,164 @@ +package rest + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + + "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" +) + +func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc( + "/stake/delegations", + editDelegationsRequestHandlerFn(cdc, kb, ctx), + ).Methods("POST") +} + +type msgDelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Bond sdk.Coin `json:"bond"` +} +type msgUnbondInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Shares string `json:"shares"` +} + +type editDelegationsBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + Sequence int64 `json:"sequence"` + Delegate []msgDelegateInput `json:"delegate"` + Unbond []msgUnbondInput `json:"unbond"` +} + +func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var m editDelegationsBody + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, &m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // build messages + messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) + i := 0 + for _, msg := range m.Delegate { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = stake.MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Bond: msg.Bond, + } + i++ + } + for _, msg := range m.Unbond { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = stake.MsgUnbond{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Shares: msg.Shares, + } + i++ + } + + // sign messages + signedTxs := make([][]byte, len(messages[:])) + for i, msg := range messages { + // increment sequence for each message + ctx = ctx.WithSequence(m.Sequence) + m.Sequence++ + + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + signedTxs[i] = txBytes + } + + // send + // XXX the operation might not be atomic if a tx fails + // should we have a sdk.MultiMsg type to make sending atomic? + results := make([]*ctypes.ResultBroadcastTxCommit, len(signedTxs[:])) + for i, txBytes := range signedTxs { + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + results[i] = res + } + + output, err := json.MarshalIndent(results[:], "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/stake/delegation.go b/x/stake/delegation.go index 6877034d7..dedac03e5 100644 --- a/x/stake/delegation.go +++ b/x/stake/delegation.go @@ -35,11 +35,11 @@ func (b Delegation) GetBondShares() sdk.Rat { return b.Shares } //Human Friendly pretty printer func (b Delegation) HumanReadableString() (string, error) { - bechAcc, err := sdk.Bech32CosmosifyAcc(b.DelegatorAddr) + bechAcc, err := sdk.Bech32ifyAcc(b.DelegatorAddr) if err != nil { return "", err } - bechVal, err := sdk.Bech32CosmosifyAcc(b.ValidatorAddr) + bechVal, err := sdk.Bech32ifyAcc(b.ValidatorAddr) if err != nil { return "", err } diff --git a/x/stake/errors.go b/x/stake/errors.go index 83c38d528..0ae57530a 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -16,6 +16,7 @@ const ( CodeInvalidValidator CodeType = 201 CodeInvalidBond CodeType = 202 CodeInvalidInput CodeType = 203 + CodeValidatorJailed CodeType = 204 CodeUnauthorized CodeType = sdk.CodeUnauthorized CodeInternal CodeType = sdk.CodeInternal CodeUnknownRequest CodeType = sdk.CodeUnknownRequest @@ -72,10 +73,10 @@ func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Delegator does not exist for that address") } func ErrValidatorExistsAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Validator already exist, cannot re-declare candidacy") + return newError(codespace, CodeInvalidValidator, "Validator already exist, cannot re-create validator") } func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Candidacy for this address is currently revoked") + return newError(codespace, CodeInvalidValidator, "Validator for this address is currently revoked") } func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Missing signature") diff --git a/x/stake/genesis.go b/x/stake/genesis.go index d45adc3d7..43ea61d8b 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,6 +1,10 @@ package stake -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) // GenesisState - all staking state that must be provided at genesis type GenesisState struct { @@ -22,21 +26,32 @@ func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []D // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - Pool: initialPool(), - Params: defaultParams(), + Pool: InitialPool(), + Params: DefaultParams(), } } // InitGenesis - store genesis parameters func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + store := ctx.KVStore(k.storeKey) k.setPool(ctx, data.Pool) k.setNewParams(ctx, data.Params) for _, validator := range data.Validators { - k.updateValidator(ctx, validator) + + // set validator + k.setValidator(ctx, validator) + + // manually set indexes for the first time + k.setValidatorByPubKeyIndex(ctx, validator) + k.setValidatorByPowerIndex(ctx, validator, data.Pool) + if validator.Status() == sdk.Bonded { + store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) + } } for _, bond := range data.Bonds { k.setDelegation(ctx, bond) } + k.updateBondedValidatorsFull(ctx, store) } // WriteGenesis - output genesis parameters @@ -52,3 +67,16 @@ func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { bonds, } } + +// WriteValidators - output current validator set +func WriteValidators(ctx sdk.Context, k Keeper) (vals []tmtypes.GenesisValidator) { + k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + vals = append(vals, tmtypes.GenesisValidator{ + PubKey: validator.GetPubKey(), + Power: validator.GetPower().Evaluate(), + Name: validator.GetMoniker(), + }) + return false + }) + return +} diff --git a/x/stake/handler.go b/x/stake/handler.go index 53653557c..f366989b6 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -11,10 +11,10 @@ func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // NOTE msg already has validate basic run switch msg := msg.(type) { - case MsgDeclareCandidacy: - return handleMsgDeclareCandidacy(ctx, msg, k) - case MsgEditCandidacy: - return handleMsgEditCandidacy(ctx, msg, k) + case MsgCreateValidator: + return handleMsgCreateValidator(ctx, msg, k) + case MsgEditValidator: + return handleMsgEditValidator(ctx, msg, k) case MsgDelegate: return handleMsgDelegate(ctx, msg, k) case MsgUnbond: @@ -25,13 +25,27 @@ func NewHandler(k Keeper) sdk.Handler { } } -// NewEndBlocker generates sdk.EndBlocker -// Performs tick functionality -func NewEndBlocker(k Keeper) sdk.EndBlocker { - return func(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) { - res.ValidatorUpdates = k.Tick(ctx) - return +// Called every block, process inflation, update validator set +func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { + pool := k.GetPool(ctx) + + // Process Validator Provisions + blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + if pool.InflationLastTime+blockTime >= 3600 { + pool.InflationLastTime = blockTime + pool = k.processProvisions(ctx) } + + // save the params + k.setPool(ctx, pool) + + // reset the intra-transaction counter + k.setIntraTxCounter(ctx, 0) + + // calculate validator set changes + ValidatorUpdates = k.getTendermintUpdates(ctx) + k.clearTendermintUpdates(ctx) + return } //_____________________________________________________________________ @@ -39,7 +53,7 @@ func NewEndBlocker(k Keeper) sdk.EndBlocker { // These functions assume everything has been authenticated, // now we just perform action and save -func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keeper) sdk.Result { +func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) @@ -55,8 +69,9 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) k.setValidator(ctx, validator) + k.setValidatorByPubKeyIndex(ctx, validator) tags := sdk.NewTags( - "action", []byte("declareCandidacy"), + "action", []byte("createValidator"), "validator", msg.ValidatorAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity), @@ -74,7 +89,7 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe } } -func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result { +func handleMsgEditValidator(ctx sdk.Context, msg MsgEditValidator, k Keeper) sdk.Result { // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) @@ -94,7 +109,7 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk k.updateValidator(ctx, validator) tags := sdk.NewTags( - "action", []byte("editCandidacy"), + "action", []byte("editValidator"), "validator", msg.ValidatorAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity), @@ -206,14 +221,14 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { bond.Shares = bond.Shares.Sub(delShares) // remove the bond - revokeCandidacy := false + revokeValidator := false if bond.Shares.IsZero() { // if the bond is the owner of the validator then - // trigger a revoke candidacy + // trigger a revoke validator if bytes.Equal(bond.DelegatorAddr, validator.Owner) && validator.Revoked == false { - revokeCandidacy = true + revokeValidator = true } k.removeDelegation(ctx, bond) @@ -232,7 +247,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { ///////////////////////////////////// // revoke validator if necessary - if revokeCandidacy { + if revokeValidator { validator.Revoked = true } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 19848e8e6..0c086f06d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -14,8 +14,8 @@ import ( //______________________________________________________________________ -func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { - return MsgDeclareCandidacy{ +func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgCreateValidator { + return MsgCreateValidator{ Description: Description{}, ValidatorAddr: address, PubKey: pubKey, @@ -33,13 +33,13 @@ func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) Msg //______________________________________________________________________ -func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { +func TestDuplicatesMsgCreateValidator(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) validatorAddr := addrs[0] pk := pks[0] - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pk, 10) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "%v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) @@ -51,8 +51,8 @@ func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { assert.Equal(t, Description{}, validator.Description) // one validator cannot bond twice - msgDeclareCandidacy.PubKey = pks[1] - got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + msgCreateValidator.PubKey = pks[1] + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.False(t, got.IsOK(), "%v", got) } @@ -64,10 +64,10 @@ func TestIncrementsMsgDelegate(t *testing.T) { bondAmount := int64(10) validatorAddr, delegatorAddr := addrs[0], addrs[1] - // first declare candidacy - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], bondAmount) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) - assert.True(t, got.IsOK(), "expected declare candidacy msg to be ok, got %v", got) + // first create validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], bondAmount) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + assert.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) @@ -134,12 +134,12 @@ func TestIncrementsMsgUnbond(t *testing.T) { ctx, accMapper, keeper := createTestInput(t, false, initBond) params := keeper.GetParams(ctx) - // declare candidacy, delegate + // create validator, delegate validatorAddr, delegatorAddr := addrs[0], addrs[1] - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], initBond) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) - assert.True(t, got.IsOK(), "expected declare-candidacy to be ok, got %v", got) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) @@ -216,7 +216,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) } -func TestMultipleMsgDeclareCandidacy(t *testing.T) { +func TestMultipleMsgCreateValidator(t *testing.T) { initBond := int64(1000) ctx, accMapper, keeper := createTestInput(t, false, initBond) params := keeper.GetParams(ctx) @@ -224,8 +224,8 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { // bond them all for i, validatorAddr := range validatorAddrs { - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[i], 10) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[i], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded @@ -266,8 +266,8 @@ func TestMultipleMsgDelegate(t *testing.T) { validatorAddr, delegatorAddrs := addrs[0], addrs[1:] //first make a validator - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], 10) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // delegate multiple parties @@ -294,14 +294,14 @@ func TestMultipleMsgDelegate(t *testing.T) { } } -func TestVoidCandidacy(t *testing.T) { +func TestRevokeValidator(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) validatorAddr, delegatorAddr := addrs[0], addrs[1] // create the validator - msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], 10) - got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) @@ -311,7 +311,7 @@ func TestVoidCandidacy(t *testing.T) { // unbond the validators bond portion msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10") got = handleMsgUnbond(ctx, msgUnbondValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.True(t, validator.Revoked) @@ -323,9 +323,9 @@ func TestVoidCandidacy(t *testing.T) { // test that the delegator can still withdraw their bonds msgUnbondDelegator := NewMsgUnbond(delegatorAddr, validatorAddr, "10") got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // verify that the pubkey can now be reused - got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected ok, got %v", got) } diff --git a/x/stake/tick.go b/x/stake/inflation.go similarity index 75% rename from x/stake/tick.go rename to x/stake/inflation.go index 70076c691..d613a478a 100644 --- a/x/stake/tick.go +++ b/x/stake/inflation.go @@ -2,7 +2,6 @@ package stake import ( sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" ) const ( @@ -12,30 +11,6 @@ const ( var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days -// Tick - called at the end of every block -func (k Keeper) Tick(ctx sdk.Context) (change []abci.Validator) { - p := k.GetPool(ctx) - - // Process Validator Provisions - blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm - if p.InflationLastTime+blockTime >= 3600 { - p.InflationLastTime = blockTime - p = k.processProvisions(ctx) - } - - // save the params - k.setPool(ctx, p) - - // reset the intra-transaction counter - k.setIntraTxCounter(ctx, 0) - - // calculate validator set changes - change = k.getTendermintUpdates(ctx) - k.clearTendermintUpdates(ctx) - - return change -} - // process provisions for an hour period func (k Keeper) processProvisions(ctx sdk.Context) Pool { diff --git a/x/stake/tick_test.go b/x/stake/inflation_test.go similarity index 100% rename from x/stake/tick_test.go rename to x/stake/inflation_test.go diff --git a/x/stake/keeper.go b/x/stake/keeper.go index ce84b1e17..9f554c764 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" ) // keeper of the staking store @@ -38,6 +39,16 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid return k.getValidator(store, addr) } +// get a single validator by pubkey +func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) { + store := ctx.KVStore(k.storeKey) + addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) + if addr == nil { + return validator, false + } + return k.getValidator(store, addr) +} + // get a single validator (reuse store) func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) { b := store.Get(GetValidatorKey(addr)) @@ -51,10 +62,22 @@ func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Val // set the main record holding validator details func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { store := ctx.KVStore(k.storeKey) + // set main store bz := k.cdc.MustMarshalBinary(validator) store.Set(GetValidatorKey(validator.Owner), bz) } +func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) { + store := ctx.KVStore(k.storeKey) + // set pointer by pubkey + store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) +} + +func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner) +} + // Get the set of all validators with no limits, used during genesis dump func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { store := ctx.KVStore(k.storeKey) @@ -203,8 +226,12 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - k.setPool(ctx, pool) + validator = k.unbondValidator(ctx, store, validator) + + // need to also clear the cliff validator spot because the revoke has + // opened up a new spot which will be filled when + // updateValidatorsBonded is called + k.clearCliffValidator(ctx) } powerIncreasing := false @@ -409,7 +436,7 @@ func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) { } // perform all the store operations for when a validator status becomes unbonded -func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) { +func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { pool := k.GetPool(ctx) // sanity check @@ -431,6 +458,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Va // also remove from the Bonded Validators Store store.Delete(GetValidatorsBondedKey(validator.PubKey)) + return validator } // perform all the store operations for when a validator status becomes bonded @@ -470,6 +498,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) pool := k.getPool(store) store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) store.Delete(GetValidatorsByPowerKey(validator, pool)) // delete from the current and power weighted validator groups if the validator @@ -607,9 +636,9 @@ func (k Keeper) getPool(store sdk.KVStore) (pool Pool) { return } -func (k Keeper) setPool(ctx sdk.Context, p Pool) { +func (k Keeper) setPool(ctx sdk.Context, pool Pool) { store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(p) + b := k.cdc.MustMarshalBinary(pool) store.Set(PoolKey, b) } @@ -656,6 +685,13 @@ func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Poo store.Set(ValidatorCliffKey, validator.Owner) } +// clear the current validator and power of the validator on the cliff +func (k Keeper) clearCliffValidator(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(ValidatorPowerCliffKey) + store.Delete(ValidatorCliffKey) +} + //__________________________________________________________________________ // Implements ValidatorSet @@ -749,3 +785,46 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func( } iterator.Close() } + +// slash a validator +func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) { + // TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957 + logger := ctx.Logger().With("module", "x/stake") + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address())) + } + sharesToRemove := val.PoolShares.Amount.Mul(fraction) + pool := k.GetPool(ctx) + val, pool, burned := val.removePoolShares(pool, sharesToRemove) + k.setPool(ctx, pool) // update the pool + k.updateValidator(ctx, val) // update the validator, possibly kicking it out + logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned)) + return +} + +// revoke a validator +func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { + logger := ctx.Logger().With("module", "x/stake") + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey)) + } + val.Revoked = true + k.updateValidator(ctx, val) // update the validator, now revoked + logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) + return +} + +// unrevoke a validator +func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + logger := ctx.Logger().With("module", "x/stake") + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey)) + } + val.Revoked = false + k.updateValidator(ctx, val) // update the validator, now unrevoked + logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) + return +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 5a84d08f2..632a86ec3 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -13,16 +13,17 @@ import ( //nolint var ( // Keys for store prefixes - ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators - ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power - ValidatorCliffKey = []byte{0x05} // key for block-local tx index - ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index - TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated - DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x09} // key for block-local tx index + ParamKey = []byte{0x00} // key for parameters relating to staking + PoolKey = []byte{0x01} // key for the staking pools + ValidatorsKey = []byte{0x02} // prefix for each key to a validator + ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey + ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators + ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power + ValidatorCliffKey = []byte{0x06} // key for block-local tx index + ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond + IntraTxCounterKey = []byte{0x10} // key for block-local tx index ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -32,6 +33,11 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte { return append(ValidatorsKey, ownerAddr.Bytes()...) } +// get the key for the validator with pubkey +func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { + return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) +} + // get the key for the current validator group, ordered like tendermint func GetValidatorsBondedKey(pk crypto.PubKey) []byte { addr := pk.Address() diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 0e62f0936..2868853e0 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -4,6 +4,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -463,8 +464,8 @@ func TestGetTendermintUpdatesAllNone(t *testing.T) { updates = keeper.getTendermintUpdates(ctx) require.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].PubKey.Bytes(), updates[0].PubKey) - assert.Equal(t, validators[1].PubKey.Bytes(), updates[1].PubKey) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey) assert.Equal(t, int64(0), updates[0].Power) assert.Equal(t, int64(0), updates[1].Power) } @@ -586,7 +587,7 @@ func TestGetTendermintUpdatesInserted(t *testing.T) { func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - params := defaultParams() + params := DefaultParams() params.MaxValidators = 2 keeper.setParams(ctx, params) @@ -721,7 +722,7 @@ func TestBond(t *testing.T) { func TestParams(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - expParams := defaultParams() + expParams := DefaultParams() //check that the empty keeper loads the default resParams := keeper.GetParams(ctx) @@ -736,7 +737,7 @@ func TestParams(t *testing.T) { func TestPool(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - expPool := initialPool() + expPool := InitialPool() //check that the empty keeper loads the default resPool := keeper.GetPool(ctx) diff --git a/x/stake/msg.go b/x/stake/msg.go index 41220acc6..40bf609ee 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -1,10 +1,7 @@ package stake import ( - "encoding/json" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" crypto "github.com/tendermint/go-crypto" ) @@ -18,27 +15,21 @@ const MsgType = "stake" const StakingToken = "steak" //Verify interface at compile time -var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} - -var msgCdc = wire.NewCodec() - -func init() { - wire.RegisterCrypto(msgCdc) -} +var _, _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}, &MsgUnbond{} //______________________________________________________________________ -// MsgDeclareCandidacy - struct for unbonding transactions -type MsgDeclareCandidacy struct { +// MsgCreateValidator - struct for unbonding transactions +type MsgCreateValidator struct { Description ValidatorAddr sdk.Address `json:"address"` PubKey crypto.PubKey `json:"pubkey"` Bond sdk.Coin `json:"bond"` } -func NewMsgDeclareCandidacy(validatorAddr sdk.Address, pubkey crypto.PubKey, - bond sdk.Coin, description Description) MsgDeclareCandidacy { - return MsgDeclareCandidacy{ +func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, + bond sdk.Coin, description Description) MsgCreateValidator { + return MsgCreateValidator{ Description: description, ValidatorAddr: validatorAddr, PubKey: pubkey, @@ -47,18 +38,18 @@ func NewMsgDeclareCandidacy(validatorAddr sdk.Address, pubkey crypto.PubKey, } //nolint -func (msg MsgDeclareCandidacy) Type() string { return MsgType } //TODO update "stake/declarecandidacy" -func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { +func (msg MsgCreateValidator) Type() string { return MsgType } +func (msg MsgCreateValidator) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } // get the bytes for the message signer to sign on -func (msg MsgDeclareCandidacy) GetSignBytes() []byte { +func (msg MsgCreateValidator) GetSignBytes() []byte { return msgCdc.MustMarshalBinary(msg) } // quick validity check -func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { +func (msg MsgCreateValidator) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrValidatorEmpty(DefaultCodespace) } @@ -77,28 +68,28 @@ func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { //______________________________________________________________________ -// MsgEditCandidacy - struct for editing a validator -type MsgEditCandidacy struct { +// MsgEditValidator - struct for editing a validator +type MsgEditValidator struct { Description ValidatorAddr sdk.Address `json:"address"` } -func NewMsgEditCandidacy(validatorAddr sdk.Address, description Description) MsgEditCandidacy { - return MsgEditCandidacy{ +func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { + return MsgEditValidator{ Description: description, ValidatorAddr: validatorAddr, } } //nolint -func (msg MsgEditCandidacy) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" -func (msg MsgEditCandidacy) GetSigners() []sdk.Address { +func (msg MsgEditValidator) Type() string { return MsgType } +func (msg MsgEditValidator) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } // get the bytes for the message signer to sign on -func (msg MsgEditCandidacy) GetSignBytes() []byte { - b, err := json.Marshal(msg) +func (msg MsgEditValidator) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) if err != nil { panic(err) } @@ -106,7 +97,7 @@ func (msg MsgEditCandidacy) GetSignBytes() []byte { } // quick validity check -func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { +func (msg MsgEditValidator) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrValidatorEmpty(DefaultCodespace) } @@ -121,8 +112,8 @@ func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { // MsgDelegate - struct for bonding transactions type MsgDelegate struct { - DelegatorAddr sdk.Address `json:"address"` - ValidatorAddr sdk.Address `json:"address"` + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` Bond sdk.Coin `json:"bond"` } @@ -135,14 +126,14 @@ func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) Msg } //nolint -func (msg MsgDelegate) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" +func (msg MsgDelegate) Type() string { return MsgType } func (msg MsgDelegate) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } // get the bytes for the message signer to sign on func (msg MsgDelegate) GetSignBytes() []byte { - b, err := json.Marshal(msg) + b, err := msgCdc.MarshalJSON(msg) if err != nil { panic(err) } @@ -170,8 +161,8 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { // MsgUnbond - struct for unbonding transactions type MsgUnbond struct { - DelegatorAddr sdk.Address `json:"address"` - ValidatorAddr sdk.Address `json:"address"` + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` Shares string `json:"shares"` } @@ -184,12 +175,12 @@ func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUn } //nolint -func (msg MsgUnbond) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" +func (msg MsgUnbond) Type() string { return MsgType } func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } // get the bytes for the message signer to sign on func (msg MsgUnbond) GetSignBytes() []byte { - b, err := json.Marshal(msg) + b, err := msgCdc.MarshalJSON(msg) if err != nil { panic(err) } diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 03a5fbf62..863613a03 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -18,8 +18,8 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) -// test ValidateBasic for MsgDeclareCandidacy -func TestMsgDeclareCandidacy(t *testing.T) { +// test ValidateBasic for MsgCreateValidator +func TestMsgCreateValidator(t *testing.T) { tests := []struct { name, moniker, identity, website, details string validatorAddr sdk.Address @@ -40,7 +40,7 @@ func TestMsgDeclareCandidacy(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgDeclareCandidacy(tc.validatorAddr, tc.pubkey, tc.bond, description) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -49,8 +49,8 @@ func TestMsgDeclareCandidacy(t *testing.T) { } } -// test ValidateBasic for MsgEditCandidacy -func TestMsgEditCandidacy(t *testing.T) { +// test ValidateBasic for MsgEditValidator +func TestMsgEditValidator(t *testing.T) { tests := []struct { name, moniker, identity, website, details string validatorAddr sdk.Address @@ -64,7 +64,7 @@ func TestMsgEditCandidacy(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditCandidacy(tc.validatorAddr, description) + msg := NewMsgEditValidator(tc.validatorAddr, description) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -139,8 +139,8 @@ func TestMsgUnbond(t *testing.T) { //tests := []struct { //tx sdk.Msg //}{ -//{NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})}, -//{NewMsgEditCandidacy(addrs[0], Description{})}, +//{NewMsgCreateValidator(addrs[0], pks[0], bond, Description{})}, +//{NewMsgEditValidator(addrs[0], Description{})}, //{NewMsgDelegate(addrs[0], addrs[1], bond)}, //{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, //} diff --git a/x/stake/params.go b/x/stake/params.go index 32b8c0ae8..026bd871f 100644 --- a/x/stake/params.go +++ b/x/stake/params.go @@ -18,12 +18,13 @@ type Params struct { } func (p Params) equal(p2 Params) bool { - bz1 := cdcEmpty.MustMarshalBinary(&p) - bz2 := cdcEmpty.MustMarshalBinary(&p2) + bz1 := msgCdc.MustMarshalBinary(&p) + bz2 := msgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } -func defaultParams() Params { +// default params +func DefaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), diff --git a/x/stake/pool.go b/x/stake/pool.go index e2547b050..ba1890ce5 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -25,13 +25,13 @@ type Pool struct { } func (p Pool) equal(p2 Pool) bool { - bz1 := cdcEmpty.MustMarshalBinary(&p) - bz2 := cdcEmpty.MustMarshalBinary(&p2) + bz1 := msgCdc.MustMarshalBinary(&p) + bz2 := msgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } // initial pool for testing -func initialPool() Pool { +func InitialPool() Pool { return Pool{ LooseUnbondedTokens: 0, BondedTokens: 0, diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 109112086..50d8dc37e 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -44,8 +44,8 @@ func makeTestCodec() *wire.Codec { cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) cdc.RegisterConcrete(bank.MsgIssue{}, "test/stake/Issue", nil) - cdc.RegisterConcrete(MsgDeclareCandidacy{}, "test/stake/DeclareCandidacy", nil) - cdc.RegisterConcrete(MsgEditCandidacy{}, "test/stake/EditCandidacy", nil) + cdc.RegisterConcrete(MsgCreateValidator{}, "test/stake/CreateValidator", nil) + cdc.RegisterConcrete(MsgEditValidator{}, "test/stake/EditValidator", nil) cdc.RegisterConcrete(MsgUnbond{}, "test/stake/Unbond", nil) // Register AppAccount @@ -89,8 +89,8 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context ) ck := bank.NewKeeper(accountMapper) keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) - keeper.setPool(ctx, initialPool()) - keeper.setNewParams(ctx, defaultParams()) + keeper.setPool(ctx, InitialPool()) + keeper.setNewParams(ctx, DefaultParams()) // fill all the addresses with some coins for _, addr := range addrs { @@ -120,7 +120,7 @@ func testAddr(addr string, bech string) sdk.Address { if err != nil { panic(err) } - bechexpected, err := sdk.Bech32CosmosifyAcc(res) + bechexpected, err := sdk.Bech32ifyAcc(res) if err != nil { panic(err) } @@ -128,7 +128,7 @@ func testAddr(addr string, bech string) sdk.Address { panic("Bech encoding doesn't match reference") } - bechres, err := sdk.GetAccAddressBech32Cosmos(bech) + bechres, err := sdk.GetAccAddressBech32(bech) if err != nil { panic(err) } diff --git a/x/stake/validator.go b/x/stake/validator.go index cf4ee85fd..3b135c12b 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" + tmtypes "github.com/tendermint/tendermint/types" ) // Validator defines the total amount of bond shares and their exchange rate to @@ -47,6 +48,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti return Validator{ Owner: owner, PubKey: pubKey, + Revoked: false, PoolShares: NewUnbondedShares(sdk.ZeroRat()), DelegatorShares: sdk.ZeroRat(), Description: description, @@ -100,7 +102,7 @@ func NewDescription(moniker, identity, website, details string) Description { // abci validator from stake validator type func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { return abci.Validator{ - PubKey: v.PubKey.Bytes(), + PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: v.PoolShares.Bonded().Evaluate(), } } @@ -109,7 +111,7 @@ func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { // with zero power used for validator updates func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { return abci.Validator{ - PubKey: v.PubKey.Bytes(), + PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: 0, } } @@ -154,6 +156,23 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, return v, pool } +// Remove pool shares +// 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) + case sdk.Unbonding: + pool, tokens = pool.removeSharesUnbonding(poolShares) + case sdk.Bonded: + pool, tokens = pool.removeSharesBonded(poolShares) + } + v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) + return v, pool, tokens +} + // XXX TEST // get the power or potential power for a validator // if bonded, the power is the BondedShares @@ -232,6 +251,7 @@ func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator +func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } func (v Validator) GetOwner() sdk.Address { return v.Owner } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } @@ -240,11 +260,11 @@ func (v Validator) GetBondHeight() int64 { return v.BondHeight } //Human Friendly pretty printer func (v Validator) HumanReadableString() (string, error) { - bechOwner, err := sdk.Bech32CosmosifyAcc(v.Owner) + bechOwner, err := sdk.Bech32ifyAcc(v.Owner) if err != nil { return "", err } - bechVal, err := sdk.Bech32CosmosifyValPub(v.PubKey) + bechVal, err := sdk.Bech32ifyValPub(v.PubKey) if err != nil { return "", err } diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go index 98a82d1dc..5834e5989 100644 --- a/x/stake/validator_test.go +++ b/x/stake/validator_test.go @@ -169,7 +169,7 @@ func randomValidator(r *rand.Rand, i int) Validator { // generate a random staking state func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { - pool := initialPool() + pool := InitialPool() validators := make([]Validator, numValidators) for i := 0; i < numValidators; i++ { diff --git a/x/stake/wire.go b/x/stake/wire.go index 6e6e38260..c0b0be71f 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -6,10 +6,15 @@ import ( // Register concrete types on wire codec func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgDeclareCandidacy{}, "cosmos-sdk/MsgDeclareCandidacy", nil) - cdc.RegisterConcrete(MsgEditCandidacy{}, "cosmos-sdk/MsgEditCandidacy", nil) + cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) } -var cdcEmpty = wire.NewCodec() +var msgCdc = wire.NewCodec() + +func init() { + RegisterWire(msgCdc) + wire.RegisterCrypto(msgCdc) +}