From 7e9859d3889a7dedfddb09d97e0f8ec01965958e Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Thu, 21 Jun 2018 22:38:40 -0700 Subject: [PATCH 01/22] Added a bech32 spec doc --- docs/spec/other/bech32.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/spec/other/bech32.md diff --git a/docs/spec/other/bech32.md b/docs/spec/other/bech32.md new file mode 100644 index 000000000..1d337ef6c --- /dev/null +++ b/docs/spec/other/bech32.md @@ -0,0 +1,25 @@ +# Bech32 on Cosmos + +The Cosmos network prefers to use the Bech32 address format whereever users must handle binary data. Bech32 encoding provides robust integrity checks on data and the human readable part(HRP) provides contextual hints that can assist UI developers with providing informative error messages. + +In the Cosmos network, keys and addresses may refer to a number of different roles in the network like accounts, validators etc. + + +## HRP table + +| HRP | Definition | +| ------------- |:-------------:| +| `cosmosaccaddr` | Cosmos Account Address | +| `cosmosaccpub` | Cosmos Account Public Key | +| `cosmosvaladdr` | Cosmos Consensus Address | +| `cosmosvalpub` | Cosmos Consensus Public Key| + +## Encoding + +While all user facing interfaces to Cosmos software should exposed bech32 interfaces, many internal interfaces encode binary value in hex or base64 encoded form. + +To covert between other binary reprsentation of addresses and keys, it is important to first apply the Amino enocoding process before bech32 encoding. + +A complete implementation of the Amino serialization format is unncessary in most cases. Simply prepending bytes from this [table](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) to the bytestring payload before bech32 encoding will sufficient for compatible representation. + +  \ No newline at end of file From 7f1169db4d8f6cbfeaa662126a30f2cbdb228cad Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Jun 2018 11:01:44 -0700 Subject: [PATCH 02/22] Merge PR #1337: tools: Fix makefile install scripts Previously, the install scripts weren't installing golint and gometalinter. This commit fixes this, and installs tendermints linter, and the HEAD of the gometalinter repository. Now make all should work. --- CHANGELOG.md | 2 ++ Makefile | 8 ++++---- tools/Makefile | 52 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e642888..a97a18bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,11 @@ FEATURES * Supported proposal types: just binary (pass/fail) TextProposals for now * Proposals need deposits to be votable; deposits are burned if proposal fails * Delegators delegate votes to validator by default but can override (for their stake) +* [tools] make get_tools installs tendermint's linter, and gometalinter FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer +* \#1052 - Make all now works * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile * Fixed bug where chain ID wasn't passed properly in x/bank REST handler diff --git a/Makefile b/Makefile index be593ef35..8d7fb0593 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.c COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" -all: check_tools get_vendor_deps install install_examples test_lint test +all: get_tools get_vendor_deps install install_examples test_lint test ######################################## ### CI @@ -36,11 +36,11 @@ else go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli endif -install: +install: go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiad go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli -install_examples: +install_examples: go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind @@ -89,7 +89,7 @@ godocs: test: test_unit -test_cli: +test_cli: @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` test_cli_retry: diff --git a/tools/Makefile b/tools/Makefile index ee1c14a84..41178a2f1 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -5,7 +5,11 @@ all: install ### DEP DEP = github.com/golang/dep/cmd/dep +GOLINT = github.com/tendermint/lint/golint +GOMETALINTER = github.com/alecthomas/gometalinter DEP_CHECK := $(shell command -v dep 2> /dev/null) +GOLINT_CHECK := $(shell command -v golint 2> /dev/null) +GOMETALINTER_CHECK := $(shell command -v gometalinter 2> /dev/null) check_tools: ifndef DEP_CHECK @@ -13,18 +17,44 @@ ifndef DEP_CHECK else @echo "Found dep in path." endif +ifndef GOLINT_CHECK + @echo "No golint in path. Install with 'make get_tools'." +else + @echo "Found golint in path." +endif +ifndef GOMETALINTER_CHECK + @echo "No gometalinter in path. Install with 'make get_tools'." +else + @echo "Found gometalinter in path." +endif get_tools: ifdef DEP_CHECK @echo "Dep is already installed. Run 'make update_tools' to update." else - @echo "$(ansi_grn)Installing dep$(ansi_end)" + @echo "Installing dep" go get -v $(DEP) endif +ifdef GOLINT_CHECK + @echo "Golint is already installed. Run 'make update_tools' to update." +else + @echo "Installing golint" + go get -v $(GOLINT) +endif +ifdef GOMETALINTER_CHECK + @echo "Gometalinter is already installed. Run 'make update_tools' to update." +else + @echo "Installing gometalinter" + go get -v $(GOMETALINTER) +endif update_tools: - @echo "$(ansi_grn)Updating dep$(ansi_end)" + @echo "Updating dep" go get -u -v $(DEP) + @echo "Updating tendermint/golint" + go get -u -v $(GOLINT) + @echo "Updating gometalinter" + go get -u -v $(GOMETALINTER) ######################################## @@ -37,24 +67,14 @@ get_vendor_deps: check_tools @dep ensure -v install: get_vendor_deps - @echo "$(ansi_grn)Installing tools$(ansi_end)" - @echo "$(ansi_yel)Install go-vendorinstall$(ansi_end)" + @echo "Installing tools" + @echo "Install go-vendorinstall" go build -o bin/go-vendorinstall go-vendorinstall/*.go - @echo "$(ansi_yel)Install gometalinter.v2$(ansi_end)" + @echo "Install gometalinter.v2" GOBIN="$(CURDIR)/bin" ./bin/go-vendorinstall github.com/alecthomas/gometalinter - @echo "$(ansi_grn)Done installing tools$(ansi_end)" - - -######################################## -# ANSI colors - -ansi_red=\033[0;31m -ansi_grn=\033[0;32m -ansi_yel=\033[0;33m -ansi_end=\033[0m - + @echo "Done installing tools" # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. From 17e5a48b6548d0cb6c576dffd7e42f1e5a7abc59 Mon Sep 17 00:00:00 2001 From: Geet Kumar Date: Fri, 22 Jun 2018 13:50:36 -0500 Subject: [PATCH 03/22] Merge PR #1334: democli: fix account query * democli: fix query account * Update changelog --- CHANGELOG.md | 1 + examples/democoin/types/account.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97a18bd1..b6d786bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ FIXES * \#1052 - Make all now works * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile * Fixed bug where chain ID wasn't passed properly in x/bank REST handler +* Fixed bug where `democli account` didn't decode the account data correctly ## 0.19.0 diff --git a/examples/democoin/types/account.go b/examples/democoin/types/account.go index 211cf3c41..e01b63c31 100644 --- a/examples/democoin/types/account.go +++ b/examples/democoin/types/account.go @@ -32,7 +32,7 @@ func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { return nil, sdk.ErrTxDecode("accBytes are empty") } acct := new(AppAccount) - err = cdc.UnmarshalBinary(accBytes, &acct) + err = cdc.UnmarshalBinaryBare(accBytes, &acct) if err != nil { panic(err) } From c3c570898d3967db6b8c02bb3539abe1cb249af9 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Fri, 22 Jun 2018 12:53:24 -0700 Subject: [PATCH 04/22] Merge PR #1340: Reverted ChangePubKey * removed msgChangePubKey * changelog * removed setPubKey --- CHANGELOG.md | 2 ++ examples/basecoin/app/app.go | 1 - x/auth/handler.go | 34 -------------------- x/auth/mapper.go | 11 ------- x/auth/mock/auth_app_test.go | 60 ------------------------------------ x/auth/msgs.go | 41 ------------------------ x/auth/msgs_test.go | 47 ---------------------------- x/auth/wire.go | 1 - 8 files changed, 2 insertions(+), 195 deletions(-) delete mode 100644 x/auth/handler.go delete mode 100644 x/auth/msgs.go delete mode 100644 x/auth/msgs_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d786bf3..64f168505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ BREAKING CHANGES * AltBytes renamed to Memo, now a string, max 100 characters, costs a bit of gas * Transactions now take a list of Messages * Signers of a transaction now only sign over their account and sequence number +* Removed MsgChangePubKey from auth +* Removed setPubKey from account mapper FEATURES * [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index f654ba05e..06ccd4a0c 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -77,7 +77,6 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // register message routes app.Router(). - AddRoute("auth", auth.NewHandler(app.accountMapper)). AddRoute("bank", bank.NewHandler(app.coinKeeper)). AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). AddRoute("stake", stake.NewHandler(app.stakeKeeper)) diff --git a/x/auth/handler.go b/x/auth/handler.go deleted file mode 100644 index 764c6f7a2..000000000 --- a/x/auth/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package auth - -import ( - "reflect" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// NewHandler returns a handler for "baseaccount" type messages. -func NewHandler(am AccountMapper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgChangeKey: - return handleMsgChangeKey(ctx, am, msg) - default: - errMsg := "Unrecognized baseaccount Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - -// Handle MsgChangeKey -// Should be very expensive, because once this happens, an account is un-prunable -func handleMsgChangeKey(ctx sdk.Context, am AccountMapper, msg MsgChangeKey) sdk.Result { - - err := am.SetPubKey(ctx, msg.Address, msg.NewPubKey) - if err != nil { - return err.Result() - } - - return sdk.Result{ - Tags: sdk.NewTags("action", []byte("changePubkey"), "address", msg.Address.Bytes(), "pubkey", msg.NewPubKey.Bytes()), - } -} diff --git a/x/auth/mapper.go b/x/auth/mapper.go index b4364f768..4baa9c466 100644 --- a/x/auth/mapper.go +++ b/x/auth/mapper.go @@ -100,17 +100,6 @@ func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.Address) (crypto.Pub return acc.GetPubKey(), nil } -// Sets the PubKey of the account at address -func (am AccountMapper) SetPubKey(ctx sdk.Context, addr sdk.Address, newPubKey crypto.PubKey) sdk.Error { - acc := am.GetAccount(ctx, addr) - if acc == nil { - return sdk.ErrUnknownAddress(addr.String()) - } - acc.SetPubKey(newPubKey) - am.SetAccount(ctx, acc) - return nil -} - // Returns the Sequence of the account at address func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.Address) (int64, sdk.Error) { acc := am.GetAccount(ctx, addr) diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go index bd56a538c..4442200f1 100644 --- a/x/auth/mock/auth_app_test.go +++ b/x/auth/mock/auth_app_test.go @@ -3,14 +3,11 @@ package mock import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" - abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" ) @@ -35,64 +32,7 @@ func getMockApp(t *testing.T) *App { coinKeeper := bank.NewKeeper(mapp.AccountMapper) mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) - mapp.Router().AddRoute("auth", auth.NewHandler(mapp.AccountMapper)) require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) return mapp } - -func TestMsgChangePubKey(t *testing.T) { - mapp := getMockApp(t) - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins := sdk.Coins{sdk.NewCoin("foocoin", 77)} - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - accs := []auth.Account{acc1} - - // Construct genesis state - SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1.(*auth.BaseAccount)) - - // Run a CheckDeliver - SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1}, []int64{0}, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 67)}) - CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 10)}) - - changePubKeyMsg := auth.MsgChangeKey{ - Address: addr1, - NewPubKey: priv2.PubKey(), - } - - mapp.BeginBlock(abci.RequestBeginBlock{}) - ctxDeliver := mapp.BaseApp.NewContext(false, abci.Header{}) - acc2 := mapp.AccountMapper.GetAccount(ctxDeliver, addr1) - - // send a MsgChangePubKey - SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{changePubKeyMsg}, []int64{0}, []int64{1}, true, priv1) - acc2 = mapp.AccountMapper.GetAccount(ctxDeliver, addr1) - - assert.True(t, priv2.PubKey().Equals(acc2.GetPubKey())) - - // signing a SendMsg with the old privKey should be an auth error - mapp.BeginBlock(abci.RequestBeginBlock{}) - tx := GenTx([]sdk.Msg{sendMsg1}, []int64{0}, []int64{2}, priv1) - res := mapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the new correct priv key should work - SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1}, []int64{0}, []int64{2}, true, priv2) - - // Check balances - CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("foocoin", 57)}) - CheckBalance(t, mapp, addr2, sdk.Coins{sdk.NewCoin("foocoin", 20)}) -} diff --git a/x/auth/msgs.go b/x/auth/msgs.go deleted file mode 100644 index 3eb5cc8ba..000000000 --- a/x/auth/msgs.go +++ /dev/null @@ -1,41 +0,0 @@ -package auth - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// MsgChangeKey - high level transaction of the auth module -type MsgChangeKey struct { - Address sdk.Address `json:"address"` - NewPubKey crypto.PubKey `json:"public_key"` -} - -var _ sdk.Msg = MsgChangeKey{} - -// NewMsgChangeKey - msg to claim an account and set the PubKey -func NewMsgChangeKey(addr sdk.Address, pubkey crypto.PubKey) MsgChangeKey { - return MsgChangeKey{Address: addr, NewPubKey: pubkey} -} - -// Implements Msg. -func (msg MsgChangeKey) Type() string { return "auth" } - -// Implements Msg. -func (msg MsgChangeKey) ValidateBasic() sdk.Error { - return nil -} - -// Implements Msg. -func (msg MsgChangeKey) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form - if err != nil { - panic(err) - } - return b -} - -// Implements Msg. -func (msg MsgChangeKey) GetSigners() []sdk.Address { - return []sdk.Address{msg.Address} -} diff --git a/x/auth/msgs_test.go b/x/auth/msgs_test.go deleted file mode 100644 index 30c98b073..000000000 --- a/x/auth/msgs_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package auth - -import ( - "testing" - - "github.com/stretchr/testify/assert" - crypto "github.com/tendermint/go-crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestNewMsgChangeKey(t *testing.T) {} - -func TestMsgChangeKeyType(t *testing.T) { - addr1 := sdk.Address([]byte("input")) - newPubKey := crypto.GenPrivKeyEd25519().PubKey() - - var msg = MsgChangeKey{ - Address: addr1, - NewPubKey: newPubKey, - } - - assert.Equal(t, msg.Type(), "auth") -} - -func TestMsgChangeKeyValidation(t *testing.T) { - - addr1 := sdk.Address([]byte("input")) - - // emptyPubKey := crypto.PubKeyEd25519{} - // var msg = MsgChangeKey{ - // Address: addr1, - // NewPubKey: emptyPubKey, - // } - - // // fmt.Println(msg.NewPubKey.Empty()) - // fmt.Println(msg.NewPubKey.Bytes()) - - // assert.NotNil(t, msg.ValidateBasic()) - - newPubKey := crypto.GenPrivKeyEd25519().PubKey() - msg := MsgChangeKey{ - Address: addr1, - NewPubKey: newPubKey, - } - assert.Nil(t, msg.ValidateBasic()) -} diff --git a/x/auth/wire.go b/x/auth/wire.go index 6e430be4c..e22151101 100644 --- a/x/auth/wire.go +++ b/x/auth/wire.go @@ -8,7 +8,6 @@ import ( func RegisterWire(cdc *wire.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) - cdc.RegisterConcrete(MsgChangeKey{}, "auth/ChangeKey", nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } From 15bba919e2bd5bd6a3e8f1465dda174bb85d69c7 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Jun 2018 13:08:50 -0700 Subject: [PATCH 05/22] Merge PR #1341: Switch gometalinter to stable * Switch gometalinter to stable * Delete empty folder --- .circleci/config.yml | 11 ++- CHANGELOG.md | 1 + Makefile | 2 +- tools/Gopkg.lock | 56 -------------- tools/Gopkg.toml | 34 --------- tools/Makefile | 34 ++------- tools/bin/.gitkeep | 0 tools/go-vendorinstall/main.go | 129 --------------------------------- tools/main.go | 11 --- 9 files changed, 14 insertions(+), 264 deletions(-) delete mode 100644 tools/Gopkg.lock delete mode 100644 tools/Gopkg.toml delete mode 100644 tools/bin/.gitkeep delete mode 100644 tools/go-vendorinstall/main.go delete mode 100644 tools/main.go diff --git a/.circleci/config.yml b/.circleci/config.yml index a873c8190..9464e110c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,14 +61,13 @@ jobs: name: Get metalinter command: | export PATH="$GOBIN:$PATH" - go get -u github.com/tendermint/lint/golint - go get -u github.com/alecthomas/gometalinter + make get_tools - run: name: Lint source command: | export PATH="$GOBIN:$PATH" - gometalinter --disable-all --enable='golint' --vendor ./... - + gometalinter.v2 --disable-all --enable='golint' --vendor ./... + test_unit: <<: *defaults parallelism: 4 @@ -84,7 +83,7 @@ jobs: command: | export PATH="$GOBIN:$PATH" make test_unit - + test_cli: <<: *defaults parallelism: 1 @@ -100,7 +99,7 @@ jobs: command: | export PATH="$GOBIN:$PATH" make test_cli_retry - + test_cover: <<: *defaults parallelism: 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f168505..d9a67d348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ FEATURES * Proposals need deposits to be votable; deposits are burned if proposal fails * Delegators delegate votes to validator by default but can override (for their stake) * [tools] make get_tools installs tendermint's linter, and gometalinter +* [tools] Switch gometalinter to the stable version FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer diff --git a/Makefile b/Makefile index 8d7fb0593..a4d7e29c3 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ test_cover: @bash tests/test_cover.sh test_lint: - gometalinter --disable-all --enable='golint' --vendor ./... + gometalinter.v2 --disable-all --enable='golint' --vendor ./... benchmark: @go test -bench=. $(PACKAGES_NOCLITEST) diff --git a/tools/Gopkg.lock b/tools/Gopkg.lock deleted file mode 100644 index 92fe1e260..000000000 --- a/tools/Gopkg.lock +++ /dev/null @@ -1,56 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/alecthomas/gometalinter" - packages = ["."] - revision = "46cc1ea3778b247666c2949669a3333c532fa9c6" - version = "v2.0.5" - -[[projects]] - branch = "master" - name = "github.com/alecthomas/units" - packages = ["."] - revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" - -[[projects]] - branch = "master" - name = "github.com/google/shlex" - packages = ["."] - revision = "6f45313302b9c56850fc17f99e40caebce98c716" - -[[projects]] - name = "github.com/nicksnyder/go-i18n" - packages = [ - "i18n", - "i18n/bundle", - "i18n/language", - "i18n/translation" - ] - revision = "0dc1626d56435e9d605a29875701721c54bc9bbd" - version = "v1.10.0" - -[[projects]] - name = "github.com/pelletier/go-toml" - packages = ["."] - revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" - version = "v1.1.0" - -[[projects]] - branch = "v3-unstable" - name = "gopkg.in/alecthomas/kingpin.v3-unstable" - packages = ["."] - revision = "b8d601de6db1f3b56a99ffe9051eb708574bc1cd" - -[[projects]] - name = "gopkg.in/yaml.v2" - packages = ["."] - revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" - version = "v2.1.1" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "bb8cda576a5c4dda202435f43a46ae50a254181a4bf22c6af6f4d3d03079d509" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/tools/Gopkg.toml b/tools/Gopkg.toml deleted file mode 100644 index 97fb62975..000000000 --- a/tools/Gopkg.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - -[[constraint]] - name = "github.com/alecthomas/gometalinter" - version = "2.0.5" - -[prune] - go-tests = true - unused-packages = true - diff --git a/tools/Makefile b/tools/Makefile index 41178a2f1..c623ba46f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,4 +1,4 @@ -all: install +all: get_tools ######################################## @@ -6,10 +6,10 @@ all: install DEP = github.com/golang/dep/cmd/dep GOLINT = github.com/tendermint/lint/golint -GOMETALINTER = github.com/alecthomas/gometalinter +GOMETALINTER = gopkg.in/alecthomas/gometalinter.v2 DEP_CHECK := $(shell command -v dep 2> /dev/null) GOLINT_CHECK := $(shell command -v golint 2> /dev/null) -GOMETALINTER_CHECK := $(shell command -v gometalinter 2> /dev/null) +GOMETALINTER_CHECK := $(shell command -v gometalinter.v2 2> /dev/null) check_tools: ifndef DEP_CHECK @@ -42,9 +42,9 @@ else go get -v $(GOLINT) endif ifdef GOMETALINTER_CHECK - @echo "Gometalinter is already installed. Run 'make update_tools' to update." + @echo "Gometalinter.v2 is already installed. Run 'make update_tools' to update." else - @echo "Installing gometalinter" + @echo "Installing gometalinter.v2" go get -v $(GOMETALINTER) endif @@ -53,30 +53,10 @@ update_tools: go get -u -v $(DEP) @echo "Updating tendermint/golint" go get -u -v $(GOLINT) - @echo "Updating gometalinter" + @echo "Updating gometalinter.v2" go get -u -v $(GOMETALINTER) - -######################################## -### Install tools - - -get_vendor_deps: check_tools - @rm -rf vendor/ - @echo "--> Running dep ensure" - @dep ensure -v - -install: get_vendor_deps - @echo "Installing tools" - @echo "Install go-vendorinstall" - go build -o bin/go-vendorinstall go-vendorinstall/*.go - - @echo "Install gometalinter.v2" - GOBIN="$(CURDIR)/bin" ./bin/go-vendorinstall github.com/alecthomas/gometalinter - - @echo "Done installing tools" - # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check_tools install_tools update_tools get_vendor_deps install +.PHONY: check_tools get_tools update_tools diff --git a/tools/bin/.gitkeep b/tools/bin/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/go-vendorinstall/main.go b/tools/go-vendorinstall/main.go deleted file mode 100644 index c42e678f2..000000000 --- a/tools/go-vendorinstall/main.go +++ /dev/null @@ -1,129 +0,0 @@ -// https://raw.githubusercontent.com/roboll/go-vendorinstall/a3e9f0a5d5861b3bb16b93200b2c359c9846b3c5/main.go - -package main - -import ( - "errors" - "flag" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" -) - -var ( - source = flag.String("source", "vendor", "source directory") - target = flag.String("target", "", "target directory (defaults to $GOBIN, if not set $GOPATH/bin)") - commands = flag.String("commands", "", "comma separated list of commands to execute after go install in temporary environment") - quiet = flag.Bool("quiet", false, "disable output") -) - -func main() { - flag.Parse() - - packages := flag.Args() - if len(packages) < 1 { - fail(errors.New("no packages: specify a package")) - } - - gopath, err := ioutil.TempDir("", "go-vendorinstall-gopath") - if err != nil { - fail(err) - } - print(fmt.Sprintf("gopath: %s", gopath)) - defer func() { - if err := os.RemoveAll(gopath); err != nil { - fail(err) - } - }() - - if len(*target) == 0 { - if gobin := os.Getenv("GOBIN"); len(gobin) > 0 { - target = &gobin - } else { - bin := fmt.Sprintf("%s/bin", os.Getenv("GOPATH")) - target = &bin - } - } - - gobin, err := filepath.Abs(*target) - if err != nil { - fail(err) - } - print(fmt.Sprintf("gobin: %s", gobin)) - - if err := link(gopath, *source); err != nil { - fail(err) - } - - oldpath := os.Getenv("PATH") - path := fmt.Sprintf("%s%s%s", gobin, string(os.PathListSeparator), os.Getenv("PATH")) - os.Setenv("PATH", fmt.Sprintf("%s%s%s", gobin, string(os.PathListSeparator), os.Getenv("PATH"))) - defer os.Setenv("PATH", oldpath) - - env := []string{fmt.Sprintf("PATH=%s", path), fmt.Sprintf("GOPATH=%s", gopath), fmt.Sprintf("GOBIN=%s", gobin)} - args := append([]string{"install"}, packages...) - if out, err := doexec("go", gopath, args, env); err != nil { - print(string(out)) - fail(err) - } - - if len(*commands) > 0 { - for _, cmd := range strings.Split(*commands, ",") { - split := strings.Split(cmd, " ") - if out, err := doexec(split[0], gopath, split[1:], env); err != nil { - print(string(out)) - fail(err) - } - } - } -} - -func print(msg string) { - if !*quiet { - fmt.Println(msg) - } -} - -func fail(err error) { - fmt.Printf("error: %s", err.Error()) - os.Exit(1) -} - -func link(gopath, source string) error { - srcdir, err := filepath.Abs(source) - if err != nil { - return err - } - - linkto := filepath.Join(gopath, "src") - if err := os.MkdirAll(linkto, 0777); err != nil { - return err - } - - files, err := ioutil.ReadDir(srcdir) - if err != nil { - return err - } - - for _, file := range files { - real := filepath.Join(srcdir, file.Name()) - link := filepath.Join(linkto, file.Name()) - if err := os.Symlink(real, link); err != nil { - return err - } - } - - return nil -} - -func doexec(bin, dir string, args []string, env []string) ([]byte, error) { - print(fmt.Sprintf("%s %s", bin, strings.Join(args, " "))) - cmd := exec.Command(bin, args...) - cmd.Env = env - cmd.Dir = dir - - return cmd.CombinedOutput() -} diff --git a/tools/main.go b/tools/main.go deleted file mode 100644 index 104268010..000000000 --- a/tools/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -// Include dependencies here so dep picks them up -// and installs sub-dependencies. - -// TODO: Ideally this gets auto-imported on dep update. -// Any way to make that happen? -// NOTE: problems with this import because its a main not a lib -// _ "github.com/alecthomas/gometalinter" - -func main() {} From b098c0da7443fc1306501458cc8097e4a4be47cb Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 22 Jun 2018 22:58:48 +0200 Subject: [PATCH 06/22] Merge PR #1344: Swap parallelism: 4 to parallelism: 1 --- .circleci/config.yml | 6 +++--- CHANGELOG.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9464e110c..13c6f330e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,7 +49,7 @@ jobs: lint: <<: *defaults - parallelism: 4 + parallelism: 1 steps: - attach_workspace: at: /tmp/workspace @@ -70,7 +70,7 @@ jobs: test_unit: <<: *defaults - parallelism: 4 + parallelism: 1 steps: - attach_workspace: at: /tmp/workspace @@ -102,7 +102,7 @@ jobs: test_cover: <<: *defaults - parallelism: 4 + parallelism: 1 steps: - attach_workspace: at: /tmp/workspace diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a67d348..915dec197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ FIXES * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile * Fixed bug where chain ID wasn't passed properly in x/bank REST handler * Fixed bug where `democli account` didn't decode the account data correctly +* \#1343 - fixed unnecessary parallelism in CI ## 0.19.0 From 296ba2d56e57894d447f1f237a4bf51dd679e555 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Fri, 22 Jun 2018 19:45:24 -0700 Subject: [PATCH 07/22] Merge PR #1350: Update README.md --- cmd/gaia/testnets/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gaia/testnets/README.md b/cmd/gaia/testnets/README.md index 5b6df83ad..57bbfc48a 100644 --- a/cmd/gaia/testnets/README.md +++ b/cmd/gaia/testnets/README.md @@ -110,7 +110,7 @@ Your node needs to know how to find peers. You'll need to add healthy seed nodes ```toml # Comma separated list of seed nodes to connect to -seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@seed.cosmos.cryptium.ch:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@35.198.166.171:46656,032fa56301de335d835057fb6ad9f7ce2242a66d@165.227.236.213:46656" +seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,1e124dd15bd9955a7ea844ab003b1b47f0998b70@seed.cosmos.cryptium.ch:46656" ``` If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorecosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6. From eb097c4c5c7e60db5002a96d6628b677e2b90a7c Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 25 Jun 2018 09:33:07 -0700 Subject: [PATCH 08/22] Merge PR 1361: server: Use differing defaults from tendermint When loading the config file, this now checks in the sdk if the file already exists. If not, it writes a config with different defaults. The defaults differ by having the profiler listen address set, and increasing the receive / send rates. --- CHANGELOG.md | 1 + server/util.go | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 915dec197..7bc975ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ FEATURES * Delegators delegate votes to validator by default but can override (for their stake) * [tools] make get_tools installs tendermint's linter, and gometalinter * [tools] Switch gometalinter to the stable version +* [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer diff --git a/server/util.go b/server/util.go index 4bf29cd7d..e0746f752 100644 --- a/server/util.go +++ b/server/util.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net" "os" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -46,7 +47,7 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error if cmd.Name() == version.VersionCmd.Name() { return nil } - config, err := tcmd.ParseConfig() + config, err := interceptLoadConfig() if err != nil { return err } @@ -65,6 +66,26 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error } } +// If a new config is created, change some of the default tendermint settings +func interceptLoadConfig() (conf *cfg.Config, err error) { + tmpConf := cfg.DefaultConfig() + viper.Unmarshal(tmpConf) + rootDir := tmpConf.RootDir + configFilePath := filepath.Join(rootDir, "config/config.toml") + // Intercept only if the file doesn't already exist + if _, err := os.Stat(configFilePath); os.IsNotExist(err) { + // the following parse config is needed to create directories + sdkDefaultConfig, _ := tcmd.ParseConfig() + sdkDefaultConfig.ProfListenAddress = "prof_laddr=localhost:6060" + sdkDefaultConfig.P2P.RecvRate = 5120000 + sdkDefaultConfig.P2P.SendRate = 5120000 + cfg.WriteConfigFile(configFilePath, sdkDefaultConfig) + // Fall through, just so that its parsed into memory. + } + conf, err = tcmd.ParseConfig() + return +} + // add server commands func AddCommands( ctx *Context, cdc *wire.Codec, From 2e97baabf63aac719852e0e77fbf57d7df87d12c Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 25 Jun 2018 11:23:17 -0700 Subject: [PATCH 09/22] Merge PR #1358: Fix typos and gofmt files * Fix typos * gofmt -s files * Add mispellings and gofmt checks to circle CI * circleci: Install misspell in the linting step --- .circleci/config.yml | 5 +++-- CHANGELOG.md | 1 + baseapp/baseapp.go | 2 +- client/lcd/lcd_test.go | 14 +++++++------- examples/democoin/x/cool/app_test.go | 2 +- store/iavlstore_test.go | 24 ++++++++++++------------ types/errors.go | 2 +- types/gas.go | 2 -- x/auth/stdtx.go | 2 +- x/bank/msgs_test.go | 4 ++-- x/gov/keeper.go | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 13c6f330e..ce01e3971 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,12 +62,13 @@ jobs: command: | export PATH="$GOBIN:$PATH" make get_tools + go get -u github.com/client9/misspell/cmd/misspell - run: name: Lint source command: | export PATH="$GOBIN:$PATH" - gometalinter.v2 --disable-all --enable='golint' --vendor ./... - + gometalinter.v2 --disable-all --enable='golint' --enable='misspell' --vendor ./... + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s test_unit: <<: *defaults parallelism: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc975ce5..8f1ef031e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ FEATURES * Delegators delegate votes to validator by default but can override (for their stake) * [tools] make get_tools installs tendermint's linter, and gometalinter * [tools] Switch gometalinter to the stable version +* [tools] Add checking for misspellings and for incorrectly formatted files in circle CI * [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates FIXES diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index ee1ec4ca9..3150e84a5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -179,7 +179,7 @@ func (app *BaseApp) LastCommitID() sdk.CommitID { return app.cms.LastCommitID() } -// the last commited block height +// the last committed block height func (app *BaseApp) LastBlockHeight() int64 { return app.cms.LastCommitID().Version } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 1c8f872fc..b56c27b95 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -236,7 +236,7 @@ func TestCoinSend(t *testing.T) { receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -271,7 +271,7 @@ func TestIBCTransfer(t *testing.T) { tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -387,7 +387,7 @@ func TestBonding(t *testing.T) { resultTx := doBond(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -412,7 +412,7 @@ func TestBonding(t *testing.T) { bond = getDelegation(t, port, addr, validator1Owner) assert.Equal(t, "30/1", bond.Shares.String()) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -434,7 +434,7 @@ func TestSubmitProposal(t *testing.T) { resultTx := doSubmitProposal(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -456,7 +456,7 @@ func TestDeposit(t *testing.T) { resultTx := doSubmitProposal(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) @@ -486,7 +486,7 @@ func TestVote(t *testing.T) { resultTx := doSubmitProposal(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) - // check if tx was commited + // check if tx was committed assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 487111bb3..5ac7a55a1 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -100,7 +100,7 @@ func TestMsgQuiz(t *testing.T) { mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, priv1) // reset the trend mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relavent again + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relevant again mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}}) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool } diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index e324758c5..e2f2f9c17 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -170,9 +170,9 @@ func TestIAVLSubspaceIterator(t *testing.T) { iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ - []byte{byte(55), byte(255), byte(255), byte(0)}, - []byte{byte(55), byte(255), byte(255), byte(1)}, - []byte{byte(55), byte(255), byte(255), byte(255)}, + {byte(55), byte(255), byte(255), byte(0)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(255)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] @@ -185,9 +185,9 @@ func TestIAVLSubspaceIterator(t *testing.T) { iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ - []byte{byte(255), byte(255), byte(0)}, - []byte{byte(255), byte(255), byte(1)}, - []byte{byte(255), byte(255), byte(255)}, + {byte(255), byte(255), byte(0)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(255)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] @@ -229,9 +229,9 @@ func TestIAVLReverseSubspaceIterator(t *testing.T) { iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ - []byte{byte(55), byte(255), byte(255), byte(255)}, - []byte{byte(55), byte(255), byte(255), byte(1)}, - []byte{byte(55), byte(255), byte(255), byte(0)}, + {byte(55), byte(255), byte(255), byte(255)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(0)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] @@ -244,9 +244,9 @@ func TestIAVLReverseSubspaceIterator(t *testing.T) { iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ - []byte{byte(255), byte(255), byte(255)}, - []byte{byte(255), byte(255), byte(1)}, - []byte{byte(255), byte(255), byte(0)}, + {byte(255), byte(255), byte(255)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(0)}, } for i = 0; iter.Valid(); iter.Next() { expectedKey := expected2[i] diff --git a/types/errors.go b/types/errors.go index f76b171af..c81808427 100644 --- a/types/errors.go +++ b/types/errors.go @@ -77,7 +77,7 @@ func CodeToDefaultMsg(code CodeType) string { case CodeUnauthorized: return "unauthorized" case CodeInsufficientFunds: - return "insufficent funds" + return "insufficient funds" case CodeUnknownRequest: return "unknown request" case CodeInvalidAddress: diff --git a/types/gas.go b/types/gas.go index 49bfa27ec..18d3aa570 100644 --- a/types/gas.go +++ b/types/gas.go @@ -1,7 +1,5 @@ package types -import () - // Gas measured by the SDK type Gas = int64 diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 8f3e52764..07b8671b7 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -34,7 +34,7 @@ func (tx StdTx) GetMsgs() []sdk.Msg { return tx.Msgs } // Addresses are returned in a determistic order. // They are accumulated from the GetSigners method for each Msg // in the order they appear in tx.GetMsgs(). -// Duplicate addresses will be ommitted. +// Duplicate addresses will be omitted. func (tx StdTx) GetSigners() []sdk.Address { seen := map[string]bool{} var signers []sdk.Address diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index b866e497b..e5cb75907 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -132,13 +132,13 @@ func TestMsgSendValidation(t *testing.T) { }{ {false, MsgSend{}}, // no input or output {false, MsgSend{Inputs: []Input{input1}}}, // just input - {false, MsgSend{Outputs: []Output{output1}}}, // just ouput + {false, MsgSend{Outputs: []Output{output1}}}, // just output {false, MsgSend{ Inputs: []Input{NewInput(emptyAddr, atom123)}, // invalid input Outputs: []Output{output1}}}, {false, MsgSend{ Inputs: []Input{input1}, - Outputs: []Output{{emptyAddr, atom123}}}, // invalid ouput + Outputs: []Output{{emptyAddr, atom123}}}, // invalid output }, {false, MsgSend{ Inputs: []Input{input1}, diff --git a/x/gov/keeper.go b/x/gov/keeper.go index d0ce12982..bc9153304 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -243,7 +243,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } - // Subtract coins from depositers account + // Subtract coins from depositer's account _, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount) if err != nil { return err, false From f2a83a07f9fa23ac66f2ae43eeb6ee5182b04afd Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 25 Jun 2018 14:53:48 -0700 Subject: [PATCH 10/22] Merge PR #1366: tests: add method to wait for n blocks to pass Adds a helper method to tests/util.go for waiting for N blocks to pass. This is useful for situations when you need to wait for multiple blocks to pass, but don't know the current block number. In general, this is safer than using "wait for height", since the block height could have advanced further than expected while the test was running. Resolves remaining point in #1283 --- CHANGELOG.md | 1 + tests/util.go | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1ef031e..789361078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ FEATURES * [tools] Switch gometalinter to the stable version * [tools] Add checking for misspellings and for incorrectly formatted files in circle CI * [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates +* [tests] Add WaitForNextNBlocksTM helper method FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer diff --git a/tests/util.go b/tests/util.go index 9e2c0d044..cc95f1e24 100644 --- a/tests/util.go +++ b/tests/util.go @@ -15,13 +15,19 @@ import ( // Wait for the next tendermint block from the Tendermint RPC // on localhost func WaitForNextHeightTM(port string) { + WaitForNextNBlocksTM(1, port) +} + +// Wait for N tendermint blocks to pass using the Tendermint RPC +// on localhost +func WaitForNextNBlocksTM(n int64, port string) { url := fmt.Sprintf("http://localhost:%v", port) cl := tmclient.NewHTTP(url, "/websocket") resBlock, err := cl.Block(nil) if err != nil { panic(err) } - waitForHeightTM(resBlock.Block.Height+1, url) + waitForHeightTM(resBlock.Block.Height+n, url) } // Wait for the given height from the Tendermint RPC From 893bb4027da0bf2ef172f4355fdca45f4bc349bd Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 25 Jun 2018 19:37:21 -0700 Subject: [PATCH 11/22] Merge PR #1377: types: remove GetMemo from Tx This method is an unneccessary requirement on Tx. Auth casts the Tx to StdTx, so the memo can be obtained in previous usecases from StdTx. This also makes it more clear for when something is implementing StdTx vs Tx. Resolves #1375 --- CHANGELOG.md | 1 + types/tx_msg.go | 3 --- x/auth/ante.go | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 789361078..f46d00949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ BREAKING CHANGES * Signers of a transaction now only sign over their account and sequence number * Removed MsgChangePubKey from auth * Removed setPubKey from account mapper +* Removed GetMemo from Tx (it is still on StdTx) FEATURES * [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag diff --git a/types/tx_msg.go b/types/tx_msg.go index 0ed49906f..12146f5b7 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -31,9 +31,6 @@ type Tx interface { // Gets the Msg. GetMsgs() []Msg - - // Gets the memo. - GetMemo() string } //__________________________________________________________ diff --git a/x/auth/ante.go b/x/auth/ante.go index 946a278e6..616ffd2cf 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -38,7 +38,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { true } - memo := tx.GetMemo() + memo := stdTx.GetMemo() if len(memo) > maxMemoCharacters { return ctx, @@ -85,7 +85,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { signerAddr, sig := signerAddrs[i], sigs[i] // check signature, return account with incremented nonce - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, tx.GetMemo()) + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) signerAcc, res := processSig( ctx, am, signerAddr, sig, signBytes, From 9f30a90980799c5f363a5f50cffa3bcea81b6421 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 25 Jun 2018 20:03:55 -0700 Subject: [PATCH 12/22] Merge PR #1374: types/rat: Fix overflowing in printing This now uses the underlying golang big.rat's string function, instead of casting to num and den which are int64s. Closes #1258 --- CHANGELOG.md | 1 + types/rational.go | 2 +- types/rational_test.go | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46d00949..da8bfb7f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ FIXES * Fixed bug where chain ID wasn't passed properly in x/bank REST handler * Fixed bug where `democli account` didn't decode the account data correctly * \#1343 - fixed unnecessary parallelism in CI +* \#1258 - printing big.rat's can no longer overflow int64 ## 0.19.0 diff --git a/types/rational.go b/types/rational.go index 6e542e110..dc6569be3 100644 --- a/types/rational.go +++ b/types/rational.go @@ -115,7 +115,7 @@ func (r Rat) Mul(r2 Rat) Rat { return Rat{*new(big.Rat).Mul(&(r.Rat), &(r2.Ra func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Rat))} } // Quo - quotient func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction -func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } +func (r Rat) String() string { return r.Rat.String() } var ( zero = big.NewInt(0) diff --git a/types/rational_test.go b/types/rational_test.go index 30abb1a51..2483704ed 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -100,7 +100,7 @@ func TestEqualities(t *testing.T) { } -func TestArithmatic(t *testing.T) { +func TestArithmetic(t *testing.T) { tests := []struct { r1, r2 Rat resMul, resDiv, resAdd, resSub Rat @@ -297,3 +297,14 @@ func TestRatsEqual(t *testing.T) { } } + +func TestStringOverflow(t *testing.T) { + // two random 64 bit primes + rat1 := NewRat(5164315003622678713, 4389711697696177267) + rat2 := NewRat(-3179849666053572961, 8459429845579852627) + rat3 := rat1.Add(rat2) + assert.Equal(t, + "29728537197630860939575850336935951464/37134458148982045574552091851127630409", + rat3.String(), + ) +} From ff09c7647176c242facf9b41c088e810a290da61 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 26 Jun 2018 09:03:27 +0200 Subject: [PATCH 13/22] Merge PR #1383: Pin exact versions --- Gopkg.lock | 12 ++++++------ Gopkg.toml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 701f383af..f3d8f3acc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -256,7 +256,7 @@ "leveldb/table", "leveldb/util" ] - revision = "e2150783cd35f5b607daca48afd8c57ec54cc995" + revision = "0d5a0ceb10cf9ab89fdd744cc8c50a83134f6697" [[projects]] name = "github.com/tendermint/abci" @@ -350,7 +350,6 @@ revision = "696e8c6f9e950eec15f150f314d2dd9ddf6bc601" [[projects]] - branch = "develop" name = "github.com/tendermint/tmlibs" packages = [ "autofile", @@ -373,6 +372,7 @@ packages = [ "blowfish", "curve25519", + "internal/subtle", "nacl/box", "nacl/secretbox", "openpgp/armor", @@ -381,7 +381,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9" + revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" [[projects]] branch = "master" @@ -395,13 +395,13 @@ "internal/timeseries", "trace" ] - revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" + revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "bff228c7b664c5fce602223a05fb708fd8654986" + revision = "a200a19cb90b19de298170992778b1fda7217bd6" [[projects]] name = "golang.org/x/text" @@ -462,6 +462,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d02a24bcfd8bded901e1b154e19b81ff797d3921046ede19d1d11eed61e871e7" + inputs-digest = "8ad5e4b90e57805024944a6ee5eed246f109c18724d453093e82bac1469dbd9e" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 49bbf9f79..a8b510e6a 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/tendermint/go-crypto" - version = "~0.6.2" + version = "=0.6.2" [[constraint]] name = "github.com/tendermint/go-amino" @@ -66,7 +66,7 @@ [[constraint]] name = "github.com/tendermint/iavl" - version = "0.8.0-rc0" + version = "=0.8.0-rc0" [[constraint]] name = "github.com/tendermint/tendermint" @@ -74,7 +74,7 @@ [[override]] name = "github.com/tendermint/tmlibs" - branch = "develop" + revision = "0c98d10b4ffbd87978d79c160e835b3d3df241ec" # this got updated and broke, so locked to an old working commit ... [[override]] From 4f57a765ad196659085173ea27c68205d8d8261e Mon Sep 17 00:00:00 2001 From: 7768 <39715573+7768@users.noreply.github.com> Date: Tue, 26 Jun 2018 13:26:12 -0400 Subject: [PATCH 14/22] Merge PR 1354: CLI: Show fractional in human-readable format Fix https://github.com/cosmos/cosmos-sdk/issues/1353 before: ``` Shares: Status Bonded, Amount: -6508168128760126341/-3308135364330552608 Delegator Shares: -6508168128760126341/-3308135364330552608 ``` after: ``` Shares: Status Bonded, Amount: 2.0000000000 Delegator Shares: 2.0000000000 ``` --- CHANGELOG.md | 1 + types/rational.go | 1 + x/stake/validator.go | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da8bfb7f0..f7e8e2016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ FIXES * Fixed bug where chain ID wasn't passed properly in x/bank REST handler * Fixed bug where `democli account` didn't decode the account data correctly * \#1343 - fixed unnecessary parallelism in CI +* \#1353 - CLI: Show pool shares fractions in human-readable format * \#1258 - printing big.rat's can no longer overflow int64 ## 0.19.0 diff --git a/types/rational.go b/types/rational.go index dc6569be3..5adf78672 100644 --- a/types/rational.go +++ b/types/rational.go @@ -116,6 +116,7 @@ func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Ra func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction func (r Rat) String() string { return r.Rat.String() } +func (r Rat) FloatString() string { return r.Rat.FloatString(10) } // a human-friendly string format. The last digit is rounded to nearest, with halves rounded away from zero. var ( zero = big.NewInt(0) diff --git a/x/stake/validator.go b/x/stake/validator.go index e21b8f237..e0e396385 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -272,8 +272,8 @@ func (v Validator) HumanReadableString() (string, error) { resp := "Validator \n" resp += fmt.Sprintf("Owner: %s\n", bechOwner) resp += fmt.Sprintf("Validator: %s\n", bechVal) - resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.String()) - resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) + resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.FloatString()) + resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.FloatString()) resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) From 8bdf06b9f608d1692fdb67858d66e8c435d71a78 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 26 Jun 2018 23:45:31 +0200 Subject: [PATCH 15/22] Remove Viper from ante handler --- CHANGELOG.md | 2 +- x/auth/ante.go | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7e8e2016..531a5263d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer * \#1052 - Make all now works * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile -* Fixed bug where chain ID wasn't passed properly in x/bank REST handler +* Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler * Fixed bug where `democli account` didn't decode the account data correctly * \#1343 - fixed unnecessary parallelism in CI * \#1353 - CLI: Show pool shares fractions in human-readable format diff --git a/x/auth/ante.go b/x/auth/ante.go index 616ffd2cf..5bcea123e 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -5,7 +5,6 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/viper" ) const ( @@ -72,12 +71,6 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { accNums[i] = sigs[i].AccountNumber } fee := stdTx.Fee - chainID := ctx.ChainID() - // XXX: major hack; need to get ChainID - // into the app right away (#565) - if chainID == "" { - chainID = viper.GetString("chain-id") - } // Check sig and nonce and collect signer accounts. var signerAccs = make([]Account, len(signerAddrs)) From 9ca3a16bb3689115705e28f4e7c908b517d9b254 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 26 Jun 2018 16:58:42 -0700 Subject: [PATCH 16/22] Merge PR #1397: Fix spelling changes * fixed spelling changes * extra spelling error --- docs/_attic/staking/local-testnet.rst | 2 +- docs/spec/staking/transactions.md | 2 +- x/stake/keeper.go | 6 +++--- x/stake/keeper_test.go | 2 +- x/stake/pool.go | 2 +- x/stake/validator.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_attic/staking/local-testnet.rst b/docs/_attic/staking/local-testnet.rst index b8d30d2e2..830830a6f 100644 --- a/docs/_attic/staking/local-testnet.rst +++ b/docs/_attic/staking/local-testnet.rst @@ -57,7 +57,7 @@ Start Nodes Now that we've initialized the chains, we can start both nodes: -NOTE: each command below must be started in seperate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided. +NOTE: each command below must be started in separate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided. :: diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 91df029c9..55b1a8ed1 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -167,7 +167,7 @@ startUnbonding(tx TxStartUnbonding): ### TxCompleteUnbonding Complete the unbonding and transfer the coins to the delegate. Perform any -slashing that occured during the unbonding period. +slashing that occurred during the unbonding period. ```golang type TxUnbondingComplete struct { diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 52ed82713..dd7a44663 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -234,7 +234,7 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator store.Set(GetValidatorKey(ownerAddr), bz) }() - // retreive the old validator record + // retrieve the old validator record oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { @@ -288,7 +288,7 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator // update the validator set for this validator updatedVal := k.updateBondedValidators(ctx, store, validator) - if updatedVal.Owner != nil { // updates to validator occured to be updated + if updatedVal.Owner != nil { // updates to validator occurred to be updated validator = updatedVal } return validator @@ -498,7 +498,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, store sdk.KVStore, validator Vali func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { - // first retreive the old validator record + // first retrieve the old validator record validator, found := k.GetValidator(ctx, address) if !found { return diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 7c163a434..7b8b9ddcc 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -300,7 +300,7 @@ func GetValidatorSortingMixed(t *testing.T) { assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) } -// TODO seperate out into multiple tests +// TODO separate out into multiple tests func TestGetValidatorsEdgeCases(t *testing.T) { ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) var found bool diff --git a/x/stake/pool.go b/x/stake/pool.go index 91e898770..4b65c0e8e 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -21,7 +21,7 @@ type Pool struct { DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) // Fee Related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calcualtions + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations } func (p Pool) equal(p2 Pool) bool { diff --git a/x/stake/validator.go b/x/stake/validator.go index e0e396385..ce6e7a7eb 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -279,7 +279,7 @@ func (v Validator) HumanReadableString() (string, error) { resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) - resp += fmt.Sprintf("Comission Change Rate: %s\n", v.CommissionChangeRate.String()) + resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String()) From d6df6b07d151f087ede88a7f1de8319f18cb2ec3 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 26 Jun 2018 18:10:34 -0700 Subject: [PATCH 17/22] Merge PR #1388: types/int: Switch Int, Uint to use pointers internally * types/int: Switch Int, Uint to use pointers internally This reduces the amount of pointer refs & derefs. * Fix nil pointers on unmarshalling amino * Fix elusive bug in marshalling with unitialized big int * Remove debug code * Switch big.rat to use pointers internally --- CHANGELOG.md | 2 + types/int.go | 102 +++++++++++++++++++++++++---------------- types/rational.go | 51 +++++++++++---------- types/rational_test.go | 6 +-- 4 files changed, 95 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 531a5263d..8b63fe13b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ FEATURES * [tools] Add checking for misspellings and for incorrectly formatted files in circle CI * [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates * [tests] Add WaitForNextNBlocksTM helper method +* [types] Switches internal representation of Int/Uint/Rat to use pointers +* [gaiad] unsafe_reset_all now resets addrbook.json FIXES * \#1259 - fix bug where certain tests that could have a nil pointer in defer diff --git a/types/int.go b/types/int.go index 7ac28233a..7d47070c3 100644 --- a/types/int.go +++ b/types/int.go @@ -60,17 +60,17 @@ func unmarshalJSON(i *big.Int, bz []byte) error { // Checks overflow, underflow and division by zero // Exists in range from -(2^255-1) to 2^255-1 type Int struct { - i big.Int + i *big.Int } // BigInt converts Int to big.Int func (i Int) BigInt() *big.Int { - return new(big.Int).Set(&(i.i)) + return new(big.Int).Set(i.i) } // NewInt constructs Int from int64 func NewInt(n int64) Int { - return Int{*big.NewInt(n)} + return Int{big.NewInt(n)} } // NewIntFromBigInt constructs Int from big.Int @@ -78,7 +78,7 @@ func NewIntFromBigInt(i *big.Int) Int { if i.BitLen() > 255 { panic("NewIntFromBigInt() out of bound") } - return Int{*i} + return Int{i} } // NewIntFromString constructs Int from string @@ -92,7 +92,7 @@ func NewIntFromString(s string) (res Int, ok bool) { ok = false return } - return Int{*i}, true + return Int{i}, true } // NewIntWithDecimal constructs Int with decimal @@ -103,14 +103,14 @@ func NewIntWithDecimal(n int64, dec int) Int { if i.BitLen() > 255 { panic("NewIntWithDecimal() out of bound") } - return Int{*i} + return Int{i} } // ZeroInt returns Int value with zero -func ZeroInt() Int { return Int{*big.NewInt(0)} } +func ZeroInt() Int { return Int{big.NewInt(0)} } // OneInt returns Int value with one -func OneInt() Int { return Int{*big.NewInt(1)} } +func OneInt() Int { return Int{big.NewInt(1)} } // Int64 converts Int to int64 // Panics if the value is out of range @@ -133,22 +133,22 @@ func (i Int) Sign() int { // Equal compares two Ints func (i Int) Equal(i2 Int) bool { - return equal(&(i.i), &(i2.i)) + return equal(i.i, i2.i) } // GT returns true if first Int is greater than second func (i Int) GT(i2 Int) bool { - return gt((&i.i), &(i2.i)) + return gt(i.i, i2.i) } // LT returns true if first Int is lesser than second func (i Int) LT(i2 Int) bool { - return lt((&i.i), &(i2.i)) + return lt(i.i, i2.i) } // Add adds Int from another func (i Int) Add(i2 Int) (res Int) { - res = Int{*add(&(i.i), &(i2.i))} + res = Int{add(i.i, i2.i)} // Check overflow if res.i.BitLen() > 255 { panic("Int overflow") @@ -163,7 +163,7 @@ func (i Int) AddRaw(i2 int64) Int { // Sub subtracts Int from another func (i Int) Sub(i2 Int) (res Int) { - res = Int{*sub(&(i.i), &(i2.i))} + res = Int{sub(i.i, i2.i)} // Check overflow if res.i.BitLen() > 255 { panic("Int overflow") @@ -182,7 +182,7 @@ func (i Int) Mul(i2 Int) (res Int) { if i.i.BitLen()+i2.i.BitLen()-1 > 255 { panic("Int overflow") } - res = Int{*mul(&(i.i), &(i2.i))} + res = Int{mul(i.i, i2.i)} // Check overflow if sign of both are same if res.i.BitLen() > 255 { panic("Int overflow") @@ -201,7 +201,7 @@ func (i Int) Div(i2 Int) (res Int) { if i2.i.Sign() == 0 { panic("Division by zero") } - return Int{*div(&(i.i), &(i2.i))} + return Int{div(i.i, i2.i)} } // DivRaw divides Int with int64 @@ -211,46 +211,58 @@ func (i Int) DivRaw(i2 int64) Int { // Neg negates Int func (i Int) Neg() (res Int) { - return Int{*neg(&(i.i))} + return Int{neg(i.i)} } // MarshalAmino defines custom encoding scheme func (i Int) MarshalAmino() (string, error) { - return marshalAmino(&(i.i)) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalAmino(i.i) } // UnmarshalAmino defines custom decoding scheme func (i *Int) UnmarshalAmino(text string) error { - return unmarshalAmino(&(i.i), text) + if i.i == nil { // Necessary since default Int initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalAmino(i.i, text) } // MarshalJSON defines custom encoding scheme func (i Int) MarshalJSON() ([]byte, error) { - return marshalJSON(&(i.i)) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalJSON(i.i) } // UnmarshalJSON defines custom decoding scheme func (i *Int) UnmarshalJSON(bz []byte) error { - return unmarshalJSON(&(i.i), bz) + if i.i == nil { // Necessary since default Int initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalJSON(i.i, bz) } // Int wraps integer with 256 bit range bound // Checks overflow, underflow and division by zero // Exists in range from 0 to 2^256-1 type Uint struct { - i big.Int + i *big.Int } // BigInt converts Uint to big.Unt func (i Uint) BigInt() *big.Int { - return new(big.Int).Set(&(i.i)) + return new(big.Int).Set(i.i) } // NewUint constructs Uint from int64 func NewUint(n uint64) Uint { i := new(big.Int) i.SetUint64(n) - return Uint{*i} + return Uint{i} } // NewUintFromBigUint constructs Uint from big.Uint @@ -259,7 +271,7 @@ func NewUintFromBigInt(i *big.Int) Uint { if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { panic("Uint overflow") } - return Uint{*i} + return Uint{i} } // NewUintFromString constructs Uint from string @@ -273,7 +285,7 @@ func NewUintFromString(s string) (res Uint, ok bool) { ok = false return } - return Uint{*i}, true + return Uint{i}, true } // NewUintWithDecimal constructs Uint with decimal @@ -284,14 +296,14 @@ func NewUintWithDecimal(n int64, dec int) Uint { if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { panic("NewUintWithDecimal() out of bound") } - return Uint{*i} + return Uint{i} } // ZeroUint returns Uint value with zero -func ZeroUint() Uint { return Uint{*big.NewInt(0)} } +func ZeroUint() Uint { return Uint{big.NewInt(0)} } // OneUint returns Uint value with one -func OneUint() Uint { return Uint{*big.NewInt(1)} } +func OneUint() Uint { return Uint{big.NewInt(1)} } // Uint64 converts Uint to uint64 // Panics if the value is out of range @@ -314,22 +326,22 @@ func (i Uint) Sign() int { // Equal compares two Uints func (i Uint) Equal(i2 Uint) bool { - return equal(&(i.i), &(i2.i)) + return equal(i.i, i2.i) } // GT returns true if first Uint is greater than second func (i Uint) GT(i2 Uint) bool { - return gt(&(i.i), &(i2.i)) + return gt(i.i, i2.i) } // LT returns true if first Uint is lesser than second func (i Uint) LT(i2 Uint) bool { - return lt(&(i.i), &(i2.i)) + return lt(i.i, i2.i) } // Add adds Uint from another func (i Uint) Add(i2 Uint) (res Uint) { - res = Uint{*add(&(i.i), &(i2.i))} + res = Uint{add(i.i, i2.i)} // Check overflow if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { panic("Uint overflow") @@ -344,7 +356,7 @@ func (i Uint) AddRaw(i2 uint64) Uint { // Sub subtracts Uint from another func (i Uint) Sub(i2 Uint) (res Uint) { - res = Uint{*sub(&(i.i), &(i2.i))} + res = Uint{sub(i.i, i2.i)} // Check overflow if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { panic("Uint overflow") @@ -363,7 +375,7 @@ func (i Uint) Mul(i2 Uint) (res Uint) { if i.i.BitLen()+i2.i.BitLen()-1 > 256 { panic("Uint overflow") } - res = Uint{*mul(&(i.i), &(i2.i))} + res = Uint{mul(i.i, i2.i)} // Check overflow if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 { panic("Uint overflow") @@ -382,7 +394,7 @@ func (i Uint) Div(i2 Uint) (res Uint) { if i2.Sign() == 0 { panic("division-by-zero") } - return Uint{*div(&(i.i), &(i2.i))} + return Uint{div(i.i, i2.i)} } // Div divides Uint with int64 @@ -392,20 +404,32 @@ func (i Uint) DivRaw(i2 uint64) Uint { // MarshalAmino defines custom encoding scheme func (i Uint) MarshalAmino() (string, error) { - return marshalAmino(&(i.i)) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalAmino(i.i) } // UnmarshalAmino defines custom decoding scheme func (i *Uint) UnmarshalAmino(text string) error { - return unmarshalAmino(&(i.i), text) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalAmino(i.i, text) } // MarshalJSON defines custom encoding scheme func (i Uint) MarshalJSON() ([]byte, error) { - return marshalJSON(&(i.i)) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return marshalJSON(i.i) } // UnmarshalJSON defines custom decoding scheme func (i *Uint) UnmarshalJSON(bz []byte) error { - return unmarshalJSON(&(i.i), bz) + if i.i == nil { // Necessary since default Uint initialization has i.i as nil + i.i = new(big.Int) + } + return unmarshalJSON(i.i, bz) } diff --git a/types/rational.go b/types/rational.go index 5adf78672..1d00e36cc 100644 --- a/types/rational.go +++ b/types/rational.go @@ -18,20 +18,20 @@ import ( // we will panic unmarshalling into the // nil embedded big.Rat type Rat struct { - big.Rat `json:"rat"` + *big.Rat `json:"rat"` } // nolint - common values -func ZeroRat() Rat { return Rat{*big.NewRat(0, 1)} } -func OneRat() Rat { return Rat{*big.NewRat(1, 1)} } +func ZeroRat() Rat { return Rat{big.NewRat(0, 1)} } +func OneRat() Rat { return Rat{big.NewRat(1, 1)} } // New - create a new Rat from integers func NewRat(Numerator int64, Denominator ...int64) Rat { switch len(Denominator) { case 0: - return Rat{*big.NewRat(Numerator, 1)} + return Rat{big.NewRat(Numerator, 1)} case 1: - return Rat{*big.NewRat(Numerator, Denominator[0])} + return Rat{big.NewRat(Numerator, Denominator[0])} default: panic("improper use of New, can only have one denominator") } @@ -84,9 +84,9 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat { switch len(denom) { case 0: - return Rat{*new(big.Rat).SetInt(num)} + return Rat{new(big.Rat).SetInt(num)} case 1: - return Rat{*new(big.Rat).SetFrac(num, denom[0])} + return Rat{new(big.Rat).SetFrac(num, denom[0])} default: panic("improper use of NewRatFromBigInt, can only have one denominator") } @@ -96,26 +96,26 @@ func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat { func NewRatFromInt(num Int, denom ...Int) Rat { switch len(denom) { case 0: - return Rat{*new(big.Rat).SetInt(num.BigInt())} + return Rat{new(big.Rat).SetInt(num.BigInt())} case 1: - return Rat{*new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())} + return Rat{new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())} default: panic("improper use of NewRatFromBigInt, can only have one denominator") } } //nolint -func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator -func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator -func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero -func (r Rat) Equal(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 0 } -func (r Rat) GT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 1 } // greater than -func (r Rat) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than -func (r Rat) Mul(r2 Rat) Rat { return Rat{*new(big.Rat).Mul(&(r.Rat), &(r2.Rat))} } // Mul - multiplication -func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Rat))} } // Quo - quotient -func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition -func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction -func (r Rat) String() string { return r.Rat.String() } +func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator +func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator +func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero +func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 } +func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than +func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than +func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication +func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient +func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition +func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.Rat)} } // Sub - subtraction +func (r Rat) String() string { return r.Rat.String() } func (r Rat) FloatString() string { return r.Rat.FloatString(10) } // a human-friendly string format. The last digit is rounded to nearest, with halves rounded away from zero. var ( @@ -169,8 +169,8 @@ func (r Rat) EvaluateInt() Int { // round Rat with the provided precisionFactor func (r Rat) Round(precisionFactor int64) Rat { - rTen := Rat{*new(big.Rat).Mul(&(r.Rat), big.NewRat(precisionFactor, 1))} - return Rat{*big.NewRat(rTen.Evaluate(), precisionFactor)} + rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))} + return Rat{big.NewRat(rTen.Evaluate(), precisionFactor)} } // TODO panic if negative or if totalDigits < len(initStr)??? @@ -185,7 +185,10 @@ func (r Rat) ToLeftPadded(totalDigits int8) string { //Wraps r.MarshalText(). func (r Rat) MarshalAmino() (string, error) { - bz, err := (&(r.Rat)).MarshalText() + if r.Rat == nil { + r.Rat = new(big.Rat) + } + bz, err := r.Rat.MarshalText() return string(bz), err } @@ -196,7 +199,7 @@ func (r *Rat) UnmarshalAmino(text string) (err error) { if err != nil { return err } - r.Rat = *tempRat + r.Rat = tempRat return nil } diff --git a/types/rational_test.go b/types/rational_test.go index 2483704ed..71b823045 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -180,8 +180,8 @@ func TestRound(t *testing.T) { precFactor int64 }{ {NewRat(333, 777), NewRat(429, 1000), 1000}, - {Rat{*new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, - {Rat{*new(big.Rat).SetFrac(big3, big7)}, Rat{*big.NewRat(4285714286, 10000000000)}, 10000000000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, Rat{big.NewRat(4285714286, 10000000000)}, 10000000000}, {NewRat(1, 2), NewRat(1, 2), 1000}, } @@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) { bz, err := r.MarshalText() require.NoError(t, err) - var r2 Rat + var r2 Rat = Rat{new(big.Rat)} err = r2.UnmarshalText(bz) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) From 6f140d729674b8c8a5bdbf71894ae85eb5c322a7 Mon Sep 17 00:00:00 2001 From: Rigel Date: Tue, 26 Jun 2018 22:00:12 -0400 Subject: [PATCH 18/22] Merge PR #1119: Unbonding, Redelegation * stake/fees spec updates * staking overview.md revisions, moving files * docs reorganization * staking spec state revisions * transaction stake updates * complete staking spec update * WIP adding unbonding/redelegation commands * added msg types for unbonding, redelegation * stake sub-package reorg * working stake reorg * modify lcd tests to not use hardcoded json strings * add description update * index keys * key managment for unbonding redelegation complete * update stake errors * completed handleMsgCompleteUnbonding fn * updated to use begin/complete unbonding/redelegation * fix token shares bug * develop docs into unbonding * got non-tests compiling after merge develop * working fixing tests * PrivlegedKeeper -> PrivilegedKeeper * tests compile * fix some tests * fixing tests * remove PrivilegedKeeper * get unbonding bug * only rpc sig verification failed tests now * move percent unbonding/redelegation to the CLI and out of handler logic * remove min unbonding height * add lcd txs * add pool sanity checks, fix a buncha tests * fix ante. set lcd log to debug (#1322) * redelegation tests, adding query functionality for bonds * add self-delegations at genesis ref #1165 * PR comments (mostly) addressed * cleanup, added Query LCD functionality * test cleanup/fixes * fix governance test * SlashValidatorSet -> ValidatorSet * changelog * stake lcd fix * x/auth: fix chainID in ante * fix lcd test * fix lint, update lint make command for spelling * lowercase error string * don't expose coinkeeper in staking * remove a few duplicate lines in changelog * chain_id in stake lcd tests * added transient redelegation * 'transient' => 'transitive' * Re-add nolint instruction * Fix tiny linter error --- CHANGELOG.md | 24 +- Makefile | 2 +- baseapp/baseapp.go | 1 + client/lcd/lcd_test.go | 107 ++- client/lcd/test_helpers.go | 5 +- cmd/gaia/app/genesis.go | 30 +- cmd/gaia/cmd/gaiacli/main.go | 3 +- docs/spec/staking/end_block.md | 4 +- docs/spec/staking/state.md | 2 +- examples/basecoin/app/app.go | 3 +- examples/basecoin/cmd/basecli/main.go | 6 +- types/rational.go | 2 + types/rational_test.go | 2 +- types/stake.go | 5 +- types/tags.go | 14 +- wire/wire.go | 12 + x/gov/test_common.go | 6 +- x/slashing/app_test.go | 4 +- x/slashing/errors.go | 35 +- x/slashing/keeper_test.go | 6 +- x/slashing/test_common.go | 12 +- x/stake/app_test.go | 24 +- x/stake/client/cli/flags.go | 39 +- x/stake/client/cli/query.go | 209 +++++- x/stake/client/cli/tx.go | 221 ++++++- x/stake/client/rest/query.go | 154 ++++- x/stake/client/rest/tx.go | 175 ++++- x/stake/delegation.go | 52 -- x/stake/errors.go | 117 ---- x/stake/genesis.go | 62 +- x/stake/handler.go | 278 ++++---- x/stake/handler_test.go | 364 ++++++++--- x/stake/keeper.go | 859 ------------------------- x/stake/{store.md => keeper/_store.md} | 0 x/stake/keeper/delegation.go | 356 ++++++++++ x/stake/keeper/delegation_test.go | 223 +++++++ x/stake/{ => keeper}/inflation.go | 23 +- x/stake/{ => keeper}/inflation_test.go | 194 +++--- x/stake/keeper/keeper.go | 122 ++++ x/stake/keeper/keeper_test.go | 39 ++ x/stake/keeper/key.go | 197 ++++++ x/stake/keeper/sdk_types.go | 104 +++ x/stake/keeper/slash.go | 59 ++ x/stake/{ => keeper}/test_common.go | 76 ++- x/stake/keeper/validator.go | 537 ++++++++++++++++ x/stake/keeper/validator_test.go | 674 +++++++++++++++++++ x/stake/keeper_keys.go | 89 --- x/stake/keeper_test.go | 792 ----------------------- x/stake/msg.go | 243 ------- x/stake/msg_test.go | 156 ----- x/stake/pool.go | 133 ---- x/stake/pool_test.go | 131 ---- x/stake/stake.go | 145 +++++ x/stake/tags/tags.go | 23 + x/stake/types/delegation.go | 140 ++++ x/stake/types/errors.go | 115 ++++ x/stake/types/genesis.go | 26 + x/stake/types/msg.go | 387 +++++++++++ x/stake/types/msg_test.go | 226 +++++++ x/stake/{ => types}/params.go | 12 +- x/stake/types/pool.go | 159 +++++ x/stake/types/pool_test.go | 128 ++++ x/stake/{ => types}/shares.go | 20 +- x/stake/types/test_common.go | 201 ++++++ x/stake/{ => types}/validator.go | 72 ++- x/stake/types/validator_test.go | 232 +++++++ x/stake/types/wire.go | 27 + x/stake/validator_test.go | 408 ------------ x/stake/view_slash_keeper.go | 29 - x/stake/view_slash_keeper_test.go | 86 --- x/stake/wire.go | 20 - 71 files changed, 5678 insertions(+), 3765 deletions(-) delete mode 100644 x/stake/delegation.go delete mode 100644 x/stake/errors.go delete mode 100644 x/stake/keeper.go rename x/stake/{store.md => keeper/_store.md} (100%) create mode 100644 x/stake/keeper/delegation.go create mode 100644 x/stake/keeper/delegation_test.go rename x/stake/{ => keeper}/inflation.go (61%) rename x/stake/{ => keeper}/inflation_test.go (68%) create mode 100644 x/stake/keeper/keeper.go create mode 100644 x/stake/keeper/keeper_test.go create mode 100644 x/stake/keeper/key.go create mode 100644 x/stake/keeper/sdk_types.go create mode 100644 x/stake/keeper/slash.go rename x/stake/{ => keeper}/test_common.go (63%) create mode 100644 x/stake/keeper/validator.go create mode 100644 x/stake/keeper/validator_test.go delete mode 100644 x/stake/keeper_keys.go delete mode 100644 x/stake/keeper_test.go delete mode 100644 x/stake/msg.go delete mode 100644 x/stake/msg_test.go delete mode 100644 x/stake/pool.go delete mode 100644 x/stake/pool_test.go create mode 100644 x/stake/stake.go create mode 100644 x/stake/tags/tags.go create mode 100644 x/stake/types/delegation.go create mode 100644 x/stake/types/errors.go create mode 100644 x/stake/types/genesis.go create mode 100644 x/stake/types/msg.go create mode 100644 x/stake/types/msg_test.go rename x/stake/{ => types}/params.go (80%) create mode 100644 x/stake/types/pool.go create mode 100644 x/stake/types/pool_test.go rename x/stake/{ => types}/shares.go (86%) create mode 100644 x/stake/types/test_common.go rename x/stake/{ => types}/validator.go (86%) create mode 100644 x/stake/types/validator_test.go create mode 100644 x/stake/types/wire.go delete mode 100644 x/stake/validator_test.go delete mode 100644 x/stake/view_slash_keeper.go delete mode 100644 x/stake/view_slash_keeper_test.go delete mode 100644 x/stake/wire.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b63fe13b..a60d162ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ BREAKING CHANGES * Removed MsgChangePubKey from auth * Removed setPubKey from account mapper * Removed GetMemo from Tx (it is still on StdTx) +* [cli] rearranged commands under subcommands +* [stake] remove Tick and add EndBlocker +* [stake] introduce concept of unbonding for delegations and validators + * `gaiacli stake unbond` replaced with `gaiacli stake begin-unbonding` + * introduced: + * `gaiacli stake complete-unbonding` + * `gaiacli stake begin-redelegation` + * `gaiacli stake complete-redelegation` FEATURES * [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag @@ -29,7 +37,11 @@ FEATURES * [types] Switches internal representation of Int/Uint/Rat to use pointers * [gaiad] unsafe_reset_all now resets addrbook.json -FIXES +FIXES +* [gaia] Added self delegation for validators in the genesis creation +* [lcd] tests now don't depend on raw json text +* [stake] error strings lower case +* [stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator * \#1259 - fix bug where certain tests that could have a nil pointer in defer * \#1052 - Make all now works * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile @@ -39,6 +51,16 @@ FIXES * \#1353 - CLI: Show pool shares fractions in human-readable format * \#1258 - printing big.rat's can no longer overflow int64 +IMPROVEMENTS +* bank module uses go-wire codec instead of 'encoding/json' +* auth module uses go-wire codec instead of 'encoding/json' +* revised use of endblock and beginblock +* [stake] module reorganized to include `types` and `keeper` package +* [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency) +* [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value) +* [types] added common tag constants +* [stake] offload more generic functionality from the handler into the keeper + ## 0.19.0 *June 13, 2018* diff --git a/Makefile b/Makefile index a4d7e29c3..41883cfaf 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ test_cover: @bash tests/test_cover.sh test_lint: - gometalinter.v2 --disable-all --enable='golint' --vendor ./... + gometalinter.v2 --disable-all --enable='golint' --enable='misspell' --vendor ./... benchmark: @go test -bench=. $(PACKAGES_NOCLITEST) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 3150e84a5..b1c7e5460 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -598,6 +598,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // Write the Deliver state and commit the MultiStore app.deliverState.ms.Write() commitID := app.cms.Commit() + // TODO: this is missing a module identifier and dumps byte array app.Logger.Debug("Commit synced", "commit", commitID, ) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b56c27b95..40bc77b7e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -32,7 +32,7 @@ import ( func TestKeys(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() // get seed @@ -218,7 +218,7 @@ func TestValidators(t *testing.T) { func TestCoinSend(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") @@ -260,7 +260,7 @@ func TestCoinSend(t *testing.T) { func TestIBCTransfer(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() acc := getAccount(t, port, addr) @@ -289,7 +289,7 @@ func TestIBCTransfer(t *testing.T) { func TestTxs(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() // query wrong @@ -378,13 +378,13 @@ func TestValidatorsQuery(t *testing.T) { func TestBonding(t *testing.T) { name, password, denom := "test", "1234567890", "steak" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() validator1Owner := pks[0].Address() // create bond TX - resultTx := doBond(t, port, seed, name, password, addr, validator1Owner) + resultTx := doDelegate(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -405,7 +405,7 @@ func TestBonding(t *testing.T) { // testing unbonding // create unbond TX - resultTx = doUnbond(t, port, seed, name, password, addr, validator1Owner) + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // query validator @@ -416,12 +416,13 @@ func TestBonding(t *testing.T) { assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) - // TODO fix shares fn in staking + // should the sender should have not received any coins as the unbonding has only just begun // query sender - //acc := getAccount(t, sendAddr) - //coins := acc.GetCoins() - //assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + assert.Equal(t, int64(40), coins.AmountOf("steak").Int64()) + // TODO add redelegation, need more complex capabilities such to mock context and } func TestSubmitProposal(t *testing.T) { @@ -572,6 +573,8 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add receiveAddr := receiveInfo.PubKey.Address() receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) + chainID := viper.GetString(client.FlagChainID) + // get the account to get the sequence acc := getAccount(t, port, addr) accnum := acc.GetAccountNumber() @@ -584,13 +587,14 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add "account_number":%d, "sequence": %d, "gas": 100000, + "chain_id": "%s", "amount":[ { "denom": "%s", "amount": 1 } ] - }`, name, password, accnum, sequence, "steak")) + }`, name, password, accnum, sequence, chainID, "steak")) res, body := Request(t, port, "POST", "/ibc/testchain/"+receiveAddrBech+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -606,7 +610,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) // get the account to get the sequence - res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/bonding_status/"+validatorAddrBech, nil) + res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/delegation/"+validatorAddrBech, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var bond stake.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) @@ -614,7 +618,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A return bond } -func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() @@ -623,6 +627,8 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", @@ -630,15 +636,19 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali "account_number": %d, "sequence": %d, "gas": 10000, - "delegate": [ + "chain_id": "%s", + "delegations": [ { "delegator_addr": "%s", "validator_addr": "%s", "bond": { "denom": "%s", "amount": 60 } } ], - "unbond": [] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech, "steak")) + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech, "steak")) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -649,7 +659,9 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali return results[0] } -func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doBeginUnbonding(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() @@ -658,6 +670,8 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", @@ -665,15 +679,64 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va "account_number": %d, "sequence": %d, "gas": 10000, - "delegate": [], - "unbond": [ + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [ { "delegator_addr": "%s", "validator_addr": "%s", "shares": "30" } - ] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech)) + ], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech)) + 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 doBeginRedelegation(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { + + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) + validatorSrcAddrBech := sdk.MustBech32ifyVal(validatorSrcAddr) + validatorDstAddrBech := sdk.MustBech32ifyVal(validatorDstAddr) + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": %d, + "sequence": %d, + "gas": 10000, + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [ + { + "delegator_addr": "%s", + "validator_src_addr": "%s", + "validator_dst_addr": "%s", + "shares": "30" + } + ], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorSrcAddrBech, validatorDstAddrBech)) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 9a22073b7..93a14585f 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -100,13 +100,13 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( config.TxIndex.IndexAllTags = true logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger = log.NewFilter(logger, log.AllowError()) + logger = log.NewFilter(logger, log.AllowDebug()) privValidatorFile := config.PrivValidatorFile() privVal := pvm.LoadOrGenFilePV(privValidatorFile) privVal.Reset() db := dbm.NewMemDB() app := gapp.NewGaiaApp(logger, db) - cdc = gapp.MakeCodec() // XXX + cdc = gapp.MakeCodec() genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) @@ -146,6 +146,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)} acc := gapp.NewGenesisAccount(&accAuth) genesisState.Accounts = append(genesisState.Accounts, acc) + genesisState.StakeData.Pool.LooseTokens += 100 } appState, err := wire.MarshalJSONIndent(cdc, genesisState) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 87eef40ed..c5b40b927 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -3,6 +3,7 @@ package app import ( "encoding/json" "errors" + "github.com/spf13/pflag" crypto "github.com/tendermint/go-crypto" tmtypes "github.com/tendermint/tendermint/types" @@ -17,8 +18,8 @@ import ( var ( // bonded tokens given to genesis validators/accounts - freeFermionVal = sdk.NewInt(100) - freeFermionsAcc = sdk.NewInt(50) + freeFermionVal = int64(100) + freeFermionsAcc = int64(50) ) // State to Unmarshal @@ -124,7 +125,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name st validator = tmtypes.GenesisValidator{ PubKey: pk, - Power: freeFermionVal.Int64(), + Power: freeFermionVal, } return } @@ -155,22 +156,33 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState accAuth := auth.NewBaseAccountWithAddress(genTx.Address) accAuth.Coins = sdk.Coins{ {genTx.Name + "Token", sdk.NewInt(1000)}, - {"steak", freeFermionsAcc}, + {"steak", sdk.NewInt(freeFermionsAcc)}, } acc := NewGenesisAccount(&accAuth) genaccs[i] = acc - stakeData.Pool.LooseUnbondedTokens = stakeData.Pool.LooseUnbondedTokens.Add(freeFermionsAcc) // increase the supply + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply // add the validator if len(genTx.Name) > 0 { desc := stake.NewDescription(genTx.Name, "", "", "") validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc) - validator.PoolShares = stake.NewBondedShares(sdk.NewRatFromInt(freeFermionVal)) + + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply + + // add some new shares to the validator + var issuedDelShares sdk.Rat + validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, freeFermionVal) stakeData.Validators = append(stakeData.Validators, validator) - // pool logic - stakeData.Pool.BondedTokens = stakeData.Pool.BondedTokens.Add(freeFermionVal) - stakeData.Pool.BondedShares = sdk.NewRatFromInt(stakeData.Pool.BondedTokens) + // create the self-delegation from the issuedDelShares + delegation := stake.Delegation{ + DelegatorAddr: validator.Owner, + ValidatorAddr: validator.Owner, + Shares: issuedDelShares, + Height: 0, + } + + stakeData.Bonds = append(stakeData.Bonds, delegation) } } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index ad7fbf985..8c6e971e7 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -95,7 +95,8 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), + stakecmd.GetCmdRedelegate("stake", cdc), slashingcmd.GetCmdUnrevoke(cdc), )...) rootCmd.AddCommand( diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 28e3891d1..61643f526 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -37,8 +37,8 @@ processProvisions(): provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr) - pool.LooseUnbondedTokens += provisions - feePool += LooseUnbondedTokens + pool.LooseTokens += provisions + feePool += LooseTokens setPool(pool) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 2593f4754..f337f4f71 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -12,7 +12,7 @@ information, etc. ```golang type Pool struct { - LooseUnbondedTokens int64 // tokens not associated with any validator + LooseTokens int64 // tokens not associated with any validator UnbondedTokens int64 // reserve of unbonded tokens held with validators UnbondingTokens int64 // tokens moving from bonded to unbonded pool BondedTokens int64 // reserve of bonded tokens diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 06ccd4a0c..dea4f26a2 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -172,7 +172,8 @@ func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, app.accountMapper.IterateAccounts(ctx, appendAccount) genState := types.GenesisState{ - Accounts: accounts, + Accounts: accounts, + StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), } appState, err = wire.MarshalJSONIndent(app.cdc, genState) if err != nil { diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 6540af38a..7aae47d46 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -51,6 +51,10 @@ func main() { // add query/post commands (custom to binary) rootCmd.AddCommand( client.GetCommands( + stakecmd.GetCmdQueryValidator("stake", cdc), + stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryDelegation("stake", cdc), + stakecmd.GetCmdQueryDelegations("stake", cdc), authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)), )...) @@ -62,7 +66,7 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), )...) // add proxy, version and key info diff --git a/types/rational.go b/types/rational.go index 1d00e36cc..a192aa316 100644 --- a/types/rational.go +++ b/types/rational.go @@ -110,7 +110,9 @@ func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - r func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 } func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than +func (r Rat) GTE(r2 Rat) bool { return !r.LT(r2) } // greater than or equal func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than +func (r Rat) LTE(r2 Rat) bool { return !r.GT(r2) } // less than or equal func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition diff --git a/types/rational_test.go b/types/rational_test.go index 71b823045..43c9ddd57 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) { bz, err := r.MarshalText() require.NoError(t, err) - var r2 Rat = Rat{new(big.Rat)} + var r2 = Rat{new(big.Rat)} err = r2.UnmarshalText(bz) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) diff --git a/types/stake.go b/types/stake.go index d49fe387f..d4e3b79ca 100644 --- a/types/stake.go +++ b/types/stake.go @@ -59,8 +59,9 @@ 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 5a8eb1f47..6bd721a5f 100644 --- a/types/tags.go +++ b/types/tags.go @@ -21,8 +21,8 @@ func (t Tags) AppendTag(k string, v []byte) Tags { } // Append two lists of tags -func (t Tags) AppendTags(a Tags) Tags { - return append(t, a...) +func (t Tags) AppendTags(tags Tags) Tags { + return append(t, tags...) } // Turn tags into KVPair list @@ -51,3 +51,13 @@ func NewTags(tags ...interface{}) Tags { func MakeTag(k string, v []byte) Tag { return Tag{Key: []byte(k), Value: v} } + +//__________________________________________________ + +// common tags +var ( + TagAction = "action" + TagSrcValidator = "source-validator" + TagDstValidator = "destination-validator" + TagDelegator = "delegator" +) diff --git a/wire/wire.go b/wire/wire.go index 0ee01939d..d8420e8fa 100644 --- a/wire/wire.go +++ b/wire/wire.go @@ -35,3 +35,15 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) { } return out.Bytes(), nil } + +//__________________________________________________________________ + +// generic sealed codec to be used throughout sdk +var Cdc *Codec + +func init() { + cdc := NewCodec() + RegisterCrypto(cdc) + Cdc = cdc + //Cdc = cdc.Seal() // TODO uncomment once amino upgraded to 0.9.10 +} diff --git a/x/gov/test_common.go b/x/gov/test_common.go index bce8e4b30..d602aad15 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -57,7 +57,11 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) + + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + + stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) InitGenesis(ctx, keeper, DefaultGenesisState()) return abci.ResponseInitChain{} } diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 0e4bb014b..8d98cefe3 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -55,7 +55,9 @@ func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stake.InitGenesis(ctx, keeper, stake.DefaultGenesisState()) + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + stake.InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } } diff --git a/x/slashing/errors.go b/x/slashing/errors.go index b9b152c7b..9f5d9c4eb 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -12,41 +12,16 @@ const ( // Default slashing codespace DefaultCodespace sdk.CodespaceType = 10 - // Invalid validator - CodeInvalidValidator CodeType = 201 - // Validator jailed - CodeValidatorJailed CodeType = 202 + CodeInvalidValidator CodeType = 101 + CodeValidatorJailed CodeType = 102 ) func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") + return sdk.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") + return sdk.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) + return sdk.NewError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked") } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index d0a87e149..fc0d3bc16 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -80,7 +80,7 @@ func TestHandleAbsentValidator(t *testing.T) { validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.BondedTokens) // 51st block missed ctx = ctx.WithBlockHeight(height) @@ -109,7 +109,7 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should have been slashed pool = sk.GetPool(ctx) - require.Equal(t, int64(99), pool.BondedTokens.Int64()) + require.Equal(t, int64(99), pool.BondedTokens) // validator start height should have been changed info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) @@ -167,5 +167,5 @@ func TestHandleNewValidator(t *testing.T) { validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.BondedTokens) } diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 525705dc4..795483efd 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -20,6 +20,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) +// TODO remove dependencies on staking (should only refer to validator set type from sdk) + var ( addrs = []sdk.Address{ testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), @@ -61,7 +63,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep ck := bank.NewKeeper(accountMapper) sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() - genesis.Pool.LooseUnbondedTokens = initCoins.MulRaw(int64(len(addrs))) + genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64() stake.InitGenesis(ctx, sk, genesis) for _, addr := range addrs { ck.AddCoins(ctx, addr, sdk.Coins{ @@ -89,9 +91,9 @@ func testAddr(addr string) sdk.Address { func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { return stake.MsgCreateValidator{ - Description: stake.Description{}, - ValidatorAddr: address, - PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, + Description: stake.Description{}, + ValidatorAddr: address, + PubKey: pubKey, + SelfDelegation: sdk.Coin{"steak", amt}, } } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index c0098fed3..41195f31b 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -22,9 +22,9 @@ var ( addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() priv4 = crypto.GenPrivKeyEd25519() addr4 = priv4.PubKey().Address() - coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} + coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} fee = auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", 0)}, + sdk.Coins{{"foocoin", sdk.NewInt(0)}}, 100000, } ) @@ -60,7 +60,9 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - InitGenesis(ctx, keeper, DefaultGenesisState()) + stakeGenesis := DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } @@ -93,8 +95,8 @@ func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, func TestStakeMsgs(t *testing.T) { mapp, keeper := getMockApp(t) - genCoin := sdk.NewCoin("steak", 42) - bondCoin := sdk.NewCoin("steak", 10) + genCoin := sdk.Coin{"steak", sdk.NewInt(42)} + bondCoin := sdk.Coin{"steak", sdk.NewInt(10)} acc1 := &auth.BaseAccount{ Address: addr1, @@ -148,10 +150,14 @@ func TestStakeMsgs(t *testing.T) { checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) //////////////////// - // Unbond + // Begin Unbonding - unbondMsg := NewMsgUnbond(addr2, addr1, "MAX") - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unbondMsg}, []int64{1}, []int64{1}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10)) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) + + // delegation should exist anymore checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) + + // balance should be the same because bonding not yet complete + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) } diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index eea7e2031..bb8923b58 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -6,11 +6,14 @@ import ( // nolint const ( - FlagAddressDelegator = "address-delegator" - FlagAddressValidator = "address-validator" - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" + FlagAddressDelegator = "address-delegator" + FlagAddressValidator = "address-validator" + FlagAddressValidatorSrc = "addr-validator-source" + FlagAddressValidatorDst = "addr-validator-dest" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagSharesAmount = "shares-amount" + FlagSharesPercent = "shares-percent" FlagMoniker = "moniker" FlagIdentity = "keybase-sig" @@ -20,22 +23,26 @@ const ( // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) - fsShares = flag.NewFlagSet("", flag.ContinueOnError) - fsDescription = flag.NewFlagSet("", flag.ContinueOnError) - fsValidator = flag.NewFlagSet("", flag.ContinueOnError) - fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsPk = flag.NewFlagSet("", flag.ContinueOnError) + fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsDescription = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator. For Ed25519 the go-amino prepend hex is 1624de6220") fsAmount.String(FlagAmount, "1steak", "Amount of coins to bond") - fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") - fsDescription.String(FlagMoniker, "", "validator name") - fsDescription.String(FlagIdentity, "", "optional keybase signature") - fsDescription.String(FlagWebsite, "", "optional website") - fsDescription.String(FlagDetails, "", "optional details") + fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") + fsShares.String(FlagSharesPercent, "", "Percent of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") + fsDescription.String(FlagMoniker, "[do-not-modify]", "validator name") + fsDescription.String(FlagIdentity, "[do-not-modify]", "optional keybase signature") + fsDescription.String(FlagWebsite, "[do-not-modify]", "optional website") + fsDescription.String(FlagDetails, "[do-not-modify]", "optional details") fsValidator.String(FlagAddressValidator, "", "hex address of the validator") fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator") } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 727cddcde..c162717ef 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -9,7 +9,7 @@ import ( "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/wire" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -105,14 +105,14 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query a single delegation bond +// get the command to query a single delegation func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegation", - Short: "Query a delegations bond based on address and validator address", + Short: "Query a delegation based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -122,26 +122,26 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := stake.GetDelegationKey(delAddr, addr, cdc) + key := stake.GetDelegationKey(delAddr, valAddr, cdc) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { return err } - // parse out the bond - bond := new(stake.Delegation) + // parse out the delegation + delegation := new(stake.Delegation) switch viper.Get(cli.OutputFlag) { case "text": - resp, err := bond.HumanReadableString() + resp, err := delegation.HumanReadableString() if err != nil { return err } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, bond) - output, err := wire.MarshalJSONIndent(cdc, bond) + cdc.MustUnmarshalBinary(res, delegation) + output, err := wire.MarshalJSONIndent(cdc, delegation) 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 validators bonded to a delegation +// get the command to query all the delegations made from one delegator func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", @@ -196,3 +196,190 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { } return cmd } + +// get the command to query a single unbonding-delegation record +func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetUBDKey(delAddr, valAddr, cdc) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + ubd := new(stake.UnbondingDelegation) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := ubd.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + cdc.MustUnmarshalBinary(res, ubd) + output, err := wire.MarshalJSONIndent(cdc, ubd) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsValidator) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) + if err != nil { + return err + } + key := stake.GetUBDsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var ubds []stake.UnbondingDelegation + for _, KV := range resKVs { + var ubd stake.UnbondingDelegation + cdc.MustUnmarshalBinary(KV.Value, &ubd) + ubds = append(ubds, ubd) + } + + output, err := wire.MarshalJSONIndent(cdc, ubds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} + +// get the command to query a single unbonding-delegation record +func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + valDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr, cdc) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + red := new(stake.Redelegation) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := red.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + cdc.MustUnmarshalBinary(res, red) + output, err := wire.MarshalJSONIndent(cdc, red) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsRedelegation) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) + if err != nil { + return err + } + key := stake.GetREDsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var reds []stake.Redelegation + for _, KV := range resKVs { + var red stake.Redelegation + cdc.MustUnmarshalBinary(KV.Value, &red) + reds = append(reds, red) + } + + output, err := wire.MarshalJSONIndent(cdc, reds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 7245e420b..7543d2707 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -104,11 +105,11 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit validator command +// delegate command func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", - Short: "delegate coins to an existing validator", + Short: "delegate liquid tokens to an validator", RunE: func(cmd *cobra.Command, args []string) error { amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { @@ -143,33 +144,185 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { } // create edit validator command -func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { +func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "unbond", - Short: "unbond shares from a validator", + Use: "redelegate", + Short: "redelegate illiquid tokens from one validator to another", + } + cmd.AddCommand( + GetCmdBeginRedelegate(storeName, cdc), + GetCmdCompleteRedelegate(cdc), + ) + return cmd +} + +// redelegate command +func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin redelegation", RunE: func(cmd *cobra.Command, args []string) error { - // check the shares before broadcasting - sharesStr := viper.GetString(FlagShares) - var shares sdk.Rat - if sharesStr != "MAX" { - var err error - shares, err = sdk.NewRatFromDecimal(sharesStr) - if err != nil { - return err - } - if !shares.GT(sdk.ZeroRat()) { - return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") - } + var err error + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err } + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorSrcAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr, sharesAmount) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + + cmd.Flags().AddFlagSet(fsShares) + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercentStr string, + delegatorAddr, validatorAddr sdk.Address) (sharesAmount sdk.Rat, err error) { + + switch { + case sharesAmountStr != "" && sharesPercentStr != "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr == "" && sharesPercentStr == "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr != "": + sharesAmount, err = sdk.NewRatFromDecimal(sharesAmountStr) + if err != nil { + return sharesAmount, err + } + if !sharesAmount.GT(sdk.ZeroRat()) { + return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") + } + case sharesPercentStr != "": + var sharesPercent sdk.Rat + sharesPercent, err = sdk.NewRatFromDecimal(sharesPercentStr) + if err != nil { + return sharesAmount, err + } + if !sharesPercent.GT(sdk.ZeroRat()) || !sharesPercent.LTE(sdk.OneRat()) { + return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") + } + + // make a query to get the existing delegation shares + key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resQuery, err := ctx.QueryStore(key, storeName) + if err != nil { + return sharesAmount, err + } + var delegation stake.Delegation + err = cdc.UnmarshalBinary(resQuery, &delegation) + if err != nil { + return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) + } + + sharesAmount = sharesPercent.Mul(delegation.Shares) + } + return +} + +// redelegate command +func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete redelegation", + RunE: func(cmd *cobra.Command, args []string) error { + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +// create edit validator command +func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbond", + Short: "begin or complete unbonding shares from a validator", + } + cmd.AddCommand( + GetCmdBeginUnbonding(storeName, cdc), + GetCmdCompleteUnbonding(cdc), + ) + return cmd +} + +// create edit validator command +func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } - msg := stake.NewMsgUnbond(delegatorAddr, validatorAddr, sharesStr) + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sharesAmount) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -189,3 +342,35 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsValidator) return cmd } + +// create edit validator command +func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsValidator) + return cmd +} diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index ec0e81330..fd213382b 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -13,18 +13,30 @@ import ( ) func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc( - "/stake/{delegator}/bonding_status/{validator}", - bondingStatusHandlerFn(ctx, "stake", cdc), + "/stake/{delegator}/delegation/{validator}", + delegationHandlerFn(ctx, "stake", cdc), ).Methods("GET") + + r.HandleFunc( + "/stake/{delegator}/ubd/{validator}", + ubdHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + + r.HandleFunc( + "/stake/{delegator}/red/{validator_src}/{validator_dst}", + redHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + r.HandleFunc( "/stake/validators", validatorsHandlerFn(ctx, "stake", cdc), ).Methods("GET") } -// http request handler to query delegator bonding status -func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +// http request handler to query a delegation +func delegationHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // read parameters @@ -51,25 +63,147 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire res, err := ctx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query bond. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) return } - // the query will return empty if there is no data for this bond + // the query will return empty if there is no data for this record if len(res) == 0 { w.WriteHeader(http.StatusNoContent) return } - var bond stake.Delegation - err = cdc.UnmarshalBinary(res, &bond) + var delegation stake.Delegation + err = cdc.UnmarshalBinary(res, &delegation) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode bond. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't decode delegation. Error: %s", err.Error()))) return } - output, err := cdc.MarshalJSON(bond) + output, err := cdc.MarshalJSON(delegation) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an unbonding-delegation +func ubdHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validator := vars["validator"] + + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorAddr, err := sdk.GetValAddressBech32(bech32validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetUBDKey(delegatorAddr, validatorAddr, cdc) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + var ubd stake.UnbondingDelegation + err = cdc.UnmarshalBinary(res, &ubd) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't decode unbonding-delegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(ubd) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an redelegation +func redHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validatorSrc := vars["validator_src"] + bech32validatorDst := vars["validator_dst"] + + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorSrcAddr, err := sdk.GetValAddressBech32(bech32validatorSrc) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorDstAddr, err := sdk.GetValAddressBech32(bech32validatorDst) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr, cdc) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + var red stake.Redelegation + err = cdc.UnmarshalBinary(res, &red) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't decode redelegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(red) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 09a27de9f..47860318c 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -24,31 +24,50 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k ).Methods("POST") } -type msgDelegateInput struct { +type msgDelegationsInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 Bond sdk.Coin `json:"bond"` } -type msgUnbondInput struct { +type msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 +} +type msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteUnbondingInput 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"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - Delegate []msgDelegateInput `json:"delegate"` - Unbond []msgUnbondInput `json:"unbond"` +// request body for edit delegations +type EditDelegationsBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` } func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m editDelegationsBody + var m EditDelegationsBody body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -70,24 +89,29 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte } // build messages - messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) + messages := make([]sdk.Msg, len(m.Delegations)+ + len(m.BeginRedelegates)+ + len(m.CompleteRedelegates)+ + len(m.BeginUnbondings)+ + len(m.CompleteUnbondings)) + i := 0 - for _, msg := range m.Delegate { + for _, msg := range m.Delegations { 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()))) + 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()))) + 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")) + w.Write([]byte("Must use own delegator address")) return } messages[i] = stake.MsgDelegate{ @@ -97,28 +121,131 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte } i++ } - for _, msg := range m.Unbond { + + for _, msg := range m.BeginRedelegates { 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()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteRedelegates { + 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 + } + validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr) + 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.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } + i++ + } + + for _, msg := range m.BeginUnbondings { + 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 + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) 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()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteUnbondings { + 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")) + w.Write([]byte("Must use own delegator address")) return } - messages[i] = stake.MsgUnbond{ + messages[i] = stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Shares: msg.Shares, } i++ } diff --git a/x/stake/delegation.go b/x/stake/delegation.go deleted file mode 100644 index 8b047acce..000000000 --- a/x/stake/delegation.go +++ /dev/null @@ -1,52 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Delegation represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -type Delegation struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares sdk.Rat `json:"shares"` - Height int64 `json:"height"` // Last height bond updated -} - -func (b Delegation) equal(b2 Delegation) bool { - return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && - bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) && - b.Height == b2.Height && - b.Shares.Equal(b2.Shares) -} - -// ensure fulfills the sdk validator types -var _ sdk.Delegation = Delegation{} - -// nolint - for sdk.Delegation -func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } -func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr } -func (b Delegation) GetBondShares() sdk.Rat { return b.Shares } - -//Human Friendly pretty printer -func (b Delegation) HumanReadableString() (string, error) { - bechAcc, err := sdk.Bech32ifyAcc(b.DelegatorAddr) - if err != nil { - return "", err - } - bechVal, err := sdk.Bech32ifyAcc(b.ValidatorAddr) - if err != nil { - return "", err - } - resp := "Delegation \n" - resp += fmt.Sprintf("Delegator: %s\n", bechAcc) - resp += fmt.Sprintf("Validator: %s\n", bechVal) - resp += fmt.Sprintf("Shares: %s", b.Shares.String()) - resp += fmt.Sprintf("Height: %d", b.Height) - - return resp, nil -} diff --git a/x/stake/errors.go b/x/stake/errors.go deleted file mode 100644 index 796d638db..000000000 --- a/x/stake/errors.go +++ /dev/null @@ -1,117 +0,0 @@ -// nolint -package stake - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type CodeType = sdk.CodeType - -const ( - DefaultCodespace sdk.CodespaceType = 4 - - // Gaia errors reserve 200 ~ 299. - CodeInvalidValidator CodeType = 201 - CodeInvalidBond CodeType = 202 - CodeInvalidInput CodeType = 203 - CodeValidatorJailed CodeType = 204 - CodeUnauthorized CodeType = sdk.CodeUnauthorized - CodeInternal CodeType = sdk.CodeInternal - CodeUnknownRequest CodeType = sdk.CodeUnknownRequest -) - -// NOTE: Don't stringer this, we'll put better messages in later. -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidValidator: - return "invalid Validator" - case CodeInvalidBond: - return "invalid Bond" - case CodeInvalidInput: - return "invalid Input" - case CodeUnauthorized: - return "unauthorized" - case CodeInternal: - return "internal Error" - case CodeUnknownRequest: - return "unknown request" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -//---------------------------------------- -// Error constructors - -func ErrNotEnoughBondShares(codespace sdk.CodespaceType, shares string) sdk.Error { - return newError(codespace, CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares)) -} -func ErrValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "cannot bond to an empty validator") -} -func ErrBadBondingDenom(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "invalid coin denomination") -} -func ErrBadBondingAmount(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "amount must be > 0") -} -func ErrNoBondingAcct(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "no bond account for this (address, validator) pair") -} -func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "commission must be positive") -} -func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") -} -func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator does not exist for that address") -} -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-create validator") -} -func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") -} -func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "missing signature") -} -func ErrBondNotNominated(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "cannot bond to non-nominated account") -} -func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator does not exist for that address") -} -func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "delegator does not contain validator bond") -} -func ErrInsufficientFunds(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "insufficient bond shares") -} -func ErrBadShares(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "bad shares provided as input, must be MAX or decimal") -} -func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "error removing validator") -} - -//---------------------------------------- - -// TODO group with code from x/bank/errors.go - -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/stake/genesis.go b/x/stake/genesis.go index 43ea61d8b..6a15ebeb4 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -4,63 +4,39 @@ import ( tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// GenesisState - all staking state that must be provided at genesis -type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Validators []Validator `json:"validators"` - Bonds []Delegation `json:"bonds"` -} - -func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { - return GenesisState{ - Pool: pool, - Params: params, - Validators: validators, - Bonds: bonds, - } -} - -// get raw genesis raw message for testing -func DefaultGenesisState() GenesisState { - return GenesisState{ - 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) +func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { + keeper.SetPool(ctx, data.Pool) + keeper.SetNewParams(ctx, data.Params) + keeper.InitIntraTxCounter(ctx) for _, validator := range data.Validators { // set validator - k.setValidator(ctx, validator) + keeper.SetValidator(ctx, validator) // manually set indexes for the first time - k.setValidatorByPubKeyIndex(ctx, validator) - k.setValidatorByPowerIndex(ctx, validator, data.Pool) + keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) if validator.Status() == sdk.Bonded { - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) + keeper.SetValidatorBondedIndex(ctx, validator) } } for _, bond := range data.Bonds { - k.setDelegation(ctx, bond) + keeper.SetDelegation(ctx, bond) } - k.updateBondedValidatorsFull(ctx, store) + keeper.UpdateBondedValidatorsFull(ctx) } // WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - validators := k.getAllValidators(ctx) - bonds := k.getAllDelegations(ctx) - return GenesisState{ +func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + validators := keeper.GetAllValidators(ctx) + bonds := keeper.GetAllDelegations(ctx) + return types.GenesisState{ pool, params, validators, @@ -69,8 +45,8 @@ func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { } // 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) { +func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { + keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetPubKey(), Power: validator.GetPower().Evaluate(), diff --git a/x/stake/handler.go b/x/stake/handler.go index f366989b6..9555d270e 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,24 +1,32 @@ package stake import ( - "bytes" + abci "github.com/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) -func NewHandler(k Keeper) sdk.Handler { +func NewHandler(k keeper.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 MsgCreateValidator: + case types.MsgCreateValidator: return handleMsgCreateValidator(ctx, msg, k) - case MsgEditValidator: + case types.MsgEditValidator: return handleMsgEditValidator(ctx, msg, k) - case MsgDelegate: + case types.MsgDelegate: return handleMsgDelegate(ctx, msg, k) - case MsgUnbond: - return handleMsgUnbond(ctx, msg, k) + case types.MsgBeginRedelegate: + return handleMsgBeginRedelegate(ctx, msg, k) + case types.MsgCompleteRedelegate: + return handleMsgCompleteRedelegate(ctx, msg, k) + case types.MsgBeginUnbonding: + return handleMsgBeginUnbonding(ctx, msg, k) + case types.MsgCompleteUnbonding: + return handleMsgCompleteUnbonding(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -26,25 +34,25 @@ func NewHandler(k Keeper) sdk.Handler { } // Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { +func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) { pool := k.GetPool(ctx) - // Process Validator Provisions - blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + // Process types.Validator Provisions + blockTime := ctx.BlockHeader().Time if pool.InflationLastTime+blockTime >= 3600 { pool.InflationLastTime = blockTime - pool = k.processProvisions(ctx) + pool = k.ProcessProvisions(ctx) } // save the params - k.setPool(ctx, pool) + k.SetPool(ctx, pool) // reset the intra-transaction counter - k.setIntraTxCounter(ctx, 0) + k.SetIntraTxCounter(ctx, 0) // calculate validator set changes - ValidatorUpdates = k.getTendermintUpdates(ctx) - k.clearTendermintUpdates(ctx) + ValidatorUpdates = k.GetTendermintUpdates(ctx) + k.ClearTendermintUpdates(ctx) return } @@ -53,212 +61,150 @@ func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { // These functions assume everything has been authenticated, // now we just perform action and save -func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k Keeper) sdk.Result { +func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { - return ErrValidatorExistsAddr(k.codespace).Result() + return ErrValidatorAlreadyExists(k.Codespace()).Result() } - if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() - } - if ctx.IsCheckTx() { - return sdk.Result{} + if msg.SelfDelegation.Denom != k.GetParams(ctx).BondDenom { + return ErrBadDenom(k.Codespace()).Result() } validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) - k.setValidator(ctx, validator) - k.setValidatorByPubKeyIndex(ctx, validator) - tags := sdk.NewTags( - "action", []byte("createValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), - ) + k.SetValidator(ctx, validator) + k.SetValidatorByPubKeyIndex(ctx, validator) - // move coins from the msg.Address account to a (self-bond) delegator account + // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here - delegateTags, err := delegate(ctx, k, msg.ValidatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.ValidatorAddr, msg.SelfDelegation, validator) if err != nil { return err.Result() } - tags = tags.AppendTags(delegateTags) + + tags := sdk.NewTags( + tags.Action, tags.ActionCreateValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(msg.Description.Moniker), + tags.Identity, []byte(msg.Description.Identity), + ) return sdk.Result{ Tags: tags, } } -func handleMsgEditValidator(ctx sdk.Context, msg MsgEditValidator, k Keeper) sdk.Result { +func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() - } - if ctx.IsCheckTx() { - return sdk.Result{} + return ErrNoValidatorFound(k.Codespace()).Result() } - // XXX move to types // replace all editable fields (clients should autofill existing values) - validator.Description.Moniker = msg.Description.Moniker - validator.Description.Identity = msg.Description.Identity - validator.Description.Website = msg.Description.Website - validator.Description.Details = msg.Description.Details + description, err := validator.Description.UpdateDescription(msg.Description) + if err != nil { + return err.Result() + } + validator.Description = description - k.updateValidator(ctx, validator) + k.UpdateValidator(ctx, validator) tags := sdk.NewTags( - "action", []byte("editValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), + tags.Action, tags.ActionEditValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(description.Moniker), + tags.Identity, []byte(description.Identity), ) return sdk.Result{ Tags: tags, } } -func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { +func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() + return ErrNoValidatorFound(k.Codespace()).Result() } if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() + return ErrBadDenom(k.Codespace()).Result() } if validator.Revoked == true { - return ErrValidatorRevoked(k.codespace).Result() + return ErrValidatorRevoked(k.Codespace()).Result() } - if ctx.IsCheckTx() { - return sdk.Result{} - } - tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Bond, validator) if err != nil { return err.Result() } + + tags := sdk.NewTags( + tags.Action, tags.ActionDelegate, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + ) return sdk.Result{ Tags: tags, } } -// common functionality between handlers -func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, validator Validator) (sdk.Tags, sdk.Error) { - - // Get or create the delegator bond - bond, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) - if !found { - bond = Delegation{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validator.Owner, - Shares: sdk.ZeroRat(), - } - } - - // Account new shares, save - pool := k.GetPool(ctx) - _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) +func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { + err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) if err != nil { - return nil, err + return err.Result() } - validator, pool, newShares := validator.addTokensFromDel(pool, bondAmt.Amount) - bond.Shares = bond.Shares.Add(newShares) - // Update bond height - bond.Height = ctx.BlockHeight() - - k.setPool(ctx, pool) - k.setDelegation(ctx, bond) - k.updateValidator(ctx, validator) - tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "validator", validator.Owner.Bytes()) - return tags, nil + tags := sdk.NewTags( + tags.Action, tags.ActionBeginUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{Tags: tags} } -func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { +func handleMsgCompleteUnbonding(ctx sdk.Context, msg types.MsgCompleteUnbonding, k keeper.Keeper) sdk.Result { - // check if bond has any shares in it unbond - bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.ValidatorAddr) - if !found { - return ErrNoDelegatorForAddress(k.codespace).Result() + err := k.CompleteUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + if err != nil { + return err.Result() } - var delShares sdk.Rat + tags := sdk.NewTags( + tags.Action, ActionCompleteUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) - // test that there are enough shares to unbond - if msg.Shares == "MAX" { - if !bond.Shares.GT(sdk.ZeroRat()) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } else { - var err sdk.Error - delShares, err = sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return err.Result() - } - if bond.Shares.LT(delShares) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } - - // get validator - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) - if !found { - return ErrNoValidatorForAddress(k.codespace).Result() - } - - if ctx.IsCheckTx() { - return sdk.Result{} - } - - // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - if msg.Shares == "MAX" { - delShares = bond.Shares - } - - // subtract bond tokens from delegator bond - bond.Shares = bond.Shares.Sub(delShares) - - // remove the bond - revokeValidator := false - if bond.Shares.IsZero() { - - // if the bond is the owner of the validator then - // trigger a revoke validator - if bytes.Equal(bond.DelegatorAddr, validator.Owner) && - validator.Revoked == false { - revokeValidator = true - } - - k.removeDelegation(ctx, bond) - } else { - // Update bond height - bond.Height = ctx.BlockHeight() - k.setDelegation(ctx, bond) - } - - // Add the coins - pool := k.GetPool(ctx) - validator, pool, returnAmount := validator.removeDelShares(pool, delShares) - k.setPool(ctx, pool) - returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} - k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) - - ///////////////////////////////////// - // revoke validator if necessary - if revokeValidator { - validator.Revoked = true - } - - validator = k.updateValidator(ctx, validator) - - if validator.DelegatorShares.IsZero() { - k.removeValidator(ctx, validator.Owner) - } - - tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes()) - return sdk.Result{ - Tags: tags, - } + return sdk.Result{Tags: tags} +} + +func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { + err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, + msg.ValidatorDstAddr, msg.SharesAmount) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionBeginRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} +} + +func handleMsgCompleteRedelegate(ctx sdk.Context, msg types.MsgCompleteRedelegate, k keeper.Keeper) sdk.Result { + err := k.CompleteRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr) + if err != nil { + return err.Result() + } + + tags := sdk.NewTags( + tags.Action, tags.ActionCompleteRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 56c2adf9d..80225fd51 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,7 +1,6 @@ package stake import ( - "strconv" "testing" "github.com/stretchr/testify/assert" @@ -10,38 +9,48 @@ import ( crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //______________________________________________________________________ -func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator { +func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgCreateValidator { return MsgCreateValidator{ - Description: Description{}, - ValidatorAddr: address, - PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, + Description: Description{}, + ValidatorAddr: address, + PubKey: pubKey, + SelfDelegation: sdk.Coin{"steak", sdk.NewInt(amt)}, } } -func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt sdk.Int) MsgDelegate { +func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Bond: sdk.Coin{"steak", amt}, + Bond: sdk.Coin{"steak", sdk.NewInt(amt)}, } } +// retrieve params which are instant +func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params { + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + return params +} + //______________________________________________________________________ func TestValidatorByPowerIndex(t *testing.T) { - validatorAddr, validatorAddr3 := addrs[0], addrs[1] + validatorAddr, validatorAddr3 := keep.Addrs[0], keep.Addrs[1] - initBond := sdk.NewInt(1000000) - initBondStr := "1000" - ctx, _, keeper := createTestInput(t, false, initBond) + initBond := int64(1000000) + ctx, _, keeper := keep.CreateTestInput(t, false, initBond) + _ = setInstantUnbondPeriod(keeper, ctx) // create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -49,7 +58,7 @@ func TestValidatorByPowerIndex(t *testing.T) { bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) gotBond := bond.Shares.Evaluate() - require.Equal(t, initBond.Int64(), gotBond, + require.Equal(t, initBond, gotBond, "initBond: %v\ngotBond: %v\nbond: %v\n", initBond, gotBond, bond) @@ -57,60 +66,62 @@ func TestValidatorByPowerIndex(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) pool := keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + power := keep.GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // create a second validator keep it bonded - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, pks[2], sdk.NewInt(1000000)) + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // slash and revoke the first validator - keeper.Slash(ctx, pks[0], 0, sdk.NewRat(1, 2)) - keeper.Revoke(ctx, pks[0]) + keeper.Slash(ctx, keep.PKs[0], 0, sdk.NewRat(1, 2)) + keeper.Revoke(ctx, keep.PKs[0]) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded require.Equal(t, int64(500000), validator.PoolShares.Amount.Evaluate()) // ensure is unbonded // the old power record should have been deleted as the power changed - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + assert.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // but the new power record should have been created validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) pool = keeper.GetPool(ctx) - power2 := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power2)) + power2 := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) // inflate a bunch for i := 0; i < 20000; i++ { - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) } // now the new record power index should be the same as the original record - power3 := GetValidatorsByPowerKey(validator, pool) + power3 := GetValidatorsByPowerIndexKey(validator, pool) assert.Equal(t, power2, power3) // unbond self-delegation - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "MAX") - got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\ninitBondStr: %v\n", got, msgUnbond, initBondStr) + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(1000000)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // verify that by power key nolonger exists _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power3)) + assert.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power3)) } func TestDuplicatesMsgCreateValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000)) + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr := addrs[0] - pk := pks[0] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, sdk.NewInt(10)) + validatorAddr := keep.Addrs[0] + pk := keep.PKs[0] + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "%v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) @@ -124,41 +135,41 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, Description{}, validator.Description) // one validator cannot bond twice - msgCreateValidator.PubKey = pks[1] + msgCreateValidator.PubKey = keep.PKs[1] got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.False(t, got.IsOK(), "%v", got) } func TestIncrementsMsgDelegate(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) params := keeper.GetParams(ctx) - bondAmount := sdk.NewInt(10) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + bondAmount := int64(10) + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] // first create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], bondAmount) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.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) require.Equal(t, sdk.Bonded, validator.Status()) - assert.Equal(t, bondAmount, validator.DelegatorShares.EvaluateInt()) - assert.Equal(t, bondAmount, validator.PoolShares.Bonded().EvaluateInt(), "validator: %v", validator) + assert.Equal(t, bondAmount, validator.DelegatorShares.Evaluate()) + assert.Equal(t, bondAmount, validator.PoolShares.Bonded().Evaluate(), "validator: %v", validator) _, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) - assert.Equal(t, bondAmount, bond.Shares.EvaluateInt()) + assert.Equal(t, bondAmount, bond.Shares.Evaluate()) pool := keeper.GetPool(ctx) exRate := validator.DelegatorShareExRate(pool) require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate) - assert.Equal(t, bondAmount, pool.BondedShares.EvaluateInt()) + assert.Equal(t, bondAmount, pool.BondedShares.Evaluate()) assert.Equal(t, bondAmount, pool.BondedTokens) // just send the same msgbond multiple times @@ -180,14 +191,14 @@ func TestIncrementsMsgDelegate(t *testing.T) { exRate := validator.DelegatorShareExRate(pool) require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i) - expBond := sdk.NewInt(int64(i + 1)).Mul(bondAmount) - expDelegatorShares := sdk.NewInt(int64(i + 2)).Mul(bondAmount) // (1 self delegation) - expDelegatorAcc := initBond.Sub(expBond) + expBond := int64(i+1) * bondAmount + expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation) + expDelegatorAcc := sdk.NewInt(initBond - expBond) require.Equal(t, bond.Height, int64(i), "Incorrect bond height") - gotBond := bond.Shares.EvaluateInt() - gotDelegatorShares := validator.DelegatorShares.EvaluateInt() + gotBond := bond.Shares.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -203,14 +214,14 @@ func TestIncrementsMsgDelegate(t *testing.T) { } func TestIncrementsMsgUnbond(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) // create validator, delegate - validatorAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -220,16 +231,19 @@ func TestIncrementsMsgUnbond(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, initBond.MulRaw(2), validator.DelegatorShares.EvaluateInt()) - assert.Equal(t, initBond.MulRaw(2), validator.PoolShares.Bonded().EvaluateInt()) + assert.Equal(t, initBond*2, validator.DelegatorShares.Evaluate()) + assert.Equal(t, initBond*2, validator.PoolShares.Bonded().Evaluate()) // just send the same msgUnbond multiple times // TODO use decimals here - unbondShares, unbondSharesStr := int64(10), "10" - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) + unbondShares := sdk.NewRat(10) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { - got := handleMsgUnbond(ctx, msgUnbond, keeper) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values @@ -238,12 +252,12 @@ func TestIncrementsMsgUnbond(t *testing.T) { bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) - expBond := initBond.SubRaw(int64(i+1) * unbondShares) - expDelegatorShares := initBond.MulRaw(2).SubRaw(int64(i+1) * unbondShares) - expDelegatorAcc := initBond.Sub(expBond) + expBond := initBond - int64(i+1)*unbondShares.Evaluate() + expDelegatorShares := 2*initBond - int64(i+1)*unbondShares.Evaluate() + expDelegatorAcc := sdk.NewInt(initBond - expBond) - gotBond := bond.Shares.EvaluateInt() - gotDelegatorShares := validator.DelegatorShares.EvaluateInt() + gotBond := bond.Shares.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -263,41 +277,42 @@ func TestIncrementsMsgUnbond(t *testing.T) { //1<<63 + 1, // more than int64 1<<63 - 1, 1 << 31, - initBond.Int64(), + initBond, } for _, c := range errorCases { - unbondShares := strconv.Itoa(int(c)) - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares := sdk.NewRat(int64(c)) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail") } - leftBonded := initBond.SubRaw(unbondShares * int64(numUnbonds)) + leftBonded := initBond - int64(numUnbonds)*unbondShares.Evaluate() // should be unable to unbond one more than we have - unbondSharesStr = strconv.Itoa(int(leftBonded.Int64()) + 1) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares = sdk.NewRat(leftBonded + 1) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) assert.False(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares.String(), leftBonded) // should be able to unbond just what we have - unbondSharesStr = strconv.Itoa(int(leftBonded.Int64())) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares = sdk.NewRat(leftBonded) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded) } func TestMultipleMsgCreateValidator(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) - validatorAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) + + validatorAddrs := []sdk.Address{keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]} // bond them all for i, validatorAddr := range validatorAddrs { - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[i], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[i], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -305,7 +320,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) { validators := keeper.GetValidators(ctx, 100) require.Equal(t, (i + 1), len(validators)) val := validators[i] - balanceExpd := initBond.SubRaw(10) + balanceExpd := sdk.NewInt(initBond - 10) balanceGot := accMapper.GetAccount(ctx, val.Owner).GetCoins().AmountOf(params.BondDenom) require.Equal(t, i+1, len(validators), "expected %d validators got %d, validators: %v", i+1, len(validators), validators) require.Equal(t, 10, int(val.DelegatorShares.Evaluate()), "expected %d shares, got %d", 10, val.DelegatorShares) @@ -316,8 +331,11 @@ func TestMultipleMsgCreateValidator(t *testing.T) { for i, validatorAddr := range validatorAddrs { validatorPre, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "10") // self-delegation - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) // self-delegation + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -328,24 +346,25 @@ func TestMultipleMsgCreateValidator(t *testing.T) { _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - expBalance := initBond + expBalance := sdk.NewInt(initBond) gotBalance := accMapper.GetAccount(ctx, validatorPre.Owner).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance) } } func TestMultipleMsgDelegate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000)) - validatorAddr, delegatorAddrs := addrs[0], addrs[1:] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddrs := keep.Addrs[0], keep.Addrs[1:] + _ = setInstantUnbondPeriod(keeper, ctx) //first make a validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // delegate multiple parties for i, delegatorAddr := range delegatorAddrs { - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewInt(10)) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -357,8 +376,11 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, "10") - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -368,37 +390,173 @@ func TestMultipleMsgDelegate(t *testing.T) { } func TestRevokeValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000)) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] + _ = setInstantUnbondPeriod(keeper, ctx) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.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, sdk.NewInt(10)) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) + validator, _ := keeper.GetValidator(ctx, validatorAddr) + // unbond the validators bond portion - msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + msgBeginUnbondingValidator := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingValidator := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.Revoked) + require.True(t, validator.Revoked, "%v", validator) // test that this address cannot yet be bonded too because is revoked got = handleMsgDelegate(ctx, msgDelegate, keeper) assert.False(t, got.IsOK(), "expected error, got %v", got) // 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 runMsgCreateValidator") + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingDelegator := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") // verify that the pubkey can now be reused got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected ok, got %v", got) } + +func TestUnbondingPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := keep.Addrs[0] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin unbonding + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") + + // cannot complete unbonding at same time + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // cannot complete unbonding at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // can complete unbonding at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestRedelegationPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2 := keep.Addrs[0], keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot complete redelegation at same time + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // cannot complete redelegation at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // can complete redelegation at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestTransitiveRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegation to next validator while first delegation exists + msgBeginRedelegate = NewMsgBeginRedelegate(validatorAddr, validatorAddr2, validatorAddr3, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // complete first redelegation + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") + + // now should be able to redelegate from the second validator to the third + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} diff --git a/x/stake/keeper.go b/x/stake/keeper.go deleted file mode 100644 index dd7a44663..000000000 --- a/x/stake/keeper.go +++ /dev/null @@ -1,859 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "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 -type Keeper struct { - storeKey sdk.StoreKey - cdc *wire.Codec - coinKeeper bank.Keeper - - // codespace - codespace sdk.CodespaceType -} - -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { - keeper := Keeper{ - storeKey: key, - cdc: cdc, - coinKeeper: ck, - codespace: codespace, - } - return keeper -} - -//_________________________________________________________________________ - -// get a single validator -func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Validator, found bool) { - store := ctx.KVStore(k.storeKey) - 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)) - if b == nil { - return validator, false - } - k.cdc.MustUnmarshalBinary(b, &validator) - return validator, true -} - -// 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) -} - -// used in testing -func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(power) != nil -} - -// 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) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators = append(validators, validator) - iterator.Next() - } - return validators -} - -// Get the set of all validators, retrieve a maxRetrieve number of records -func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators Validators) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - validators = make([]Validator, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators[i] = validator - iterator.Next() - } - return validators[:i] // trim -} - -//___________________________________________________________________________ - -// get the group of the bonded validators -func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []Validator) { - store := ctx.KVStore(k.storeKey) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - validators = make([]Validator, maxValidators) - - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := 0 - for ; iterator.Valid(); iterator.Next() { - - // sanity check - if i > int(maxValidators-1) { - panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - validators[i] = validator - i++ - } - iterator.Close() - return validators[:i] // trim -} - -// get the group of bonded validators sorted by power-rank -func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []Validator { - store := ctx.KVStore(k.storeKey) - maxValidators := k.GetParams(ctx).MaxValidators - validators := make([]Validator, maxValidators) - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - i := 0 - for { - if !iterator.Valid() || i > int(maxValidators-1) { - iterator.Close() - break - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - // Reached to revoked validators, stop iterating - if validator.Revoked { - iterator.Close() - break - } - if validator.Status() == sdk.Bonded { - validators[i] = validator - i++ - } - iterator.Next() - } - return validators[:i] // trim -} - -//_________________________________________________________________________ -// Accumulated updates to the active/bonded validator set for tendermint - -// get the most recently updated validators -func (k Keeper) getTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { - store := ctx.KVStore(k.storeKey) - - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest - for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val abci.Validator - k.cdc.MustUnmarshalBinary(valBytes, &val) - updates = append(updates, val) - } - iterator.Close() - return -} - -// remove all validator update entries after applied to Tendermint -func (k Keeper) clearTendermintUpdates(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - -//___________________________________________________________________________ - -// perfom all the nessisary steps for when a validator changes its power -// updates all validator stores as well as tendermint update store -// may kick out validators if new validator is entering the bonded validator group -func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator { - store := ctx.KVStore(k.storeKey) - pool := k.getPool(store) - ownerAddr := validator.Owner - - // always update the main list ordered by owner address before exiting - defer func() { - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(ownerAddr), bz) - }() - - // retrieve the old validator record - oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) - - if validator.Revoked && oldValidator.Status() == sdk.Bonded { - 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 - if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { - powerIncreasing = true - } - - // if already a validator, copy the old block height and counter, else set them - if oldFound && oldValidator.Status() == sdk.Bonded { - validator.BondHeight = oldValidator.BondHeight - validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter - } else { - validator.BondHeight = ctx.BlockHeight() - counter := k.getIntraTxCounter(ctx) - validator.BondIntraTxCounter = counter - k.setIntraTxCounter(ctx, counter+1) - } - - // update the list ordered by voting power - if oldFound { - store.Delete(GetValidatorsByPowerKey(oldValidator, pool)) - } - valPower := GetValidatorsByPowerKey(validator, pool) - store.Set(valPower, validator.Owner) - - // efficiency case: - // if already bonded and power increasing only need to update tendermint - if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { - bz := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(ownerAddr), bz) - return validator - } - - // efficiency case: - // if was unbonded/or is a new validator - and the new power is less than the cliff validator - cliffPower := k.getCliffValidatorPower(ctx) - if cliffPower != nil && - (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && - bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower - return validator - } - - // update the validator set for this validator - updatedVal := k.updateBondedValidators(ctx, store, validator) - if updatedVal.Owner != nil { // updates to validator occurred to be updated - validator = updatedVal - } - return validator -} - -// Update the validator group and kick out any old validators. In addition this -// function adds (or doesn't add) a validator which has updated its bonded -// tokens to the validator group. -> this validator is specified through the -// updatedValidatorAddr term. -// -// The correct subset is retrieved by iterating through an index of the -// validators sorted by power, stored using the ValidatorsByPowerKey. -// Simultaneously the current validator records are updated in store with the -// ValidatorsBondedKey. This store is used to determine if a validator is a -// validator without needing to iterate over the subspace as we do in -// GetValidators. -// -// Optionally also return the validator from a retrieve address if the validator has been bonded -func (k Keeper) updateBondedValidators(ctx sdk.Context, store sdk.KVStore, - newValidator Validator) (updatedVal Validator) { - - kickCliffValidator := false - oldCliffValidatorAddr := k.getCliffValidator(ctx) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - // TODO benchmark if we should read the current power and not write if it's the same - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, or under the - // situation that this is the "new validator" just use the validator - // provided because it has not yet been updated in the main validator - // store - ownerAddr := iterator.Value() - if bytes.Equal(ownerAddr, newValidator.Owner) { - validator = newValidator - } else { - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - } - - // if not previously a validator (and unrevoked), - // kick the cliff validator / bond this new validator - if validator.Status() != sdk.Bonded && !validator.Revoked { - kickCliffValidator = true - - validator = k.bondValidator(ctx, store, validator) - if bytes.Equal(ownerAddr, newValidator.Owner) { - updatedVal = validator - } - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - if oldCliffValidatorAddr != nil && kickCliffValidator { - validator, found := k.getValidator(store, oldCliffValidatorAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) - } - k.unbondValidator(ctx, store, validator) - } - - return -} - -// full update of the bonded validator set, many can be added/kicked -func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) { - // clear the current validators store, add to the ToKickOut temp store - toKickOut := make(map[string]byte) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - for ; iterator.Valid(); iterator.Next() { - ownerAddr := iterator.Value() - toKickOut[string(ownerAddr)] = 0 // set anything - } - iterator.Close() - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, - // or under the situation that this is the "new validator" just - // use the validator provided because it has not yet been updated - // in the main validator store - ownerAddr := iterator.Value() - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - - _, found = toKickOut[string(ownerAddr)] - if found { - delete(toKickOut, string(ownerAddr)) - } else { - - // if it wasn't in the toKickOut group it means - // this wasn't a previously a validator, therefor - // update the validator to enter the validator group - validator = k.bondValidator(ctx, store, validator) - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - for key := range toKickOut { - ownerAddr := []byte(key) - validator, found := k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - k.unbondValidator(ctx, store, validator) - } - return -} - -// perform all the store operations for when a validator status becomes unbonded -func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Unbonded { - panic(fmt.Sprintf("should not already be be unbonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - k.setPool(ctx, pool) - - // save the now unbonded validator record - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - // 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 -func (k Keeper) bondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("should not already be be bonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Bonded) - k.setPool(ctx, pool) - - // save the now bonded validator record to the three referenced stores - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - return validator -} - -func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { - - // first retrieve the old validator record - validator, found := k.GetValidator(ctx, address) - if !found { - return - } - - // delete the old validator record - 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 - // is bonded - and add validator with zero power to the validator updates - if store.Get(GetValidatorsBondedKey(validator.PubKey)) == nil { - return - } - store.Delete(GetValidatorsBondedKey(validator.PubKey)) - - bz := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(address), bz) -} - -//_____________________________________________________________________ - -// load a delegator bond -func (k Keeper) GetDelegation(ctx sdk.Context, - delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) { - - store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) - if delegatorBytes == nil { - return bond, false - } - - k.cdc.MustUnmarshalBinary(delegatorBytes, &bond) - return bond, true -} - -// load all delegations used during genesis dump -func (k Keeper) getAllDelegations(ctx sdk.Context) (delegations []Delegation) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegationKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bondBytes := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &delegation) - delegations = append(delegations, delegation) - iterator.Next() - } - return delegations[:i] // trim -} - -// load all bonds of a delegator -func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest - - bonds = make([]Delegation, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bondBytes := iterator.Value() - var bond Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &bond) - bonds[i] = bond - iterator.Next() - } - return bonds[:i] // trim -} - -func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(bond) - store.Set(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc), b) -} - -func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc)) -} - -//_______________________________________________________________________ - -// load/save the global staking params -func (k Keeper) GetParams(ctx sdk.Context) Params { - store := ctx.KVStore(k.storeKey) - return k.getParams(store) -} -func (k Keeper) getParams(store sdk.KVStore) (params Params) { - b := store.Get(ParamKey) - if b == nil { - panic("Stored params should not have been nil") - } - - k.cdc.MustUnmarshalBinary(b, ¶ms) - return -} - -// Need a distinct function because setParams depends on an existing previous -// record of params to exist (to check if maxValidators has changed) - and we -// panic on retrieval if it doesn't exist - hence if we use setParams for the very -// first params set it will panic. -func (k Keeper) setNewParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -// Public version of setNewParams -func (k Keeper) SetNewParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -func (k Keeper) setParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - exParams := k.getParams(store) - - // if max validator count changes, must recalculate validator set - if exParams.MaxValidators != params.MaxValidators { - k.updateBondedValidatorsFull(ctx, store) - } - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -//_______________________________________________________________________ - -// load/save the pool -func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { - store := ctx.KVStore(k.storeKey) - return k.getPool(store) -} -func (k Keeper) getPool(store sdk.KVStore) (pool Pool) { - b := store.Get(PoolKey) - if b == nil { - panic("Stored pool should not have been nil") - } - k.cdc.MustUnmarshalBinary(b, &pool) - return -} - -func (k Keeper) setPool(ctx sdk.Context, pool Pool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) - store.Set(PoolKey, b) -} - -// Public version of setpool -func (k Keeper) SetPool(ctx sdk.Context, pool Pool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) - store.Set(PoolKey, b) -} - -//__________________________________________________________________________ - -// get the current in-block validator operation counter -func (k Keeper) getIntraTxCounter(ctx sdk.Context) int16 { - store := ctx.KVStore(k.storeKey) - b := store.Get(IntraTxCounterKey) - if b == nil { - return 0 - } - var counter int16 - k.cdc.MustUnmarshalBinary(b, &counter) - return counter -} - -// set the current in-block validator operation counter -func (k Keeper) setIntraTxCounter(ctx sdk.Context, counter int16) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(counter) - store.Set(IntraTxCounterKey, bz) -} - -//__________________________________________________________________________ - -// get the current validator on the cliff -func (k Keeper) getCliffValidator(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorCliffKey) -} - -// get the current power of the validator on the cliff -func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorPowerCliffKey) -} - -// set the current validator and power of the validator on the cliff -func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Pool) { - store := ctx.KVStore(k.storeKey) - bz := GetValidatorsByPowerKey(validator, pool) - store.Set(ValidatorPowerCliffKey, bz) - 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 - -var _ sdk.ValidatorSet = Keeper{} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// get the sdk.validator for a particular address -func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { - val, found := k.GetValidator(ctx, addr) - if !found { - return nil - } - return val -} - -// total power from the bond -func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { - pool := k.GetPool(ctx) - return pool.BondedShares -} - -//__________________________________________________________________________ - -// Implements DelegationSet - -var _ sdk.ValidatorSet = Keeper{} - -// get the delegation for a particular set of delegator and validator addresses -func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { - bond, ok := k.GetDelegation(ctx, addrDel, addrVal) - if !ok { - return nil - } - return bond -} - -// Returns self as it is both a validatorset and delegationset -func (k Keeper) GetValidatorSet() sdk.ValidatorSet { - return k -} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { - store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, key) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bz, &delegation) - stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? - if stop { - break - } - i++ - } - 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 %v 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/store.md b/x/stake/keeper/_store.md similarity index 100% rename from x/stake/store.md rename to x/stake/keeper/_store.md diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go new file mode 100644 index 000000000..e9155d7f9 --- /dev/null +++ b/x/stake/keeper/delegation.go @@ -0,0 +1,356 @@ +package keeper + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// load a delegation +func (k Keeper) GetDelegation(ctx sdk.Context, + delegatorAddr, validatorAddr sdk.Address) (delegation types.Delegation, found bool) { + + store := ctx.KVStore(k.storeKey) + delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) + if delegatorBytes == nil { + return delegation, false + } + + k.cdc.MustUnmarshalBinary(delegatorBytes, &delegation) + return delegation, true +} + +// load all delegations used during genesis dump +func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + bondBytes := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegations = append(delegations, delegation) + iterator.Next() + } + iterator.Close() + return delegations +} + +// load all delegations for a delegator +func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, + maxRetrieve int16) (delegations []types.Delegation) { + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + + delegations = make([]types.Delegation, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + bondBytes := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegations[i] = delegation + iterator.Next() + } + iterator.Close() + return delegations[:i] // trim +} + +// set the delegation +func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(delegation) + store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc), b) +} + +// remove the delegation +func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// load a unbonding delegation +func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, + DelegatorAddr, ValidatorAddr sdk.Address) (ubd types.UnbondingDelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr, k.cdc) + bz := store.Get(ubdKey) + if bz == nil { + return ubd, false + } + + k.cdc.MustUnmarshalBinary(bz, &ubd) + return ubd, true +} + +// set the unbonding delegation and associated index +func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(ubd) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + store.Set(ubdKey, bz) + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc), ubdKey) +} + +// remove the unbonding delegation object and associated index +func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + store.Delete(ubdKey) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// load a redelegation +func (k Keeper) GetRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.Address) (red types.Redelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr, k.cdc) + bz := store.Get(redKey) + if bz == nil { + return red, false + } + + k.cdc.MustUnmarshalBinary(bz, &red) + return red, true +} + +// has a redelegation +func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorDstAddr sdk.Address) bool { + + store := ctx.KVStore(k.storeKey) + prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest + + found := false + if iterator.Valid() { + //record found + found = true + } + iterator.Close() + return found +} + +// set a redelegation and associated index +func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(red) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + store.Set(redKey, bz) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) +} + +// remove a redelegation object and associated index +func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + store.Delete(redKey) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// Perform a delegation, set/update everything necessary within the store +func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.Address, bondAmt sdk.Coin, + validator types.Validator) (newShares sdk.Rat, err sdk.Error) { + + // Get or create the delegator delegation + delegation, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) + if !found { + delegation = types.Delegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validator.Owner, + Shares: sdk.ZeroRat(), + } + } + + // Account new shares, save + pool := k.GetPool(ctx) + _, _, err = k.coinKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) + if err != nil { + return + } + validator, pool, newShares = validator.AddTokensFromDel(pool, bondAmt.Amount.Int64()) + delegation.Shares = delegation.Shares.Add(newShares) + + // Update delegation height + delegation.Height = ctx.BlockHeight() + + k.SetPool(ctx, pool) + k.SetDelegation(ctx, delegation) + k.UpdateValidator(ctx, validator) + + return +} + +// unbond the the delegation return +func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address, + shares sdk.Rat) (amount int64, err sdk.Error) { + + // check if delegation has any shares in it unbond + delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + err = types.ErrNoDelegatorForAddress(k.Codespace()) + return + } + + // retrieve the amount to remove + if delegation.Shares.LT(shares) { + err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) + return + } + + // get validator + validator, found := k.GetValidator(ctx, validatorAddr) + if !found { + err = types.ErrNoValidatorFound(k.Codespace()) + return + } + + // subtract shares from delegator + delegation.Shares = delegation.Shares.Sub(shares) + + // remove the delegation + if delegation.Shares.IsZero() { + + // if the delegation is the owner of the validator then + // trigger a revoke validator + if bytes.Equal(delegation.DelegatorAddr, validator.Owner) && validator.Revoked == false { + validator.Revoked = true + } + k.RemoveDelegation(ctx, delegation) + } else { + // Update height + delegation.Height = ctx.BlockHeight() + k.SetDelegation(ctx, delegation) + } + + // remove the coins from the validator + pool := k.GetPool(ctx) + validator, pool, amount = validator.RemoveDelShares(pool, shares) + + k.SetPool(ctx, pool) + + // update then remove validator if necessary + validator = k.UpdateValidator(ctx, validator) + if validator.DelegatorShares.IsZero() { + k.RemoveValidator(ctx, validator.Owner) + } + + return amount, nil +} + +//______________________________________________________________________________________________________ + +// complete unbonding an unbonding record +func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error { + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorAddr, sharesAmount) + if err != nil { + return err + } + + // create the unbonding delegation + params := k.GetParams(ctx) + minTime := ctx.BlockHeader().Time + params.UnbondingTime + + ubd := types.UnbondingDelegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + MinTime: minTime, + Balance: sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}, + } + k.SetUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address) sdk.Error { + + ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + return types.ErrNoUnbondingDelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if ubd.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "unbonding", "unit-time", ubd.MinTime, ctxTime) + } + + k.coinKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance}) + k.RemoveUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error { + + // check if this is a transitive redelegation + if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) { + return types.ErrTransitiveRedelegation(k.Codespace()) + } + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount) + if err != nil { + return err + } + + params := k.GetParams(ctx) + returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)} + dstValidator, found := k.GetValidator(ctx, validatorDstAddr) + if !found { + return types.ErrBadRedelegationDst(k.Codespace()) + } + sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator) + + // create the unbonding delegation + minTime := ctx.BlockHeader().Time + params.UnbondingTime + + red := types.Redelegation{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + MinTime: minTime, + SharesDst: sharesCreated, + SharesSrc: sharesAmount, + } + k.SetRedelegation(ctx, red) + return nil +} + +// complete unbonding an ongoing redelegation +func (k Keeper) CompleteRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) sdk.Error { + + red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) + if !found { + return types.ErrNoRedelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if red.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime) + } + + k.RemoveRedelegation(ctx, red) + return nil +} diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go new file mode 100644 index 000000000..d7c1ac984 --- /dev/null +++ b/x/stake/keeper/delegation_test.go @@ -0,0 +1,223 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// tests GetDelegation, GetDelegations, SetDelegation, RemoveDelegation, GetDelegations +func TestDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + //construct the validators + amts := []int64{9, 8, 7} + var validators [3]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + } + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + // first add a validators[0] to delegate too + bond1to1 := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: sdk.NewRat(9), + } + + // check the empty keeper first + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.False(t, found) + + // set and retrieve a record + keeper.SetDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bond1to1.Equal(resBond)) + + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.SetDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bond1to1.Equal(resBond)) + + // add some more records + bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} + keeper.SetDelegation(ctx, bond1to2) + keeper.SetDelegation(ctx, bond1to3) + keeper.SetDelegation(ctx, bond2to1) + keeper.SetDelegation(ctx, bond2to2) + keeper.SetDelegation(ctx, bond2to3) + + // test all bond retrieve capabilities + resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bond1to1.Equal(resBonds[0])) + assert.True(t, bond1to2.Equal(resBonds[1])) + assert.True(t, bond1to3.Equal(resBonds[2])) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bond2to1.Equal(resBonds[0])) + assert.True(t, bond2to2.Equal(resBonds[1])) + assert.True(t, bond2to3.Equal(resBonds[2])) + allBonds := keeper.GetAllDelegations(ctx) + require.Equal(t, 6, len(allBonds)) + assert.True(t, bond1to1.Equal(allBonds[0])) + assert.True(t, bond1to2.Equal(allBonds[1])) + assert.True(t, bond1to3.Equal(allBonds[2])) + assert.True(t, bond2to1.Equal(allBonds[3])) + assert.True(t, bond2to2.Equal(allBonds[4])) + assert.True(t, bond2to3.Equal(allBonds[5])) + + // delete a record + keeper.RemoveDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) + assert.False(t, found) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 2, len(resBonds)) + assert.True(t, bond2to1.Equal(resBonds[0])) + assert.True(t, bond2to2.Equal(resBonds[1])) + + // delete all the records from delegator 2 + keeper.RemoveDelegation(ctx, bond2to1) + keeper.RemoveDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) + assert.False(t, found) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) + assert.False(t, found) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 0, len(resBonds)) +} + +// tests Get/Set/Remove UnbondingDelegation +func TestUnbondingDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + MinTime: 0, + Balance: sdk.NewCoin("steak", 5), + } + + // set and retrieve a record + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, ubd.Equal(resBond)) + + // modify a records, save, and retrieve + ubd.Balance = sdk.NewCoin("steak", 21) + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, ubd.Equal(resBond)) + + // delete a record + keeper.RemoveUnbondingDelegation(ctx, ubd) + _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.False(t, found) +} + +func TestUnbondDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = 10 + + //create a validator and a delegator to that validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, 10) + require.Equal(t, int64(10), issuedShares.Evaluate()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + pool = keeper.GetPool(ctx) + require.Equal(t, int64(10), pool.BondedTokens) + require.Equal(t, int64(10), validator.PoolShares.Bonded().Evaluate()) + + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + var err error + var amount int64 + amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6)) + require.NoError(t, err) + assert.Equal(t, int64(6), amount) // shares to be added to an unbonding delegation / redelegation + + delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + pool = keeper.GetPool(ctx) + + assert.Equal(t, int64(4), delegation.Shares.Evaluate()) + assert.Equal(t, int64(4), validator.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(6), pool.LooseTokens, "%v", pool) + assert.Equal(t, int64(4), pool.BondedTokens) +} + +// tests Get/Set/Remove/Has UnbondingDelegation +func TestRedelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // test shouldn't have and redelegations + has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + assert.False(t, has) + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.True(t, found) + assert.True(t, rd.Equal(resBond)) + + // check if has the redelegation + has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + assert.True(t, has) + + // modify a records, save, and retrieve + rd.SharesSrc = sdk.NewRat(21) + rd.SharesDst = sdk.NewRat(21) + keeper.SetRedelegation(ctx, rd) + resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.True(t, found) + assert.True(t, rd.Equal(resBond)) + + // delete a record + keeper.RemoveRedelegation(ctx, rd) + _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.False(t, found) +} diff --git a/x/stake/inflation.go b/x/stake/keeper/inflation.go similarity index 61% rename from x/stake/inflation.go rename to x/stake/keeper/inflation.go index b123dd2dd..0574b8ecb 100644 --- a/x/stake/inflation.go +++ b/x/stake/keeper/inflation.go @@ -1,35 +1,32 @@ -package stake +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) const ( hrsPerYr = 8766 // as defined by a julian year of 365.25 days - precision = 100000000000 // increased to this precision for accuracy with tests on tick_test.go + precision = 100000000000 // increased to this precision for accuracy ) -var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days +var hrsPerYrRat = sdk.NewRat(hrsPerYr) // process provisions for an hour period -func (k Keeper) processProvisions(ctx sdk.Context) Pool { +func (k Keeper) ProcessProvisions(ctx sdk.Context) types.Pool { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx) + pool.Inflation = k.NextInflation(ctx) - // Because the validators hold a relative bonded share (`GlobalStakeShare`), when - // more bonded tokens are added proportionally to all validators the only term - // which needs to be updated is the `BondedPool`. So for each previsions cycle: - - provisions := pool.Inflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat).EvaluateInt() + provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate() // TODO add to the fees provisions - pool.LooseUnbondedTokens = pool.LooseUnbondedTokens.Add(provisions) + pool.LooseTokens += provisions return pool } // get the next inflation rate for the hour -func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { +func (k Keeper) NextInflation(ctx sdk.Context) (inflation sdk.Rat) { params := k.GetParams(ctx) pool := k.GetPool(ctx) @@ -40,7 +37,7 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat().Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat().Sub(pool.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat) // increase the new annual inflation for this next cycle diff --git a/x/stake/inflation_test.go b/x/stake/keeper/inflation_test.go similarity index 68% rename from x/stake/inflation_test.go rename to x/stake/keeper/inflation_test.go index 8a117fc92..cb2f6007a 100644 --- a/x/stake/inflation_test.go +++ b/x/stake/keeper/inflation_test.go @@ -1,65 +1,64 @@ -package stake +package keeper import ( "math/rand" "strconv" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //changing the int in NewSource will allow you to test different, deterministic, sets of operations var r = rand.New(rand.NewSource(6595)) func TestGetInflation(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) hrsPerYrRat := sdk.NewRat(hrsPerYr) // Governing Mechanism: - // bondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange - - zero := sdk.ZeroInt() - one := sdk.OneInt() + // BondedRatio = BondedTokens / TotalSupply + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange tests := []struct { name string - setBondedTokens, setLooseTokens sdk.Int + setBondedTokens, setLooseTokens int64 setInflation, expectedChange sdk.Rat }{ // with 0% bonded atom supply the inflation should increase by InflationRateChange - {"test 1", zero, zero, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, + {"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, // 100% bonded, starting at 20% inflation and being reduced // (1 - (1/0.67))*(0.13/8667) - {"test 2", one, zero, sdk.NewRat(20, 100), + {"test 2", 1, 0, sdk.NewRat(20, 100), sdk.OneRat().Sub(sdk.OneRat().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, // 50% bonded, starting at 10% inflation and being increased - {"test 3", one, one, sdk.NewRat(10, 100), + {"test 3", 1, 1, sdk.NewRat(10, 100), sdk.OneRat().Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, // test 7% minimum stop (testing with 100% bonded) - {"test 4", one, zero, sdk.NewRat(7, 100), sdk.ZeroRat()}, - {"test 5", one, zero, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, + {"test 4", 1, 0, sdk.NewRat(7, 100), sdk.ZeroRat()}, + {"test 5", 1, 0, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, // test 20% maximum stop (testing with 0% bonded) - {"test 6", zero, zero, sdk.NewRat(20, 100), sdk.ZeroRat()}, - {"test 7", zero, zero, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, + {"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat()}, + {"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, // perfect balance shouldn't change inflation - {"test 8", sdk.NewInt(67), sdk.NewInt(33), sdk.NewRat(15, 100), sdk.ZeroRat()}, + {"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()}, } for _, tc := range tests { - pool.BondedTokens, pool.LooseUnbondedTokens = tc.setBondedTokens, tc.setLooseTokens + pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens pool.Inflation = tc.setInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) - inflation := keeper.nextInflation(ctx) + inflation := keeper.NextInflation(ctx) diffInflation := inflation.Sub(tc.setInflation) assert.True(t, diffInflation.Equal(tc.expectedChange), @@ -69,17 +68,18 @@ func TestGetInflation(t *testing.T) { // Test that provisions are correctly added to the pool and validators each hour for 1 year func TestProcessProvisions(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( - initialTotalTokens int64 = 550000000 - initialBondedTokens int64 = 250000000 - initialUnbondedTokens int64 = 300000000 - cumulativeExpProvs sdk.Int = sdk.ZeroInt() - validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} - bondedValidators uint16 = 2 + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 250000000 + initialUnbondedTokens int64 = 300000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 2 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -89,7 +89,7 @@ func TestProcessProvisions(t *testing.T) { for hr := 0; hr < 8766; hr++ { pool := keeper.GetPool(ctx) _, expProvisions, _ := updateProvisions(t, keeper, pool, ctx, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) + cumulativeExpProvs = cumulativeExpProvs + expProvisions } //get the pool and do the final value checks from checkFinalPoolValues @@ -100,17 +100,18 @@ func TestProcessProvisions(t *testing.T) { // Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate // Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years) func TestHourlyInflationRateOfChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( - initialTotalTokens int64 = 550000000 - initialBondedTokens int64 = 150000000 - initialUnbondedTokens int64 = 400000000 - cumulativeExpProvs sdk.Int = sdk.ZeroInt() - validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} - bondedValidators uint16 = 1 + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 150000000 + initialUnbondedTokens int64 = 400000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -121,7 +122,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { pool := keeper.GetPool(ctx) previousInflation := pool.Inflation updatedInflation, expProvisions, pool := updateProvisions(t, keeper, pool, ctx, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) + cumulativeExpProvs = cumulativeExpProvs + expProvisions msg := strconv.Itoa(hr) checkInflation(t, pool, previousInflation, updatedInflation, msg) } @@ -133,7 +134,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { //Test that a large unbonding will significantly lower the bonded ratio func TestLargeUnbond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -147,32 +148,33 @@ func TestLargeUnbond(t *testing.T) { validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000} bondedValidators uint16 = 7 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[0]) + validator, found := keeper.GetValidator(ctx, Addrs[0]) assert.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() // validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) bondedShares = bondedShares.Sub(bondSharesVal0) - val0UnbondedTokens = pool.unbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() - unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.unbondedShareExRate())) + val0UnbondedTokens = pool.UnbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() + unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.UnbondedShareExRate())) // unbonded shares should increase assert.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1))) // Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75) - assert.True(t, (pool.bondedRatio().LT(initialBondedRatio))) + assert.True(t, (pool.BondedRatio().LT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -181,7 +183,7 @@ func TestLargeUnbond(t *testing.T) { //Test that a large bonding will significantly increase the bonded ratio func TestLargeBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -193,24 +195,25 @@ func TestLargeBond(t *testing.T) { validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000} bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[9]) + validator, found := keeper.GetValidator(ctx, Addrs[9]) assert.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() - params := DefaultParams() + params := types.DefaultParams() params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) // validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) @@ -219,7 +222,7 @@ func TestLargeBond(t *testing.T) { // unbonded shares should decrease assert.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1))) // Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%) - assert.True(t, (pool.bondedRatio().GT(initialBondedRatio))) + assert.True(t, (pool.BondedRatio().GT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -228,20 +231,19 @@ func TestLargeBond(t *testing.T) { // Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators func TestInflationWithRandomOperations(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) - params := DefaultParams() - keeper.setParams(ctx, params) + ctx, _, keeper := CreateTestInput(t, false, 0) + params := types.DefaultParams() + keeper.SetParams(ctx, params) numValidators := 20 // start off by randomly setting up 20 validators - pool, validators := randomSetup(r, numValidators) + pool, validators := types.RandomSetup(r, numValidators) require.Equal(t, numValidators, len(validators)) for i := 0; i < len(validators); i++ { - keeper.setValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) } - - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Used to rotate validators so each random operation is applied to a different validator validatorCounter := 0 @@ -250,30 +252,30 @@ func TestInflationWithRandomOperations(t *testing.T) { for i := 0; i < numValidators; i++ { pool := keeper.GetPool(ctx) - // Get inflation before randomOperation, for comparison later + // Get inflation before RandomOperation, for comparison later previousInflation := pool.Inflation // Perform the random operation, and record how validators are modified - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, pool, validators[validatorCounter]) - validatorsMod := make([]Validator, len(validators)) + poolMod, validatorMod, tokens, msg := types.RandomOperation(r)(r, pool, validators[validatorCounter]) + validatorsMod := make([]types.Validator, len(validators)) copy(validatorsMod[:], validators[:]) require.Equal(t, numValidators, len(validators), "i %v", validatorCounter) require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter) validatorsMod[validatorCounter] = validatorMod - assertInvariants(t, msg, + types.AssertInvariants(t, msg, pool, validators, poolMod, validatorsMod, tokens) // set pool and validators after the random operation pool = poolMod - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) validators = validatorsMod // Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation - updatedInflation := keeper.nextInflation(ctx) + updatedInflation := keeper.NextInflation(ctx) pool.Inflation = updatedInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Ensure inflation changes as expected when random operations are applied. checkInflation(t, pool, previousInflation, updatedInflation, msg) @@ -285,41 +287,43 @@ func TestInflationWithRandomOperations(t *testing.T) { ////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// // Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens int64, cumulativeExpProvs sdk.Int) { - calculatedTotalTokens := cumulativeExpProvs.AddRaw(initialTotalTokens) - - assert.Equal(t, calculatedTotalTokens.Int64(), pool.TokenSupply().Int64()) +func checkFinalPoolValues(t *testing.T, pool types.Pool, initialTotalTokens, cumulativeExpProvs int64) { + calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs + assert.Equal(t, calculatedTotalTokens, pool.TokenSupply()) } // Processes provisions are added to the pool correctly every hour // Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, hr int) (sdk.Rat, sdk.Int, Pool) { - expInflation := keeper.nextInflation(ctx) - expProvisions := (expInflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat)).EvaluateInt() +func updateProvisions(t *testing.T, keeper Keeper, pool types.Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, types.Pool) { + expInflation := keeper.NextInflation(ctx) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() startTotalSupply := pool.TokenSupply() - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) //check provisions were added to pool - require.Equal(t, startTotalSupply.Add(expProvisions).Int64(), pool.TokenSupply().Int64()) + require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) + return expInflation, expProvisions, pool } // Deterministic setup of validators and pool // Allows you to decide how many validators to setup // Allows you to pick which validators are bonded by adjusting the MaxValidators of params -func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, maxValidators uint16) ([]Validator, Keeper, Pool) { - params := DefaultParams() +func setupTestValidators(pool types.Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, + maxValidators uint16) ([]types.Validator, Keeper, types.Pool) { + + params := types.DefaultParams() params.MaxValidators = maxValidators - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) numValidators := len(validatorTokens) - validators := make([]Validator, numValidators) + validators := make([]types.Validator, numValidators) for i := 0; i < numValidators; i++ { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i], pool, _ = validators[i].addTokensFromDel(pool, sdk.NewInt(validatorTokens[i])) - keeper.setPool(ctx, pool) - validators[i] = keeper.updateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, validatorTokens[i]) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order pool = keeper.GetPool(ctx) } @@ -327,28 +331,28 @@ func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTok } // Checks that the deterministic validator setup you wanted matches the values in the pool -func checkValidatorSetup(t *testing.T, pool Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { - assert.Equal(t, initialTotalTokens, pool.TokenSupply().Int64()) - assert.Equal(t, initialBondedTokens, pool.BondedTokens.Int64()) - assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens.Int64()) +func checkValidatorSetup(t *testing.T, pool types.Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { + assert.Equal(t, initialTotalTokens, pool.TokenSupply(), "%v", pool) + assert.Equal(t, initialBondedTokens, pool.BondedTokens, "%v", pool) + assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens, "%v", pool) // test initial bonded ratio - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.bondedRatio()) + assert.True(t, pool.BondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.BondedRatio()) // test the value of validator shares - assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) + assert.True(t, pool.BondedShareExRate().Equal(sdk.OneRat()), "%v", pool.BondedShareExRate()) } // Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) { +func checkInflation(t *testing.T, pool types.Pool, previousInflation, updatedInflation sdk.Rat, msg string) { inflationChange := updatedInflation.Sub(previousInflation) switch { //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): if previousInflation.Equal(sdk.NewRat(20, 100)) { assert.Equal(t, true, inflationChange.IsZero(), msg) @@ -358,11 +362,11 @@ func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation } //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): if previousInflation.Equal(sdk.NewRat(7, 100)) { assert.Equal(t, true, inflationChange.IsZero(), msg) diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go new file mode 100644 index 000000000..187649c5f --- /dev/null +++ b/x/stake/keeper/keeper.go @@ -0,0 +1,122 @@ +package keeper + +import ( + 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/stake/types" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *wire.Codec + coinKeeper bank.Keeper + + // codespace + codespace sdk.CodespaceType +} + +func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + coinKeeper: ck, + codespace: codespace, + } + return keeper +} + +//_________________________________________________________________________ + +// return the codespace +func (k Keeper) Codespace() sdk.CodespaceType { + return k.codespace +} + +//_________________________________________________________________________ +// some generic reads/writes that don't need their own files + +// load/save the global staking params +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + store := ctx.KVStore(k.storeKey) + + b := store.Get(ParamKey) + if b == nil { + panic("Stored params should not have been nil") + } + + k.cdc.MustUnmarshalBinary(b, ¶ms) + return +} + +// Need a distinct function because setParams depends on an existing previous +// record of params to exist (to check if maxValidators has changed) - and we +// panic on retrieval if it doesn't exist - hence if we use setParams for the very +// first params set it will panic. +func (k Keeper) SetNewParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +// set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + exParams := k.GetParams(ctx) + + // if max validator count changes, must recalculate validator set + if exParams.MaxValidators != params.MaxValidators { + k.UpdateBondedValidatorsFull(ctx) + } + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +//_______________________________________________________________________ + +// load/save the pool +func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + panic("Stored pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(b, &pool) + return +} + +// set the pool +func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(pool) + store.Set(PoolKey, b) +} + +//__________________________________________________________________________ + +// get the current in-block validator operation counter +func (k Keeper) InitIntraTxCounter(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + if b == nil { + k.SetIntraTxCounter(ctx, 0) + } +} + +// get the current in-block validator operation counter +func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + var counter int16 + k.cdc.MustUnmarshalBinary(b, &counter) + return counter +} + +// set the current in-block validator operation counter +func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(counter) + store.Set(IntraTxCounterKey, bz) +} diff --git a/x/stake/keeper/keeper_test.go b/x/stake/keeper/keeper_test.go new file mode 100644 index 000000000..1be587285 --- /dev/null +++ b/x/stake/keeper/keeper_test.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +func TestParams(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expParams := types.DefaultParams() + + //check that the empty keeper loads the default + resParams := keeper.GetParams(ctx) + assert.True(t, expParams.Equal(resParams)) + + //modify a params, save, and retrieve + expParams.MaxValidators = 777 + keeper.SetParams(ctx, expParams) + resParams = keeper.GetParams(ctx) + assert.True(t, expParams.Equal(resParams)) +} + +func TestPool(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expPool := types.InitialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + assert.True(t, expPool.Equal(resPool)) + + //modify a params, save, and retrieve + expPool.BondedTokens = 777 + keeper.SetPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + assert.True(t, expPool.Equal(resPool)) +} diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go new file mode 100644 index 000000000..87dc511d6 --- /dev/null +++ b/x/stake/keeper/key.go @@ -0,0 +1,197 @@ +package keeper + +import ( + "encoding/binary" + + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// TODO remove some of these prefixes once have working multistore + +//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 + ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey + ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators + ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power + ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator + ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + IntraTxCounterKey = []byte{0x09} // key for intra-block tx index + DelegationKey = []byte{0x0A} // key for a delegation + UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner + RedelegationKey = []byte{0x0D} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by validator owner + RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by validator owner +) + +const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch + +// get the key for the validator with address +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 GetValidatorsBondedIndexKey(ownerAddr sdk.Address) []byte { + return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...) +} + +// get the power which is the key for the validator used in the power-store +func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { + + // NOTE the address doesn't need to be stored because counter bytes must always be different + return GetValidatorPowerRank(validator, pool) +} + +// get the power of a validator +func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { + + power := validator.EquivalentBondedShares(pool) + powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) + + // TODO ensure that the key will be a readable string.. probably should add seperators and have + revokedBytes := make([]byte, 1) + if validator.Revoked { + revokedBytes[0] = byte(0x01) + } else { + revokedBytes[0] = byte(0x00) + } + + // TODO ensure that the key will be a readable string.. probably should add seperators and have + // heightBytes and counterBytes represent strings like powerBytes does + heightBytes := make([]byte, binary.MaxVarintLen64) + binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) + counterBytes := make([]byte, 2) + binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) + + return append(ValidatorsByPowerIndexKey, + append(revokedBytes, + append(powerBytes, + append(heightBytes, counterBytes...)...)...)...) +} + +// get the key for the accumulated update validators +func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { + return append(TendermintUpdatesKey, ownerAddr.Bytes()...) +} + +//________________________________________________________________________________ + +// get the key for delegator bond with validator +func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +} + +// get the prefix for a delegator for all validators +func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(DelegationKey, res...) +} + +//________________________________________________________________________________ + +// get the key for an unbonding delegation +func GetUBDKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetUBDsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +} + +// get the index-key for an unbonding delegation, stored by validator-index +func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetUBDsByValIndexKey(validatorAddr, cdc), delegatorAddr.Bytes()...) +} + +//______________ + +// get the prefix for all unbonding delegations from a delegator +func GetUBDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(UnbondingDelegationKey, res...) +} + +// get the prefix keyspace for the indexs of unbonding delegations for a validator +func GetUBDsByValIndexKey(validatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorAddr) + return append(UnbondingDelegationByValIndexKey, res...) +} + +//________________________________________________________________________________ + +// get the key for a redelegation +func GetREDKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsKey(delegatorAddr, cdc), + append( + validatorSrcAddr.Bytes(), + validatorDstAddr.Bytes()...)..., + ) +} + +// get the index-key for a redelegation, stored by source-validator-index +func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsFromValSrcIndexKey(validatorSrcAddr, cdc), + append( + delegatorAddr.Bytes(), + validatorDstAddr.Bytes()...)..., + ) +} + +// get the index-key for a redelegation, stored by destination-validator-index +func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsToValDstIndexKey(validatorDstAddr, cdc), + append( + delegatorAddr.Bytes(), + validatorSrcAddr.Bytes()...)..., + ) +} + +//______________ + +// get the prefix keyspace for redelegations from a delegator +func GetREDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(RedelegationKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating away from a source validator +func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorSrcAddr) + return append(RedelegationByValSrcIndexKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorDstAddr) + return append(RedelegationByValDstIndexKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +// from a particular delegator +func GetREDsByDelToValDstIndexKey(delegatorAddr sdk.Address, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsToValDstIndexKey(validatorDstAddr, cdc), + delegatorAddr.Bytes()...) +} diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go new file mode 100644 index 000000000..55ad658a2 --- /dev/null +++ b/x/stake/keeper/sdk_types.go @@ -0,0 +1,104 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Implements ValidatorSet +var _ sdk.ValidatorSet = Keeper{} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// get the sdk.validator for a particular address +func (k Keeper) Validator(ctx sdk.Context, address sdk.Address) sdk.Validator { + val, found := k.GetValidator(ctx, address) + if !found { + return nil + } + return val +} + +// total power from the bond +func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { + pool := k.GetPool(ctx) + return pool.BondedShares +} + +//__________________________________________________________________________ + +// Implements DelegationSet + +var _ sdk.DelegationSet = Keeper{} + +// Returns self as it is both a validatorset and delegationset +func (k Keeper) GetValidatorSet() sdk.ValidatorSet { + return k +} + +// get the delegation for a particular set of delegator and validator addresses +func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { + bond, ok := k.GetDelegation(ctx, addrDel, addrVal) + if !ok { + return nil + } + return bond +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + key := GetDelegationsKey(delAddr, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, key) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bz, &delegation) + stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go new file mode 100644 index 000000000..dda6530ea --- /dev/null +++ b/x/stake/keeper/slash.go @@ -0,0 +1,59 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// NOTE the current slash functionality doesn't take into consideration unbonding/rebonding records +// or the time of breach. This will be updated in slashing v2 +// 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 + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address())) + } + sharesToRemove := validator.PoolShares.Amount.Mul(fraction) + pool := k.GetPool(ctx) + validator, pool, burned := validator.RemovePoolShares(pool, sharesToRemove) + k.SetPool(ctx, pool) // update the pool + k.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out + + logger := ctx.Logger().With("module", "x/stake") + 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) { + + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey)) + } + validator.Revoked = true + k.UpdateValidator(ctx, validator) // update the validator, now revoked + + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) + return +} + +// unrevoke a validator +func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey)) + } + validator.Revoked = false + k.UpdateValidator(ctx, validator) // update the validator, now unrevoked + + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) + return +} diff --git a/x/stake/test_common.go b/x/stake/keeper/test_common.go similarity index 63% rename from x/stake/test_common.go rename to x/stake/keeper/test_common.go index 19e35c277..eea71c07e 100644 --- a/x/stake/test_common.go +++ b/x/stake/keeper/test_common.go @@ -1,4 +1,4 @@ -package stake +package keeper import ( "bytes" @@ -18,35 +18,52 @@ import ( "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/types" ) // dummy addresses used for testing var ( - addrs = createTestAddrs(100) - pks = createTestPubKeys(100) + Addrs = createTestAddrs(100) + PKs = createTestPubKeys(100) emptyAddr sdk.Address emptyPubkey crypto.PubKey + + addrDels = []sdk.Address{ + Addrs[0], + Addrs[1], + } + addrVals = []sdk.Address{ + Addrs[2], + Addrs[3], + Addrs[4], + Addrs[5], + Addrs[6], + } ) //_______________________________________________________________________________________ // intended to be used with require/assert: require.True(ValEq(...)) -func ValEq(t *testing.T, exp, got Validator) (*testing.T, bool, string, Validator, Validator) { - return t, exp.equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func ValEq(t *testing.T, exp, got types.Validator) (*testing.T, bool, string, types.Validator, types.Validator) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got } //_______________________________________________________________________________________ -func makeTestCodec() *wire.Codec { +// create a codec used only for testing +func MakeTestCodec() *wire.Codec { var cdc = wire.NewCodec() // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) cdc.RegisterConcrete(bank.MsgIssue{}, "test/stake/Issue", nil) - cdc.RegisterConcrete(MsgCreateValidator{}, "test/stake/CreateValidator", nil) - cdc.RegisterConcrete(MsgEditValidator{}, "test/stake/EditValidator", nil) - cdc.RegisterConcrete(MsgUnbond{}, "test/stake/Unbond", nil) + cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) + cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) + cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) + cdc.RegisterConcrete(types.MsgCompleteUnbonding{}, "test/stake/CompleteUnbonding", nil) + cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil) + cdc.RegisterConcrete(types.MsgCompleteRedelegate{}, "test/stake/CompleteRedelegate", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) @@ -56,8 +73,9 @@ func makeTestCodec() *wire.Codec { return cdc } -func paramsNoInflation() Params { - return Params{ +// default params without inflation +func ParamsNoInflation() types.Params { + return types.Params{ InflationRateChange: sdk.ZeroRat(), InflationMax: sdk.ZeroRat(), InflationMin: sdk.ZeroRat(), @@ -68,7 +86,7 @@ func paramsNoInflation() Params { } // hogpodge of all sorts of input required for testing -func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Context, auth.AccountMapper, Keeper) { +func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") keyAcc := sdk.NewKVStoreKey("acc") @@ -81,28 +99,32 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Conte require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) - cdc := makeTestCodec() + cdc := MakeTestCodec() accountMapper := auth.NewAccountMapper( cdc, // amino codec keyAcc, // target store &auth.BaseAccount{}, // prototype ) ck := bank.NewKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) - keeper.setPool(ctx, InitialPool()) - keeper.setNewParams(ctx, DefaultParams()) + keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace) + keeper.SetPool(ctx, types.InitialPool()) + keeper.SetNewParams(ctx, types.DefaultParams()) + keeper.InitIntraTxCounter(ctx) - // fill all the addresses with some coins - for _, addr := range addrs { + // fill all the addresses with some coins, set the loose pool tokens simultaneously + for _, addr := range Addrs { + pool := keeper.GetPool(ctx) ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.GetParams(ctx).BondDenom, initCoins}, + {keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, }) + pool.LooseTokens += initCoins + keeper.SetPool(ctx, pool) } return ctx, accountMapper, keeper } -func newPubKey(pk string) (res crypto.PubKey) { +func NewPubKey(pk string) (res crypto.PubKey) { pkBytes, err := hex.DecodeString(pk) if err != nil { panic(err) @@ -114,7 +136,7 @@ func newPubKey(pk string) (res crypto.PubKey) { } // for incode address generation -func testAddr(addr string, bech string) sdk.Address { +func TestAddr(addr string, bech string) sdk.Address { res, err := sdk.GetAccAddressHex(addr) if err != nil { @@ -151,7 +173,7 @@ func createTestAddrs(numAddrs int) []sdk.Address { buffer.WriteString(numString) //adding on final two digits to make addresses unique res, _ := sdk.GetAccAddressHex(buffer.String()) bech, _ := sdk.Bech32ifyAcc(res) - addresses = append(addresses, testAddr(buffer.String(), bech)) + addresses = append(addresses, TestAddr(buffer.String(), bech)) buffer.Reset() } return addresses @@ -166,8 +188,16 @@ func createTestPubKeys(numPubKeys int) []crypto.PubKey { numString := strconv.Itoa(i) buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string buffer.WriteString(numString) //adding on final two digits to make pubkeys unique - publicKeys = append(publicKeys, newPubKey(buffer.String())) + publicKeys = append(publicKeys, NewPubKey(buffer.String())) buffer.Reset() } return publicKeys } + +//_____________________________________________________________________________________ + +// does a certain by-power index record exist +func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) bool { + store := ctx.KVStore(keeper.storeKey) + return store.Get(power) != nil +} diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go new file mode 100644 index 000000000..b018aea44 --- /dev/null +++ b/x/stake/keeper/validator.go @@ -0,0 +1,537 @@ +package keeper + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// get a single validator +func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorKey(addr)) + if b == nil { + return validator, false + } + k.cdc.MustUnmarshalBinary(b, &validator) + return validator, true +} + +// get a single validator by pubkey +func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) + if addr == nil { + return validator, false + } + return k.GetValidator(ctx, addr) +} + +// set the main record holding validator details +func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + // set main store + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bz) +} + +// validator index +func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + // set pointer by pubkey + store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) +} + +// used in testing +func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(power) != nil +} + +// Get the set of all validators with no limits, used during genesis dump +func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + validators = append(validators, validator) + iterator.Next() + } + iterator.Close() + return validators +} + +// Get the set of all validators, retrieve a maxRetrieve number of records +func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + validators = make([]types.Validator, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + validators[i] = validator + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//___________________________________________________________________________ + +// get the group of the bonded validators +func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + validators = make([]types.Validator, maxValidators) + + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := 0 + for ; iterator.Valid(); iterator.Next() { + + // sanity check + if i > int(maxValidators-1) { + panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") + } + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + validators[i] = validator + i++ + } + iterator.Close() + return validators[:i] // trim +} + +// get the group of bonded validators sorted by power-rank +func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { + store := ctx.KVStore(k.storeKey) + maxValidators := k.GetParams(ctx).MaxValidators + validators := make([]types.Validator, maxValidators) + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + i := 0 + for { + if !iterator.Valid() || i > int(maxValidators-1) { + break + } + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + if validator.Status() == sdk.Bonded { + validators[i] = validator + i++ + } + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//_________________________________________________________________________ +// Accumulated updates to the active/bonded validator set for tendermint + +// get the most recently updated validators +func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { + store := ctx.KVStore(k.storeKey) + + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val abci.Validator + k.cdc.MustUnmarshalBinary(valBytes, &val) + updates = append(updates, val) + } + iterator.Close() + return +} + +// remove all validator update entries after applied to Tendermint +func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + + // delete subspace + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } + iterator.Close() +} + +//___________________________________________________________________________ + +// perfom all the nessisary steps for when a validator changes its power +// updates all validator stores as well as tendermint update store +// may kick out validators if new validator is entering the bonded validator group +func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + ownerAddr := validator.Owner + + // always update the main list ordered by owner address before exiting + defer func() { + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(ownerAddr), bz) + }() + + // retrieve the old validator record + oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) + + if validator.Revoked && oldValidator.Status() == sdk.Bonded { + validator = k.unbondValidator(ctx, 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 + if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { + powerIncreasing = true + } + + // if already a validator, copy the old block height and counter, else set them + if oldFound && oldValidator.Status() == sdk.Bonded { + validator.BondHeight = oldValidator.BondHeight + validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter + } else { + validator.BondHeight = ctx.BlockHeight() + counter := k.GetIntraTxCounter(ctx) + validator.BondIntraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) + } + + // update the list ordered by voting power + if oldFound { + store.Delete(GetValidatorsByPowerIndexKey(oldValidator, pool)) + } + valPower := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(valPower, validator.Owner) + + // efficiency case: + // if already bonded and power increasing only need to update tendermint + if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { + bz := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc)) + store.Set(GetTendermintUpdatesKey(ownerAddr), bz) + return validator + } + + // efficiency case: + // if was unbonded/or is a new validator - and the new power is less than the cliff validator + cliffPower := k.getCliffValidatorPower(ctx) + if cliffPower != nil && + (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && + bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower + return validator + } + + // update the validator set for this validator + updatedVal := k.UpdateBondedValidators(ctx, validator) + if updatedVal.Owner != nil { // updates to validator occurred to be updated + validator = updatedVal + } + return validator +} + +// Update the validator group and kick out any old validators. In addition this +// function adds (or doesn't add) a validator which has updated its bonded +// tokens to the validator group. -> this validator is specified through the +// updatedValidatorAddr term. +// +// The correct subset is retrieved by iterating through an index of the +// validators sorted by power, stored using the ValidatorsByPowerIndexKey. +// Simultaneously the current validator records are updated in store with the +// ValidatorsBondedIndexKey. This store is used to determine if a validator is a +// validator without needing to iterate over the subspace as we do in +// GetValidators. +// +// Optionally also return the validator from a retrieve address if the validator has been bonded +func (k Keeper) UpdateBondedValidators(ctx sdk.Context, + newValidator types.Validator) (updatedVal types.Validator) { + + store := ctx.KVStore(k.storeKey) + + kickCliffValidator := false + oldCliffValidatorAddr := k.getCliffValidator(ctx) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + // TODO benchmark if we should read the current power and not write if it's the same + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } + break + } + + // either retrieve the original validator from the store, or under the + // situation that this is the "new validator" just use the validator + // provided because it has not yet been updated in the main validator + // store + ownerAddr := iterator.Value() + if bytes.Equal(ownerAddr, newValidator.Owner) { + validator = newValidator + } else { + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + } + + // if not previously a validator (and unrevoked), + // kick the cliff validator / bond this new validator + if validator.Status() != sdk.Bonded && !validator.Revoked { + kickCliffValidator = true + + validator = k.bondValidator(ctx, validator) + if bytes.Equal(ownerAddr, newValidator.Owner) { + updatedVal = validator + } + } + + if validator.Revoked && validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } else { + bondedValidatorsCount++ + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + if oldCliffValidatorAddr != nil && kickCliffValidator { + validator, found := k.GetValidator(ctx, oldCliffValidatorAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) + } + k.unbondValidator(ctx, validator) + } + + return +} + +// full update of the bonded validator set, many can be added/kicked +func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { + + store := ctx.KVStore(k.storeKey) + + // clear the current validators store, add to the ToKickOut temp store + toKickOut := make(map[string]byte) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + for ; iterator.Valid(); iterator.Next() { + ownerAddr := iterator.Value() + toKickOut[string(ownerAddr)] = 0 // set anything + } + iterator.Close() + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } + break + } + + // either retrieve the original validator from the store, + // or under the situation that this is the "new validator" just + // use the validator provided because it has not yet been updated + // in the main validator store + ownerAddr := iterator.Value() + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + + _, found = toKickOut[string(ownerAddr)] + if found { + delete(toKickOut, string(ownerAddr)) + } else { + + // if it wasn't in the toKickOut group it means + // this wasn't a previously a validator, therefor + // update the validator to enter the validator group + validator = k.bondValidator(ctx, validator) + } + + if validator.Revoked && validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } else { + bondedValidatorsCount++ + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + for key := range toKickOut { + ownerAddr := []byte(key) + validator, found := k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + k.unbondValidator(ctx, validator) + } + return +} + +// perform all the store operations for when a validator status becomes unbonded +func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Unbonded { + panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + k.SetPool(ctx, pool) + + // save the now unbonded validator record + bzVal := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bzVal) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc)) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + // also remove from the Bonded types.Validators Store + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + return validator +} + +// perform all the store operations for when a validator status becomes bonded +func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + k.SetPool(ctx, pool) + + // save the now bonded validator record to the three referenced stores + bzVal := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bzVal) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc)) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + return validator +} + +// remove the validator record and associated indexes +func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.Address) { + + // first retrieve the old validator record + validator, found := k.GetValidator(ctx, address) + if !found { + return + } + + // delete the old validator record + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) + store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + + // delete from the current and power weighted validator groups if the validator + // is bonded - and add validator with zero power to the validator updates + if store.Get(GetValidatorsBondedIndexKey(validator.Owner)) == nil { + return + } + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + + bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc)) + store.Set(GetTendermintUpdatesKey(address), bz) +} + +//__________________________________________________________________________ + +// get the current validator on the cliff +func (k Keeper) getCliffValidator(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorCliffIndexKey) +} + +// get the current power of the validator on the cliff +func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorPowerCliffKey) +} + +// set the current validator and power of the validator on the cliff +func (k Keeper) setCliffValidator(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + bz := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(ValidatorPowerCliffKey, bz) + store.Set(ValidatorCliffIndexKey, 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(ValidatorCliffIndexKey) +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go new file mode 100644 index 000000000..245954ad1 --- /dev/null +++ b/x/stake/keeper/validator_test.go @@ -0,0 +1,674 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + // test how the validator is set from a purely unbonbed pool + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, 10) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + + // after the save the validator should be bonded + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + + // Check each store for being saved + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + assert.True(ValEq(t, validator, resVal)) + + resVals := keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidatorsByPower(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + assert.Equal(t, validator.ABCIValidator(keeper.cdc), updates[0]) + +} + +func TestUpdateValidatorByPowerIndex(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + // create a random pool + pool.LooseTokens = 10000 + pool.BondedTokens = 1234 + pool.BondedShares = sdk.NewRat(124) + pool.UnbondingTokens = 13934 + pool.UnbondingShares = sdk.NewRat(145) + pool.UnbondedTokens = 154 + pool.UnbondedShares = sdk.NewRat(1333) + keeper.SetPool(ctx, pool) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, 100) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) + + pool = keeper.GetPool(ctx) + power := GetValidatorsByPowerIndexKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // burn half the delegator shares + validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) + assert.Equal(t, int64(50), burned) + keeper.SetPool(ctx, pool) // update the pool + keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out + assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + + pool = keeper.GetPool(ctx) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + power = GetValidatorsByPowerIndexKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) +} + +// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator +func TestValidatorBasics(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + pool := keeper.GetPool(ctx) + + //construct the validators + var validators [3]types.Validator + amts := []int64{9, 8, 7} + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewUnbondedShares(sdk.ZeroRat()) + validators[i].AddTokensFromDel(pool, amt) + } + + // check the empty keeper first + _, found := keeper.GetValidator(ctx, addrVals[0]) + assert.False(t, found) + resVals := keeper.GetValidatorsBonded(ctx) + assert.Zero(t, len(resVals)) + + // set and retrieve a record + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // modify a records, save, and retrieve + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(10)) + validators[0].DelegatorShares = sdk.NewRat(10) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // add other validators + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resVal, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + assert.True(ValEq(t, validators[1], resVal)) + resVal, found = keeper.GetValidator(ctx, addrVals[2]) + require.True(t, found) + assert.True(ValEq(t, validators[2], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 3, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here + assert.True(ValEq(t, validators[1], resVals[1])) + assert.True(ValEq(t, validators[2], resVals[2])) + + // remove a record + keeper.RemoveValidator(ctx, validators[1].Owner) + _, found = keeper.GetValidator(ctx, addrVals[1]) + assert.False(t, found) +} + +// test how the validators are sorted, tests GetValidatorsByPower +func GetValidatorSortingUnmixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewBondedShares(sdk.NewRat(amt)) + validators[i].DelegatorShares = sdk.NewRat(amt) + keeper.UpdateValidator(ctx, validators[i]) + } + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) + + // test a basic increase in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(500)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + + // test a decrease in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // test equal voting power, different age + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(200)) + ctx = ctx.WithBlockHeight(10) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) + assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) + + // no change in voting power - no change in sort + ctx = ctx.WithBlockHeight(20) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // change in voting power of both validators, both still in v-set, no age change + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + ctx = ctx.WithBlockHeight(30) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n, "%v", resValidators) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) +} + +func GetValidatorSortingMixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i].DelegatorShares = sdk.NewRat(amt) + } + validators[0].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[0])) + validators[1].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[1])) + validators[2].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[2])) + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(amts[3])) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(amts[4])) + for i := range amts { + keeper.UpdateValidator(ctx, validators[i]) + } + val0, found := keeper.GetValidator(ctx, Addrs[0]) + require.True(t, found) + val1, found := keeper.GetValidator(ctx, Addrs[1]) + require.True(t, found) + val2, found := keeper.GetValidator(ctx, Addrs[2]) + require.True(t, found) + val3, found := keeper.GetValidator(ctx, Addrs[3]) + require.True(t, found) + val4, found := keeper.GetValidator(ctx, Addrs[4]) + require.True(t, found) + assert.Equal(t, sdk.Unbonded, val0.Status()) + assert.Equal(t, sdk.Unbonded, val1.Status()) + assert.Equal(t, sdk.Unbonded, val2.Status()) + assert.Equal(t, sdk.Bonded, val3.Status()) + assert.Equal(t, sdk.Bonded, val4.Status()) + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) +} + +// TODO separate out into multiple tests +func TestGetValidatorsEdgeCases(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + var found bool + + // now 2 max resValidators + params := keeper.GetParams(ctx) + nMax := uint16(2) + params.MaxValidators = nMax + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400} + var validators [4]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[2], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 500) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // A validator which leaves the gotValidator set due to a decrease in voting power, + // then increases to the original voting power, does not get its spot back in the + // case of a tie. + + // validator 3 enters bonded validator set + ctx = ctx.WithBlockHeight(40) + + validators[3], found = keeper.GetValidator(ctx, validators[3].Owner) + require.True(t, found) + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 1) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + // validator 3 kicked out temporarily + validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewRat(201)) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // validator 4 does not get spot back + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 200) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + validator, exists := keeper.GetValidator(ctx, validators[3].Owner) + require.Equal(t, exists, true) + require.Equal(t, int64(40), validator.BondHeight) +} + +func TestValidatorBondHeight(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + pool := keeper.GetPool(ctx) + var validators [3]types.Validator + validators[0] = types.NewValidator(Addrs[0], PKs[0], types.Description{}) + validators[1] = types.NewValidator(Addrs[1], PKs[1], types.Description{}) + validators[2] = types.NewValidator(Addrs[2], PKs[2], types.Description{}) + + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 200) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 100) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 100) + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + //////////////////////////////////////// + // If two validators both increase to the same voting power in the same block, + // the one with the first transaction should become bonded + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, uint16(len(resValidators)), params.MaxValidators) + + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[1], resValidators[1])) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 50) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 50) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, params.MaxValidators, uint16(len(resValidators))) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +func TestFullValidatorSetPowerChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + max := 2 + params.MaxValidators = uint16(2) + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400, 200} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + var found bool + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + assert.Equal(t, sdk.Unbonded, validators[0].Status()) + assert.Equal(t, sdk.Unbonded, validators[1].Status()) + assert.Equal(t, sdk.Bonded, validators[2].Status()) + assert.Equal(t, sdk.Bonded, validators[3].Status()) + assert.Equal(t, sdk.Unbonded, validators[4].Status()) + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs + assert.True(ValEq(t, validators[3], resValidators[1])) + + // test a swap in voting power + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 600) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +// clear the tracked changes to the gotValidator set +func TestClearTendermintUpdates(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{100, 400, 200} + validators := make([]types.Validator, len(amts)) + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + + updates := keeper.GetTendermintUpdates(ctx) + assert.Equal(t, len(amts), len(updates)) + keeper.ClearTendermintUpdates(ctx) + updates = keeper.GetTendermintUpdates(ctx) + assert.Equal(t, 0, len(updates)) +} + +func TestGetTendermintUpdatesAllNone(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + + // test from nothing to something + // tendermintUpdate set: {} -> {c1, c3} + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) + assert.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1]) + + // test from something to nothing + // tendermintUpdate set: {} -> {c1, c2, c3, c4} + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + keeper.RemoveValidator(ctx, validators[0].Owner) + keeper.RemoveValidator(ctx, validators[1].Owner) + + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + 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) +} + +func TestGetTendermintUpdatesIdentical(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test identical, + // tendermintUpdate set: {} -> {} + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) +} + +func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test single value change + // tendermintUpdate set: {} -> {c1'} + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(600)) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + + updates := keeper.GetTendermintUpdates(ctx) + + require.Equal(t, 1, len(updates)) + assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) +} + +func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 190) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 80) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) + require.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1]) +} + +func TestGetTendermintUpdatesInserted(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20, 5, 15, 25} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[0]) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[3].ABCIValidator(keeper.cdc), updates[0]) + + // test validtor added at the end + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[4] = keeper.UpdateValidator(ctx, validators[4]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[4].ABCIValidator(keeper.cdc), updates[0]) +} + +func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := types.DefaultParams() + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + amts := []int64{10, 20, 5} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validator added at the end but not inserted in the valset + // tendermintUpdate set: {} -> {} + keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 0, len(updates)) + + // test validator change its power and become a gotValidator (pushing out an existing) + // tendermintUpdate set: {} -> {c0, c4} + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + pool := keeper.GetPool(ctx) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 10) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates), "%v", updates) + require.Equal(t, validators[0].ABCIValidatorZero(keeper.cdc), updates[0]) + require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[1]) +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go deleted file mode 100644 index 20062f00e..000000000 --- a/x/stake/keeper_keys.go +++ /dev/null @@ -1,89 +0,0 @@ -package stake - -import ( - "encoding/binary" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" -) - -// TODO remove some of these prefixes once have working multistore - -//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 - 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 - -// get the key for the validator with address -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() - return append(ValidatorsBondedKey, addr.Bytes()...) -} - -// get the key for the validator used in the power-store -func GetValidatorsByPowerKey(validator Validator, pool Pool) []byte { - - power := validator.EquivalentBondedShares(pool) - powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - - // TODO ensure that the key will be a readable string.. probably should add seperators and have - revokedBytes := make([]byte, 1) - if validator.Revoked { - revokedBytes[0] = byte(0x01) - } else { - revokedBytes[0] = byte(0x00) - } - // heightBytes and counterBytes represent strings like powerBytes does - heightBytes := make([]byte, binary.MaxVarintLen64) - binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) - counterBytes := make([]byte, 2) - binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(ValidatorsByPowerKey, - append(revokedBytes, - append(powerBytes, - append(heightBytes, - append(counterBytes, validator.Owner.Bytes()...)...)...)...)...) // TODO don't technically need to store owner -} - -// get the key for the accumulated update validators -func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { - return append(TendermintUpdatesKey, ownerAddr.Bytes()...) -} - -// get the key for delegator bond with validator -func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) -} - -// get the prefix for a delegator for all validators -func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegationKey, res...) -} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go deleted file mode 100644 index 7b8b9ddcc..000000000 --- a/x/stake/keeper_test.go +++ /dev/null @@ -1,792 +0,0 @@ -package stake - -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" -) - -var ( - addrDels = []sdk.Address{ - addrs[0], - addrs[1], - } - addrVals = []sdk.Address{ - addrs[2], - addrs[3], - addrs[4], - addrs[5], - addrs[6], - } -) - -func TestUpdateValidatorByPowerIndex(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - // create a random pool - pool.BondedTokens = sdk.NewInt(1234) - pool.BondedShares = sdk.NewRat(124) - pool.UnbondingTokens = sdk.NewInt(13934) - pool.UnbondingShares = sdk.NewRat(145) - pool.UnbondedTokens = sdk.NewInt(154) - pool.UnbondedShares = sdk.NewRat(1333) - keeper.setPool(ctx, pool) - - // add a validator - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, delSharesCreated := validator.addTokensFromDel(pool, sdk.NewInt(100)) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) - - pool = keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) - - // burn half the delegator shares - validator, pool, burned := validator.removeDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) - assert.Equal(t, int64(50), burned.Int64()) - keeper.setPool(ctx, pool) // update the pool - keeper.updateValidator(ctx, validator) // update the validator, possibly kicking it out - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) - - pool = keeper.GetPool(ctx) - validator, found = keeper.GetValidator(ctx, addrVals[0]) - power = GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) -} - -func TestSetValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - // test how the validator is set from a purely unbonbed pool - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, _ = validator.addTokensFromDel(pool, sdk.NewInt(10)) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - - // after the save the validator should be bonded - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - require.Equal(t, sdk.Bonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - - // Check each store for being saved - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - assert.True(ValEq(t, validator, resVal)) - - resVals := keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - resVals = keeper.GetValidatorsByPower(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - assert.Equal(t, validator.abciValidator(keeper.cdc), updates[0]) - -} - -// This function tests updateValidator, GetValidator, GetValidatorsBonded, removeValidator -func TestValidatorBasics(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - //construct the validators - var validators [3]Validator - amts := []int64{9, 8, 7} - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.ZeroRat()) - validators[i].addTokensFromDel(pool, sdk.NewInt(amt)) - } - - // check the empty keeper first - _, found := keeper.GetValidator(ctx, addrVals[0]) - assert.False(t, found) - resVals := keeper.GetValidatorsBonded(ctx) - assert.Zero(t, len(resVals)) - - // set and retrieve a record - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // modify a records, save, and retrieve - validators[0].PoolShares = NewBondedShares(sdk.NewRat(10)) - validators[0].DelegatorShares = sdk.NewRat(10) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found = keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // add other validators - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resVal, found = keeper.GetValidator(ctx, addrVals[1]) - require.True(t, found) - assert.True(ValEq(t, validators[1], resVal)) - resVal, found = keeper.GetValidator(ctx, addrVals[2]) - require.True(t, found) - assert.True(ValEq(t, validators[2], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 3, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[2])) // order doesn't matter here - assert.True(ValEq(t, validators[1], resVals[1])) - assert.True(ValEq(t, validators[2], resVals[0])) - - // remove a record - keeper.removeValidator(ctx, validators[1].Owner) - _, found = keeper.GetValidator(ctx, addrVals[1]) - assert.False(t, found) -} - -// test how the validators are sorted, tests GetValidatorsByPower -func GetValidatorSortingUnmixed(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - // initialize some validators into the state - amts := []int64{0, 100, 1, 400, 200} - n := len(amts) - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewBondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) - - // test a basic increase in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(500)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - - // test a decrease in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - - // test equal voting power, different age - validators[3].PoolShares = NewBondedShares(sdk.NewRat(200)) - ctx = ctx.WithBlockHeight(10) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) - assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) - - // no change in voting power - no change in sort - ctx = ctx.WithBlockHeight(20) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - - // change in voting power of both validators, both still in v-set, no age change - validators[3].PoolShares = NewBondedShares(sdk.NewRat(300)) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - ctx = ctx.WithBlockHeight(30) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n, "%v", resValidators) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) -} - -func GetValidatorSortingMixed(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - // now 2 max resValidators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 1, 400, 200} - - n := len(amts) - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(amts[0])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(amts[1])) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(amts[2])) - validators[3].PoolShares = NewBondedShares(sdk.NewRat(amts[3])) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(amts[4])) - for i := range amts { - keeper.updateValidator(ctx, validators[i]) - } - val0, found := keeper.GetValidator(ctx, addrs[0]) - require.True(t, found) - val1, found := keeper.GetValidator(ctx, addrs[1]) - require.True(t, found) - val2, found := keeper.GetValidator(ctx, addrs[2]) - require.True(t, found) - val3, found := keeper.GetValidator(ctx, addrs[3]) - require.True(t, found) - val4, found := keeper.GetValidator(ctx, addrs[4]) - require.True(t, found) - assert.Equal(t, sdk.Unbonded, val0.Status()) - assert.Equal(t, sdk.Unbonded, val1.Status()) - assert.Equal(t, sdk.Unbonded, val2.Status()) - assert.Equal(t, sdk.Bonded, val3.Status()) - assert.Equal(t, sdk.Bonded, val4.Status()) - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) -} - -// TODO separate out into multiple tests -func TestGetValidatorsEdgeCases(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - var found bool - - // now 2 max resValidators - params := keeper.GetParams(ctx) - nMax := uint16(2) - params.MaxValidators = nMax - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 400, 400} - var validators [4]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - validators[i] = keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[2], resValidators[0])) - assert.True(ValEq(t, validators[3], resValidators[1])) - - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(500)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - - // A validator which leaves the gotValidator set due to a decrease in voting power, - // then increases to the original voting power, does not get its spot back in the - // case of a tie. - - // validator 3 enters bonded validator set - ctx = ctx.WithBlockHeight(40) - - validators[3], found = keeper.GetValidator(ctx, validators[3].Owner) - require.True(t, found) - validators[3].PoolShares = NewUnbondedShares(sdk.NewRat(401)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[3], resValidators[1])) - - // validator 3 kicked out temporarily - validators[3].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - - // validator 4 does not get spot back - validators[3].PoolShares = NewBondedShares(sdk.NewRat(400)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) - validator, exists := keeper.GetValidator(ctx, validators[3].Owner) - require.Equal(t, exists, true) - require.Equal(t, int64(40), validator.BondHeight) -} - -func TestValidatorBondHeight(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - // now 2 max resValidators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - // initialize some validators into the state - var validators [3]Validator - validators[0] = NewValidator(addrs[0], pks[0], Description{}) - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(200)) - validators[0].DelegatorShares = sdk.NewRat(200) - validators[1] = NewValidator(addrs[1], pks[1], Description{}) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[1].DelegatorShares = sdk.NewRat(100) - validators[2] = NewValidator(addrs[2], pks[2], Description{}) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[2].DelegatorShares = sdk.NewRat(100) - - validators[0] = keeper.updateValidator(ctx, validators[0]) - //////////////////////////////////////// - // If two validators both increase to the same voting power in the same block, - // the one with the first transaction should become bonded - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, uint16(len(resValidators)), params.MaxValidators) - - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[1], resValidators[1])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = keeper.updateValidator(ctx, validators[1]) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) -} - -func TestFullValidatorSetPowerChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - params := keeper.GetParams(ctx) - max := 2 - params.MaxValidators = uint16(2) - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 400, 400, 200} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - var found bool - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - assert.Equal(t, sdk.Unbonded, validators[0].Status()) - assert.Equal(t, sdk.Unbonded, validators[1].Status()) - assert.Equal(t, sdk.Bonded, validators[2].Status()) - assert.Equal(t, sdk.Bonded, validators[3].Status()) - assert.Equal(t, sdk.Unbonded, validators[4].Status()) - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, max, len(resValidators)) - assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs - assert.True(ValEq(t, validators[3], resValidators[1])) - - // test a swap in voting power - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, max, len(resValidators)) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) -} - -// clear the tracked changes to the gotValidator set -func TestClearTendermintUpdates(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{100, 400, 200} - validators := make([]Validator, len(amts)) - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - updates := keeper.getTendermintUpdates(ctx) - assert.Equal(t, len(amts), len(updates)) - keeper.clearTendermintUpdates(ctx) - updates = keeper.getTendermintUpdates(ctx) - assert.Equal(t, 0, len(updates)) -} - -func TestGetTendermintUpdatesAllNone(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // test from nothing to something - // tendermintUpdate set: {} -> {c1, c3} - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - assert.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) - - // test from something to nothing - // tendermintUpdate set: {} -> {c1, c2, c3, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - keeper.removeValidator(ctx, validators[0].Owner) - keeper.removeValidator(ctx, validators[1].Owner) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - 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) -} - -func TestGetTendermintUpdatesIdentical(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test identical, - // tendermintUpdate set: {} -> {} - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) -} - -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test single value change - // tendermintUpdate set: {} -> {c1'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - - updates := keeper.getTendermintUpdates(ctx) - - require.Equal(t, 1, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test multiple value change - // tendermintUpdate set: {c1, c3} -> {c1', c3'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[1].PoolShares = NewBondedShares(sdk.NewRat(100)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - require.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - require.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) -} - -func TestGetTendermintUpdatesInserted(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20, 5, 15, 25} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - validators[2] = keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[3] = keeper.updateValidator(ctx, validators[3]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[3].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the end - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[4] = keeper.updateValidator(ctx, validators[4]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[4].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - params := DefaultParams() - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - amts := []int64{10, 20, 5} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validator added at the end but not inserted in the valset - // tendermintUpdate set: {} -> {} - keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 0, len(updates)) - - // test validator change its power and become a gotValidator (pushing out an existing) - // tendermintUpdate set: {} -> {c0, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(15)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates), "%v", updates) - require.Equal(t, validators[0].abciValidatorZero(keeper.cdc), updates[0]) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[1]) -} - -// tests GetDelegation, GetDelegations, SetDelegation, removeDelegation, GetBonds -func TestBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // first add a validators[0] to delegate too - validators[0] = keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - // check the empty keeper first - _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegation(ctx, bond1to2) - keeper.setDelegation(ctx, bond1to3) - keeper.setDelegation(ctx, bond2to1) - keeper.setDelegation(ctx, bond2to2) - keeper.setDelegation(ctx, bond2to3) - - // test all bond retrieve capabilities - resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - allBonds := keeper.getAllDelegations(ctx) - require.Equal(t, 6, len(allBonds)) - assert.True(t, bond1to1.equal(allBonds[0])) - assert.True(t, bond1to2.equal(allBonds[1])) - assert.True(t, bond1to3.equal(allBonds[2])) - assert.True(t, bond2to1.equal(allBonds[3])) - assert.True(t, bond2to2.equal(allBonds[4])) - assert.True(t, bond2to3.equal(allBonds[5])) - - // delete a record - keeper.removeDelegation(ctx, bond2to3) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 2, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - - // delete all the records from delegator 2 - keeper.removeDelegation(ctx, bond2to1) - keeper.removeDelegation(ctx, bond2to2) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) - assert.False(t, found) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 0, len(resBonds)) -} - -func TestParams(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - expParams := DefaultParams() - - //check that the empty keeper loads the default - resParams := keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) - - //modify a params, save, and retrieve - expParams.MaxValidators = 777 - keeper.setParams(ctx, expParams) - resParams = keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) -} - -func TestPool(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - expPool := InitialPool() - - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) - - //modify a params, save, and retrieve - expPool.BondedTokens = sdk.NewInt(777) - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) -} diff --git a/x/stake/msg.go b/x/stake/msg.go deleted file mode 100644 index b764a2388..000000000 --- a/x/stake/msg.go +++ /dev/null @@ -1,243 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// name to idetify transaction types -const MsgType = "stake" - -// XXX remove: think it makes more sense belonging with the Params so we can -// initialize at genesis - to allow for the same tests we should should make -// the ValidateBasic() function a return from an initializable function -// ValidateBasic(bondDenom string) function -const StakingToken = "steak" - -//Verify interface at compile time -var _, _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}, &MsgUnbond{} - -//______________________________________________________________________ - -// 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 NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, - bond sdk.Coin, description Description) MsgCreateValidator { - return MsgCreateValidator{ - Description: description, - ValidatorAddr: validatorAddr, - PubKey: pubkey, - Bond: bond, - } -} - -//nolint -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 MsgCreateValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - PubKey string `json:"pubkey"` - Bond sdk.Coin `json:"bond"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - PubKey: sdk.MustBech32ifyValPub(msg.PubKey), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgCreateValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount.Sign() != 1 { - return ErrBadBondingAmount(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "description must be included") - } - return nil -} - -//______________________________________________________________________ - -// MsgEditValidator - struct for editing a validator -type MsgEditValidator struct { - Description - ValidatorAddr sdk.Address `json:"address"` -} - -func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { - return MsgEditValidator{ - Description: description, - ValidatorAddr: validatorAddr, - } -} - -//nolint -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 MsgEditValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgEditValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") - } - return nil -} - -//______________________________________________________________________ - -// MsgDelegate - struct for bonding transactions -type MsgDelegate struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` -} - -func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Bond: bond, - } -} - -//nolint -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 := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Bond: msg.Bond, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgDelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount.Sign() != 1 { - return ErrBadBondingAmount(DefaultCodespace) - } - return nil -} - -//______________________________________________________________________ - -// MsgUnbond - struct for unbonding transactions -type MsgUnbond struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares string `json:"shares"` -} - -func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUnbond { - return MsgUnbond{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Shares: shares, - } -} - -//nolint -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 := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Shares string `json:"shares"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Shares: msg.Shares, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgUnbond) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Shares != "MAX" { - rat, err := sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return ErrBadShares(DefaultCodespace) - } - if rat.IsZero() || rat.LT(sdk.ZeroRat()) { - return ErrBadShares(DefaultCodespace) - } - } - return nil -} diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go deleted file mode 100644 index c14698d1b..000000000 --- a/x/stake/msg_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -var ( - coinPos = sdk.NewCoin("steak", 1000) - coinZero = sdk.NewCoin("steak", 0) - coinNeg = sdk.NewCoin("steak", -10000) - coinPosNotAtoms = sdk.NewCoin("foo", 10000) - coinZeroNotAtoms = sdk.NewCoin("foo", 0) - coinNegNotAtoms = sdk.NewCoin("foo", -10000) -) - -// test ValidateBasic for MsgCreateValidator -func TestMsgCreateValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, details string - validatorAddr sdk.Address - pubkey crypto.PubKey - bond sdk.Coin - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true}, - {"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true}, - {"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgEditValidator -func TestMsgEditValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, details string - validatorAddr sdk.Address - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], true}, - {"partial description", "", "", "c", "", addrs[0], true}, - {"empty description", "", "", "", "", addrs[0], false}, - {"empty address", "a", "b", "c", "d", emptyAddr, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditValidator(tc.validatorAddr, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgDelegate -func TestMsgDelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - bond sdk.Coin - expectPass bool - }{ - {"basic good", addrs[0], addrs[1], coinPos, true}, - {"self bond", addrs[0], addrs[0], coinPos, true}, - {"empty delegator", emptyAddr, addrs[0], coinPos, false}, - {"empty validator", addrs[0], emptyAddr, coinPos, false}, - {"empty bond", addrs[0], addrs[1], coinZero, false}, - {"negative bond", addrs[0], addrs[1], coinNeg, false}, - {"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgUnbond -func TestMsgUnbond(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - shares string - expectPass bool - }{ - {"max unbond", addrs[0], addrs[1], "MAX", true}, - {"decimal unbond", addrs[0], addrs[1], "0.1", true}, - {"negative decimal unbond", addrs[0], addrs[1], "-0.1", false}, - {"zero unbond", addrs[0], addrs[1], "0.0", false}, - {"invalid decimal", addrs[0], addrs[0], "sunny", false}, - {"empty delegator", emptyAddr, addrs[0], "0.1", false}, - {"empty validator", addrs[0], emptyAddr, "0.1", false}, - } - - for _, tc := range tests { - msg := NewMsgUnbond(tc.delegatorAddr, tc.validatorAddr, tc.shares) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// TODO introduce with go-amino -//func TestSerializeMsg(t *testing.T) { - -//// make sure all types construct properly -//bondAmt := 1234321 -//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - -//tests := []struct { -//tx sdk.Msg -//}{ -//{NewMsgCreateValidator(addrs[0], pks[0], bond, Description{})}, -//{NewMsgEditValidator(addrs[0], Description{})}, -//{NewMsgDelegate(addrs[0], addrs[1], bond)}, -//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, -//} - -//for i, tc := range tests { -//var tx sdk.Tx -//bs := wire.BinaryBytes(tc.tx) -//err := wire.ReadBinaryBytes(bs, &tx) -//if assert.NoError(t, err, "%d", i) { -//assert.Equal(t, tc.tx, tx, "%d", i) -//} -//} -//} diff --git a/x/stake/pool.go b/x/stake/pool.go deleted file mode 100644 index 4b65c0e8e..000000000 --- a/x/stake/pool.go +++ /dev/null @@ -1,133 +0,0 @@ -package stake - -import ( - "bytes" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Pool - dynamic parameters of the current state -type Pool struct { - LooseUnbondedTokens sdk.Int `json:"loose_unbonded_tokens"` // tokens not associated with any validator - UnbondedTokens sdk.Int `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators - UnbondingTokens sdk.Int `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool - BondedTokens sdk.Int `json:"bonded_tokens"` // reserve of bonded tokens - UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool - BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation sdk.Rat `json:"inflation"` // current annual inflation rate - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations -} - -func (p Pool) equal(p2 Pool) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) - return bytes.Equal(bz1, bz2) -} - -// initial pool for testing -func InitialPool() Pool { - return Pool{ - LooseUnbondedTokens: sdk.ZeroInt(), - BondedTokens: sdk.ZeroInt(), - UnbondingTokens: sdk.ZeroInt(), - UnbondedTokens: sdk.ZeroInt(), - BondedShares: sdk.ZeroRat(), - UnbondingShares: sdk.ZeroRat(), - UnbondedShares: sdk.ZeroRat(), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroRat(), - } -} - -//____________________________________________________________________ - -// Sum total of all staking tokens in the pool -func (p Pool) TokenSupply() sdk.Int { - return p.LooseUnbondedTokens.Add(p.UnbondedTokens).Add(p.UnbondingTokens).Add(p.BondedTokens) -} - -//____________________________________________________________________ - -// get the bond ratio of the global state -func (p Pool) bondedRatio() sdk.Rat { - if p.TokenSupply().Sign() == 1 { - return sdk.NewRatFromInt(p.BondedTokens, p.TokenSupply()) - } - return sdk.ZeroRat() -} - -// get the exchange rate of bonded token per issued share -func (p Pool) bondedShareExRate() sdk.Rat { - if p.BondedShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.BondedTokens).Quo(p.BondedShares) -} - -// get the exchange rate of unbonding tokens held in validators per issued share -func (p Pool) unbondingShareExRate() sdk.Rat { - if p.UnbondingShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.UnbondingTokens).Quo(p.UnbondingShares) -} - -// get the exchange rate of unbonded tokens held in validators per issued share -func (p Pool) unbondedShareExRate() sdk.Rat { - if p.UnbondedShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.UnbondedTokens).Quo(p.UnbondedShares) -} - -//_______________________________________________________________________ - -func (p Pool) addTokensUnbonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) - p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount) - p.UnbondedTokens = p.UnbondedTokens.Add(amount) - return p, NewUnbondedShares(issuedSharesAmount) -} - -func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.unbondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.UnbondedShares = p.UnbondedShares.Sub(shares) - p.UnbondedTokens = p.UnbondedTokens.Sub(removedTokens) - return p, removedTokens -} - -func (p Pool) addTokensUnbonding(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) - p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount) - p.UnbondingTokens = p.UnbondingTokens.Add(amount) - return p, NewUnbondingShares(issuedSharesAmount) -} - -func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.unbondingShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.UnbondingShares = p.UnbondingShares.Sub(shares) - p.UnbondingTokens = p.UnbondingTokens.Sub(removedTokens) - return p, removedTokens -} - -func (p Pool) addTokensBonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) - p.BondedShares = p.BondedShares.Add(issuedSharesAmount) - p.BondedTokens = p.BondedTokens.Add(amount) - return p, NewBondedShares(issuedSharesAmount) -} - -func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.bondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.BondedShares = p.BondedShares.Sub(shares) - p.BondedTokens = p.BondedTokens.Sub(removedTokens) - return p, removedTokens -} diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go deleted file mode 100644 index 62ecdd1a6..000000000 --- a/x/stake/pool_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestBondedRatio(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.LooseUnbondedTokens = sdk.NewInt(1) - pool.BondedTokens = sdk.NewInt(2) - - // bonded pool / total supply - require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) - - // avoids divide-by-zero - pool.LooseUnbondedTokens = sdk.NewInt(0) - pool.BondedTokens = sdk.NewInt(0) - require.Equal(t, pool.bondedRatio(), sdk.ZeroRat()) -} - -func TestBondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.BondedTokens = sdk.NewInt(3) - pool.BondedShares = sdk.NewRat(10) - - // bonded pool / bonded shares - require.Equal(t, pool.bondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.BondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.bondedShareExRate(), sdk.OneRat()) -} - -func TestUnbondingShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.UnbondingTokens = sdk.NewInt(3) - pool.UnbondingShares = sdk.NewRat(10) - - // unbonding pool / unbonding shares - require.Equal(t, pool.unbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondingShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondingShareExRate(), sdk.OneRat()) -} - -func TestUnbondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.UnbondedTokens = sdk.NewInt(3) - pool.UnbondedShares = sdk.NewRat(10) - - // unbonded pool / unbonded shares - require.Equal(t, pool.unbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat()) -} - -func TestAddTokensBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensBonded(sdk.NewInt(10)) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Add(sdk.NewInt(10))) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens))) -} - -func TestRemoveSharesBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Sub(tokensB)) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens))) -} - -func TestAddTokensUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensUnbonded(sdk.NewInt(10)) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and unbonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Add(sdk.NewInt(10))) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens))) -} - -func TestRemoveSharesUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and bonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Sub(tokensB)) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens))) -} diff --git a/x/stake/stake.go b/x/stake/stake.go new file mode 100644 index 000000000..5e4356b81 --- /dev/null +++ b/x/stake/stake.go @@ -0,0 +1,145 @@ +// nolint +package stake + +import ( + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// keeper +type Keeper = keeper.Keeper + +var NewKeeper = keeper.NewKeeper + +// types +type Validator = types.Validator +type Description = types.Description +type Delegation = types.Delegation +type UnbondingDelegation = types.UnbondingDelegation +type Redelegation = types.Redelegation +type Params = types.Params +type Pool = types.Pool +type PoolShares = types.PoolShares +type MsgCreateValidator = types.MsgCreateValidator +type MsgEditValidator = types.MsgEditValidator +type MsgDelegate = types.MsgDelegate +type MsgBeginUnbonding = types.MsgBeginUnbonding +type MsgCompleteUnbonding = types.MsgCompleteUnbonding +type MsgBeginRedelegate = types.MsgBeginRedelegate +type MsgCompleteRedelegate = types.MsgCompleteRedelegate +type GenesisState = types.GenesisState + +var ( + GetValidatorKey = keeper.GetValidatorKey + GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey + GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey + GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey + GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey + GetDelegationKey = keeper.GetDelegationKey + GetDelegationsKey = keeper.GetDelegationsKey + ParamKey = keeper.ParamKey + PoolKey = keeper.PoolKey + ValidatorsKey = keeper.ValidatorsKey + ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey + ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey + ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey + ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey + ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey + TendermintUpdatesKey = keeper.TendermintUpdatesKey + DelegationKey = keeper.DelegationKey + IntraTxCounterKey = keeper.IntraTxCounterKey + GetUBDKey = keeper.GetUBDKey + GetUBDByValIndexKey = keeper.GetUBDByValIndexKey + GetUBDsKey = keeper.GetUBDsKey + GetUBDsByValIndexKey = keeper.GetUBDsByValIndexKey + GetREDKey = keeper.GetREDKey + GetREDByValSrcIndexKey = keeper.GetREDByValSrcIndexKey + GetREDByValDstIndexKey = keeper.GetREDByValDstIndexKey + GetREDsKey = keeper.GetREDsKey + GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey + GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey + GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewUnbondedShares = types.NewUnbondedShares + NewUnbondingShares = types.NewUnbondingShares + NewBondedShares = types.NewBondedShares + NewValidator = types.NewValidator + NewDescription = types.NewDescription + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterWire = types.RegisterWire + + // messages + NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgEditValidator = types.NewMsgEditValidator + NewMsgDelegate = types.NewMsgDelegate + NewMsgBeginUnbonding = types.NewMsgBeginUnbonding + NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding + NewMsgBeginRedelegate = types.NewMsgBeginRedelegate + NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate +) + +// errors +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidValidator = types.CodeInvalidValidator + CodeInvalidDelegation = types.CodeInvalidDelegation + CodeInvalidInput = types.CodeInvalidInput + CodeValidatorJailed = types.CodeValidatorJailed + CodeUnauthorized = types.CodeUnauthorized + CodeInternal = types.CodeInternal + CodeUnknownRequest = types.CodeUnknownRequest +) + +var ( + ErrNilValidatorAddr = types.ErrNilValidatorAddr + ErrNoValidatorFound = types.ErrNoValidatorFound + ErrValidatorAlreadyExists = types.ErrValidatorAlreadyExists + ErrValidatorRevoked = types.ErrValidatorRevoked + ErrBadRemoveValidator = types.ErrBadRemoveValidator + ErrDescriptionLength = types.ErrDescriptionLength + ErrCommissionNegative = types.ErrCommissionNegative + ErrCommissionHuge = types.ErrCommissionHuge + + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrBadDenom = types.ErrBadDenom + ErrBadDelegationAmount = types.ErrBadDelegationAmount + ErrNoDelegation = types.ErrNoDelegation + ErrBadDelegatorAddr = types.ErrBadDelegatorAddr + ErrNoDelegatorForAddress = types.ErrNoDelegatorForAddress + ErrInsufficientShares = types.ErrInsufficientShares + ErrDelegationValidatorEmpty = types.ErrDelegationValidatorEmpty + ErrNotEnoughDelegationShares = types.ErrNotEnoughDelegationShares + ErrBadSharesAmount = types.ErrBadSharesAmount + ErrBadSharesPercent = types.ErrBadSharesPercent + + ErrNotMature = types.ErrNotMature + ErrNoUnbondingDelegation = types.ErrNoUnbondingDelegation + ErrNoRedelegation = types.ErrNoRedelegation + ErrBadRedelegationDst = types.ErrBadRedelegationDst + + ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven + ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven + ErrMissingSignature = types.ErrMissingSignature +) + +// tags +var ( + ActionCreateValidator = tags.ActionCreateValidator + ActionEditValidator = tags.ActionEditValidator + ActionDelegate = tags.ActionDelegate + ActionBeginUnbonding = tags.ActionBeginUnbonding + ActionCompleteUnbonding = tags.ActionCompleteUnbonding + ActionBeginRedelegation = tags.ActionBeginRedelegation + ActionCompleteRedelegation = tags.ActionCompleteRedelegation + + TagAction = tags.Action + TagSrcValidator = tags.SrcValidator + TagDstValidator = tags.DstValidator + TagDelegator = tags.Delegator + TagMoniker = tags.Moniker + TagIdentity = tags.Identity +) diff --git a/x/stake/tags/tags.go b/x/stake/tags/tags.go new file mode 100644 index 000000000..edb4eda07 --- /dev/null +++ b/x/stake/tags/tags.go @@ -0,0 +1,23 @@ +// nolint +package tags + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionCreateValidator = []byte("create-validator") + ActionEditValidator = []byte("edit-validator") + ActionDelegate = []byte("delegate") + ActionBeginUnbonding = []byte("begin-unbonding") + ActionCompleteUnbonding = []byte("complete-unbonding") + ActionBeginRedelegation = []byte("begin-redelegation") + ActionCompleteRedelegation = []byte("complete-redelegation") + + Action = types.TagAction + SrcValidator = types.TagSrcValidator + DstValidator = types.TagDstValidator + Delegator = types.TagDelegator + Moniker = "moniker" + Identity = "Identity" +) diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go new file mode 100644 index 000000000..235e1e608 --- /dev/null +++ b/x/stake/types/delegation.go @@ -0,0 +1,140 @@ +package types + +import ( + "bytes" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type Delegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + Shares sdk.Rat `json:"shares"` + Height int64 `json:"height"` // Last height bond updated +} + +// two are equal +func (d Delegation) Equal(d2 Delegation) bool { + return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && + bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && + d.Height == d2.Height && + d.Shares.Equal(d2.Shares) +} + +// ensure fulfills the sdk validator types +var _ sdk.Delegation = Delegation{} + +// nolint - for sdk.Delegation +func (d Delegation) GetDelegator() sdk.Address { return d.DelegatorAddr } +func (d Delegation) GetValidator() sdk.Address { return d.ValidatorAddr } +func (d Delegation) GetBondShares() sdk.Rat { return d.Shares } + +//Human Friendly pretty printer +func (d Delegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) + if err != nil { + return "", err + } + resp := "Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Validator: %s\n", bechVal) + resp += fmt.Sprintf("Shares: %s", d.Shares.String()) + resp += fmt.Sprintf("Height: %d", d.Height) + + return resp, nil + +} + +//__________________________________________________________________ + +// element stored to represent the passive unbonding queue +type UnbondingDelegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator + ValidatorAddr sdk.Address `json:"validator_addr"` // validator unbonding from owner addr + CreationHeight int64 `json:"creation_height"` // height which the unbonding took place + MinTime int64 `json:"min_time"` // unix time for unbonding completion + Balance sdk.Coin `json:"balance"` // atoms to receive at completion +} + +// nolint +func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +//Human Friendly pretty printer +func (d UnbondingDelegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) + if err != nil { + return "", err + } + resp := "Unbonding Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Validator: %s\n", bechVal) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Expected balance: %s", d.Balance.String()) + + return resp, nil + +} + +//__________________________________________________________________ + +// element stored to represent the passive redelegation queue +type Redelegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator + ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` // validator redelegation source owner addr + ValidatorDstAddr sdk.Address `json:"validator_dst_addr"` // validator redelegation destination owner addr + CreationHeight int64 `json:"creation_height"` // height which the redelegation took place + MinTime int64 `json:"min_time"` // unix time for redelegation completion + SharesSrc sdk.Rat `json:"shares` // amount of source shares redelegating + SharesDst sdk.Rat `json:"shares` // amount of destination shares redelegating +} + +// nolint +func (d Redelegation) Equal(d2 Redelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +//Human Friendly pretty printer +func (d Redelegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechValSrc, err := sdk.Bech32ifyAcc(d.ValidatorSrcAddr) + if err != nil { + return "", err + } + bechValDst, err := sdk.Bech32ifyAcc(d.ValidatorDstAddr) + if err != nil { + return "", err + } + resp := "Redelegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Source Validator: %s\n", bechValSrc) + resp += fmt.Sprintf("Destination Validator: %s\n", bechValDst) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Source shares: %s", d.SharesSrc.String()) + resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String()) + + return resp, nil + +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go new file mode 100644 index 000000000..622bd0e1a --- /dev/null +++ b/x/stake/types/errors.go @@ -0,0 +1,115 @@ +// nolint +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CodeType = sdk.CodeType + +const ( + DefaultCodespace sdk.CodespaceType = 4 + + CodeInvalidValidator CodeType = 101 + CodeInvalidDelegation CodeType = 102 + CodeInvalidInput CodeType = 103 + CodeValidatorJailed CodeType = 104 + CodeUnauthorized CodeType = sdk.CodeUnauthorized + CodeInternal CodeType = sdk.CodeInternal + CodeUnknownRequest CodeType = sdk.CodeUnknownRequest +) + +//validator +func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") +} +func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") +} +func ErrValidatorAlreadyExists(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator") +} +func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") +} +func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator") +} +func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error { + msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max) + return sdk.NewError(codespace, CodeInvalidValidator, msg) +} +func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive") +} +func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") +} + +// delegation +func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") +} +func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") +} +func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0") +} +func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair") +} +func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address") +} +func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation") +} +func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares") +} +func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator") +} +func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares)) +} +func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0") +} +func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1") +} + +// redelegation +func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error { + msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v", + operation, descriptor, got, min) + return sdk.NewError(codespace, CodeUnauthorized, msg) +} +func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") +} +func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") +} +func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") +} +func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") +} + +// messages +func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") +} +func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided") +} +func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") +} diff --git a/x/stake/types/genesis.go b/x/stake/types/genesis.go new file mode 100644 index 000000000..d08c6b899 --- /dev/null +++ b/x/stake/types/genesis.go @@ -0,0 +1,26 @@ +package types + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + Pool Pool `json:"pool"` + Params Params `json:"params"` + Validators []Validator `json:"validators"` + Bonds []Delegation `json:"bonds"` +} + +func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { + return GenesisState{ + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Pool: InitialPool(), + Params: DefaultParams(), + } +} diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go new file mode 100644 index 000000000..d5a568c39 --- /dev/null +++ b/x/stake/types/msg.go @@ -0,0 +1,387 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// name to idetify transaction types +const MsgType = "stake" + +//Verify interface at compile time +var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} +var _, _ sdk.Msg = &MsgBeginUnbonding{}, &MsgCompleteUnbonding{} +var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} + +//______________________________________________________________________ + +// MsgCreateValidator - struct for unbonding transactions +type MsgCreateValidator struct { + Description + ValidatorAddr sdk.Address `json:"address"` + PubKey crypto.PubKey `json:"pubkey"` + SelfDelegation sdk.Coin `json:"self_delegation"` +} + +func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, + selfDelegation sdk.Coin, description Description) MsgCreateValidator { + return MsgCreateValidator{ + Description: description, + ValidatorAddr: validatorAddr, + PubKey: pubkey, + SelfDelegation: selfDelegation, + } +} + +//nolint +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 MsgCreateValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + PubKey string `json:"pubkey"` + Bond sdk.Coin `json:"bond"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + PubKey: sdk.MustBech32ifyValPub(msg.PubKey), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCreateValidator) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.SelfDelegation.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included") + } + return nil +} + +//______________________________________________________________________ + +// MsgEditValidator - struct for editing a validator +type MsgEditValidator struct { + Description + ValidatorAddr sdk.Address `json:"address"` +} + +func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { + return MsgEditValidator{ + Description: description, + ValidatorAddr: validatorAddr, + } +} + +//nolint +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 MsgEditValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgEditValidator) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgDelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + Bond sdk.Coin `json:"bond"` +} + +func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Bond: bond, + } +} + +//nolint +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 := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + Bond sdk.Coin `json:"bond"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + Bond: msg.Bond, + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.Bond.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgBeginRedelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` + ValidatorDstAddr sdk.Address `json:"validator_dst_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginRedelegate { + + return MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginRedelegate) Type() string { return MsgType } +func (msg MsgBeginRedelegate) GetSigners() []sdk.Address { + return []sdk.Address{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgBeginRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorSrcAddr string `json:"validator_src_addr"` + ValidatorDstAddr string `json:"validator_dst_addr"` + SharesAmount string `json:"shares"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr), + ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr), + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + return nil +} + +// MsgDelegate - struct for bonding transactions +type MsgCompleteRedelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorSrcAddr sdk.Address `json:"validator_source_addr"` + ValidatorDstAddr sdk.Address `json:"validator_destination_addr"` +} + +func NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address) MsgCompleteRedelegate { + + return MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } +} + +//nolint +func (msg MsgCompleteRedelegate) Type() string { return MsgType } +func (msg MsgCompleteRedelegate) GetSigners() []sdk.Address { + return []sdk.Address{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgCompleteRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorSrcAddr string `json:"validator_src_addr"` + ValidatorDstAddr string `json:"validator_dst_addr"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr), + ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCompleteRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgBeginUnbonding - struct for unbonding transactions +type MsgBeginUnbonding struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginUnbonding(delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginUnbonding { + return MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginUnbonding) Type() string { return MsgType } +func (msg MsgBeginUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgBeginUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + SharesAmount string `json:"shares_amount"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + return nil +} + +// MsgCompleteUnbonding - struct for unbonding transactions +type MsgCompleteUnbonding struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` +} + +func NewMsgCompleteUnbonding(delegatorAddr, validatorAddr sdk.Address) MsgCompleteUnbonding { + return MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgCompleteUnbonding) Type() string { return MsgType } +func (msg MsgCompleteUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgCompleteUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_src_addr"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCompleteUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go new file mode 100644 index 000000000..e02ed8c97 --- /dev/null +++ b/x/stake/types/msg_test.go @@ -0,0 +1,226 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +var ( + coinPos = sdk.Coin{"steak", sdk.NewInt(1000)} + coinZero = sdk.Coin{"steak", sdk.NewInt(0)} + coinNeg = sdk.Coin{"steak", sdk.NewInt(-10000)} +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgCreateValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.Address + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgEditValidator +func TestMsgEditValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.Address + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, true}, + {"partial description", "", "", "c", "", addr1, true}, + {"empty description", "", "", "", "", addr1, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgEditValidator(tc.validatorAddr, description) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgDelegate +func TestMsgDelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorAddr sdk.Address + bond sdk.Coin + expectPass bool + }{ + {"basic good", addr1, addr2, coinPos, true}, + {"self bond", addr1, addr1, coinPos, true}, + {"empty delegator", emptyAddr, addr1, coinPos, false}, + {"empty validator", addr1, emptyAddr, coinPos, false}, + {"empty bond", addr1, addr2, coinZero, false}, + {"negative bond", addr1, addr2, coinNeg, false}, + } + + for _, tc := range tests { + msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorSrcAddr sdk.Address + validatorDstAddr sdk.Address + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, addr3, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, addr3, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, addr3, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, addr3, sdk.NewRat(1, 10), false}, + {"empty source validator", addr1, emptyAddr, addr3, sdk.NewRat(1, 10), false}, + {"empty destination validator", addr1, addr2, emptyAddr, sdk.NewRat(1, 10), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgCompleteRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorSrcAddr sdk.Address + validatorDstAddr sdk.Address + expectPass bool + }{ + {"regular", addr1, addr2, addr3, true}, + {"empty delegator", emptyAddr, addr1, addr3, false}, + {"empty source validator", addr1, emptyAddr, addr3, false}, + {"empty destination validator", addr1, addr2, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorAddr sdk.Address + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, sdk.NewRat(1, 10), false}, + {"empty validator", addr1, emptyAddr, sdk.NewRat(1, 10), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginUnbonding(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgCompleteUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorAddr sdk.Address + expectPass bool + }{ + {"regular", addr1, addr2, true}, + {"empty delegator", emptyAddr, addr1, false}, + {"empty validator", addr1, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteUnbonding(tc.delegatorAddr, tc.validatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// TODO introduce with go-amino +//func TestSerializeMsg(t *testing.T) { + +//// make sure all types construct properly +//bondAmt := 1234321 +//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} + +//tests := []struct { +//tx sdk.Msg +//}{ +//{NewMsgCreateValidator(addr1, pk1, bond, Description{})}, +//{NewMsgEditValidator(addr1, Description{})}, +//{NewMsgDelegate(addr1, addr2, bond)}, +//{NewMsgUnbond(addr1, addr2, strconv.Itoa(bondAmt))}, +//} + +//for i, tc := range tests { +//var tx sdk.Tx +//bs := wire.BinaryBytes(tc.tx) +//err := wire.ReadBinaryBytes(bs, &tx) +//if assert.NoError(t, err, "%d", i) { +//assert.Equal(t, tc.tx, tx, "%d", i) +//} +//} +//} diff --git a/x/stake/params.go b/x/stake/types/params.go similarity index 80% rename from x/stake/params.go rename to x/stake/types/params.go index 026bd871f..8c1b897f2 100644 --- a/x/stake/params.go +++ b/x/stake/types/params.go @@ -1,4 +1,4 @@ -package stake +package types import ( "bytes" @@ -13,13 +13,16 @@ type Params struct { InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + UnbondingTime int64 `json:"unbonding_time"` + MaxValidators uint16 `json:"max_validators"` // maximum number of validators BondDenom string `json:"bond_denom"` // bondable coin denomination } -func (p Params) equal(p2 Params) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) +// nolint +func (p Params) Equal(p2 Params) bool { + bz1 := MsgCdc.MustMarshalBinary(&p) + bz2 := MsgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } @@ -30,6 +33,7 @@ func DefaultParams() Params { InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), + UnbondingTime: 60 * 60 * 24 * 3, // 3 weeks in seconds MaxValidators: 100, BondDenom: "steak", } diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go new file mode 100644 index 000000000..cb2ad240a --- /dev/null +++ b/x/stake/types/pool.go @@ -0,0 +1,159 @@ +package types + +import ( + "bytes" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Pool - dynamic parameters of the current state +type Pool struct { + LooseTokens int64 `json:"loose_tokens"` // tokens not associated with any validator + UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators + UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool + BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens + UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool + BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation sdk.Rat `json:"inflation"` // current annual inflation rate + + DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) + + // Fee Related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations +} + +// nolint +func (p Pool) Equal(p2 Pool) bool { + bz1 := MsgCdc.MustMarshalBinary(&p) + bz2 := MsgCdc.MustMarshalBinary(&p2) + return bytes.Equal(bz1, bz2) +} + +// initial pool for testing +func InitialPool() Pool { + return Pool{ + LooseTokens: 0, + BondedTokens: 0, + UnbondingTokens: 0, + UnbondedTokens: 0, + BondedShares: sdk.ZeroRat(), + UnbondingShares: sdk.ZeroRat(), + UnbondedShares: sdk.ZeroRat(), + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + DateLastCommissionReset: 0, + PrevBondedShares: sdk.ZeroRat(), + } +} + +//____________________________________________________________________ + +// Sum total of all staking tokens in the pool +func (p Pool) TokenSupply() int64 { + return p.LooseTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens +} + +//____________________________________________________________________ + +// get the bond ratio of the global state +func (p Pool) BondedRatio() sdk.Rat { + if p.TokenSupply() > 0 { + return sdk.NewRat(p.BondedTokens, p.TokenSupply()) + } + return sdk.ZeroRat() +} + +// get the exchange rate of bonded token per issued share +func (p Pool) BondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares) +} + +// get the exchange rate of unbonding tokens held in validators per issued share +func (p Pool) UnbondingShareExRate() sdk.Rat { + if p.UnbondingShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.UnbondingTokens).Quo(p.UnbondingShares) +} + +// get the exchange rate of unbonded tokens held in validators per issued share +func (p Pool) UnbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares) +} + +//_______________________________________________________________________ + +func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondedShareExRate()) // tokens * (shares/tokens) + p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount) + p.UnbondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewUnbondedShares(issuedSharesAmount) +} + +func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.UnbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondedShares = p.UnbondedShares.Sub(shares) + p.UnbondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonded tokens negative, pool: %v", p)) + } + return p, removedTokens +} + +func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondingShareExRate()) // tokens * (shares/tokens) + p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount) + p.UnbondingTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewUnbondingShares(issuedSharesAmount) +} + +func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.UnbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondingShares = p.UnbondingShares.Sub(shares) + p.UnbondingTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonding tokens negative, pool: %v", p)) + } + return p, removedTokens +} + +func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.BondedShareExRate()) // tokens * (shares/tokens) + p.BondedShares = p.BondedShares.Add(issuedSharesAmount) + p.BondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewBondedShares(issuedSharesAmount) +} + +func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.BondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.BondedShares = p.BondedShares.Sub(shares) + p.BondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p)) + } + return p, removedTokens +} diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go new file mode 100644 index 000000000..21dd4ff4d --- /dev/null +++ b/x/stake/types/pool_test.go @@ -0,0 +1,128 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestBondedRatio(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 1 + pool.BondedTokens = 2 + + // bonded pool / total supply + require.Equal(t, pool.BondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) + + // avoids divide-by-zero + pool.LooseTokens = 0 + pool.BondedTokens = 0 + require.Equal(t, pool.BondedRatio(), sdk.ZeroRat()) +} + +func TestBondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.BondedTokens = 3 + pool.BondedShares = sdk.NewRat(10) + + // bonded pool / bonded shares + require.Equal(t, pool.BondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.BondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.BondedShareExRate(), sdk.OneRat()) +} + +func TestUnbondingShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondingTokens = 3 + pool.UnbondingShares = sdk.NewRat(10) + + // unbonding pool / unbonding shares + require.Equal(t, pool.UnbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondingShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondingShareExRate(), sdk.OneRat()) +} + +func TestUnbondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondedTokens = 3 + pool.UnbondedShares = sdk.NewRat(10) + + // unbonded pool / unbonded shares + require.Equal(t, pool.UnbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondedShareExRate(), sdk.OneRat()) +} + +func TestAddTokensBonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensBonded(10) + assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) + assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10) + + // same number of bonded shares / tokens when exchange rate is one + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestRemoveSharesBonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) + assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB) + + // same number of bonded shares / tokens when exchange rate is one + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestAddTokensUnbonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensUnbonded(10) + assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and unbonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) + assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10) + + // same number of unbonded shares / tokens when exchange rate is one + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} + +func TestRemoveSharesUnbonded(t *testing.T) { + + poolA := InitialPool() + poolA.UnbondedTokens = 10 + poolA.UnbondedShares = sdk.NewRat(10) + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) + assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and bonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB) + + // same number of unbonded shares / tokens when exchange rate is one + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} diff --git a/x/stake/shares.go b/x/stake/types/shares.go similarity index 86% rename from x/stake/shares.go rename to x/stake/types/shares.go index 81b5ff97e..09301d0db 100644 --- a/x/stake/shares.go +++ b/x/stake/types/shares.go @@ -1,4 +1,4 @@ -package stake +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -70,10 +70,10 @@ func (s PoolShares) ToUnbonded(p Pool) PoolShares { var amount sdk.Rat switch s.Status { case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.unbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr + exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr case sdk.Unbonded: amount = s.Amount @@ -86,12 +86,12 @@ func (s PoolShares) ToUnbonding(p Pool) PoolShares { var amount sdk.Rat switch s.Status { case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr case sdk.Unbonding: amount = s.Amount case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr } return NewUnbondingShares(amount) @@ -104,10 +104,10 @@ func (s PoolShares) ToBonded(p Pool) PoolShares { case sdk.Bonded: amount = s.Amount case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr } return NewUnbondedShares(amount) @@ -120,11 +120,11 @@ func (s PoolShares) ToBonded(p Pool) PoolShares { func (s PoolShares) Tokens(p Pool) sdk.Rat { switch s.Status { case sdk.Bonded: - return p.bondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + return p.BondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares case sdk.Unbonding: - return p.unbondingShareExRate().Mul(s.Amount) + return p.UnbondingShareExRate().Mul(s.Amount) case sdk.Unbonded: - return p.unbondedShareExRate().Mul(s.Amount) + return p.UnbondedShareExRate().Mul(s.Amount) default: panic("unknown share kind") } diff --git a/x/stake/types/test_common.go b/x/stake/types/test_common.go new file mode 100644 index 000000000..d98deaeb5 --- /dev/null +++ b/x/stake/types/test_common.go @@ -0,0 +1,201 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" +) + +var ( + // dummy pubkeys/addresses + pk1 = crypto.GenPrivKeyEd25519().PubKey() + pk2 = crypto.GenPrivKeyEd25519().PubKey() + pk3 = crypto.GenPrivKeyEd25519().PubKey() + addr1 = pk1.Address() + addr2 = pk2.Address() + addr3 = pk3.Address() + + emptyAddr sdk.Address + emptyPubkey crypto.PubKey +) + +//______________________________________________________________ + +// any operation that transforms staking state +// takes in RNG instance, pool, validator +// returns updated pool, updated validator, delta tokens, descriptive message +type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) + +// operation: bond or unbond a validator depending on current status +func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var msg string + var newStatus sdk.BondStatus + if val.Status() == sdk.Bonded { + msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Unbonded + + } else if val.Status() == sdk.Unbonded { + msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Bonded + } + val, pool = val.UpdateStatus(pool, newStatus) + return pool, val, 0, msg +} + +// operation: add a random number of tokens to a validator +func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + tokens := int64(r.Int31n(1000)) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + val, pool, _ = val.AddTokensFromDel(pool, tokens) + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative +} + +// operation: remove a random number of shares from a validator +func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var shares sdk.Rat + for { + shares = sdk.NewRat(int64(r.Int31n(1000))) + if shares.LT(val.DelegatorShares) { + break + } + } + + msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) + + val, pool, tokens := val.RemoveDelShares(pool, shares) + return pool, val, tokens, msg +} + +// pick a random staking operation +func RandomOperation(r *rand.Rand) Operation { + operations := []Operation{ + OpBondOrUnbond, + OpAddTokens, + OpRemoveShares, + } + r.Shuffle(len(operations), func(i, j int) { + operations[i], operations[j] = operations[j], operations[i] + }) + return operations[0] +} + +// ensure invariants that should always be true are true +func AssertInvariants(t *testing.T, msg string, + pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) { + + // total tokens conserved + require.Equal(t, + pOrig.UnbondedTokens+pOrig.BondedTokens, + pMod.UnbondedTokens+pMod.BondedTokens+tokens, + "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", + msg, + pOrig.BondedShares, pOrig.UnbondedShares, + pMod.BondedShares, pMod.UnbondedShares, + pOrig.UnbondedTokens, pOrig.BondedTokens, + pMod.UnbondedTokens, pMod.BondedTokens, tokens) + + // nonnegative bonded shares + require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), + "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // nonnegative unbonded shares + require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), + "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // nonnegative bonded ex rate + require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative BondedShareExRate: %d", + msg, pMod.BondedShareExRate().Evaluate()) + + // nonnegative unbonded ex rate + require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d", + msg, pMod.UnbondedShareExRate().Evaluate()) + + for _, vMod := range vMods { + + // nonnegative ex rate + require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", + msg, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // nonnegative poolShares + require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.PoolShares.Bonded(), + vMod.DelegatorShares, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // nonnegative delShares + require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.DelegatorShares, + vMod.PoolShares.Bonded(), + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + } + +} + +//________________________________________________________________________________ +// TODO refactor this random setup + +// generate a random validator +func randomValidator(r *rand.Rand, i int) Validator { + + poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) + delShares := sdk.NewRat(int64(r.Int31n(10000))) + + var pShares PoolShares + if r.Float64() < float64(0.5) { + pShares = NewBondedShares(poolSharesAmt) + } else { + pShares = NewUnbondedShares(poolSharesAmt) + } + return Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: pShares, + DelegatorShares: delShares, + } +} + +// generate a random staking state +func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { + pool := InitialPool() + pool.LooseTokens = 100000 + + validators := make([]Validator, numValidators) + for i := 0; i < numValidators; i++ { + validator := randomValidator(r, i) + if validator.Status() == sdk.Bonded { + pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) + pool.BondedTokens += validator.PoolShares.Bonded().Evaluate() + } else if validator.Status() == sdk.Unbonded { + pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) + pool.UnbondedTokens += validator.PoolShares.Unbonded().Evaluate() + } + validators[i] = validator + } + return pool, validators +} diff --git a/x/stake/validator.go b/x/stake/types/validator.go similarity index 86% rename from x/stake/validator.go rename to x/stake/types/validator.go index ce6e7a7eb..a9561a05f 100644 --- a/x/stake/validator.go +++ b/x/stake/types/validator.go @@ -1,14 +1,15 @@ -package stake +package types import ( "bytes" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" ) // Validator defines the total amount of bond shares and their exchange rate to @@ -40,9 +41,6 @@ type Validator struct { PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools } -// Validators - list of Validators -type Validators []Validator - // NewValidator - initialize a new validator func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Description) Validator { return Validator{ @@ -64,7 +62,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti } // only the vitals - does not check bond height of IntraTxCounter -func (v Validator) equal(c2 Validator) bool { +func (v Validator) Equal(c2 Validator) bool { return v.PubKey.Equals(c2.PubKey) && bytes.Equal(v.Owner, c2.Owner) && v.PoolShares.Equal(c2.PoolShares) && @@ -97,10 +95,47 @@ func NewDescription(moniker, identity, website, details string) Description { } } -//XXX updateDescription function which enforce limit to number of description characters +// update the description based on input +func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { + if d.Moniker == "[do-not-modify]" { + d2.Moniker = d.Moniker + } + if d.Identity == "[do-not-modify]" { + d2.Identity = d.Identity + } + if d.Website == "[do-not-modify]" { + d2.Website = d.Website + } + if d.Details == "[do-not-modify]" { + d2.Details = d.Details + } + return Description{ + Moniker: d2.Moniker, + Identity: d2.Identity, + Website: d2.Website, + Details: d2.Details, + }.EnsureLength() +} + +// ensure the length of the description +func (d Description) EnsureLength() (Description, sdk.Error) { + if len(d.Moniker) > 70 { + return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) + } + if len(d.Identity) > 3000 { + return d, ErrDescriptionLength(DefaultCodespace, "identity", len(d.Identity), 3000) + } + if len(d.Website) > 140 { + return d, ErrDescriptionLength(DefaultCodespace, "website", len(d.Website), 140) + } + if len(d.Details) > 280 { + return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) + } + return d, nil +} // abci validator from stake validator type -func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { +func (v Validator) ABCIValidator(cdc *wire.Codec) abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: v.PoolShares.Bonded().Evaluate(), @@ -109,7 +144,7 @@ func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { // abci validator from stake validator type // with zero power used for validator updates -func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { +func (v Validator) ABCIValidatorZero(cdc *wire.Codec) abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: 0, @@ -123,7 +158,7 @@ func (v Validator) Status() sdk.BondStatus { // update the location of the shares within a validator if its bond status has changed func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { - var tokens sdk.Int + var tokens int64 switch v.Status() { case sdk.Unbonded: @@ -159,8 +194,8 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, // 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, sdk.Int) { - var tokens sdk.Int +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) @@ -173,7 +208,7 @@ func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, P return v, pool, tokens } -// XXX TEST +// TODO remove should only be tokens // get the power or potential power for a validator // if bonded, the power is the BondedShares // if not bonded, the power is the amount of bonded shares which the @@ -184,10 +219,9 @@ func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { //_________________________________________________________________________________________________________ -// XXX Audit this function further to make sure it's correct // add tokens to a validator -func (v Validator) addTokensFromDel(pool Pool, - amount sdk.Int) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { +func (v Validator) AddTokensFromDel(pool Pool, + amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { exRate := v.DelegatorShareExRate(pool) // bshr/delshr @@ -212,8 +246,8 @@ func (v Validator) addTokensFromDel(pool Pool, // remove delegator shares from a validator // NOTE this function assumes the shares have already been updated for the validator status -func (v Validator) removeDelShares(pool Pool, - delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins sdk.Int) { +func (v Validator) RemoveDelShares(pool Pool, + delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { amount := v.DelegatorShareExRate(pool).Mul(delShares) eqBondedSharesToRemove := NewBondedShares(amount) diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go new file mode 100644 index 000000000..2af3ace40 --- /dev/null +++ b/x/stake/types/validator_test.go @@ -0,0 +1,232 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestAddTokensValidatorBonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) +} + +func TestAddTokensValidatorUnbonding(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) +} + +func TestAddTokensValidatorUnbonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) +} + +// TODO refactor to make simpler like the AddToken tests above +func TestRemoveDelShares(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + valA := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + poolA.BondedTokens = valA.PoolShares.Bonded().Evaluate() + poolA.BondedShares = valA.PoolShares.Bonded() + assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10)) + + // coins were created + assert.Equal(t, coinsB, int64(10)) + // pool shares were removed + assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) + // conservation of tokens + assert.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens) + + // specific case from random tests + poolShares := sdk.NewRat(5102) + delShares := sdk.NewRat(115) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + BondedShares: sdk.NewRat(248305), + UnbondedShares: sdk.NewRat(232147), + BondedTokens: 248305, + UnbondedTokens: 232147, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + shares := sdk.NewRat(29) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) + _, newPool, tokens := val.RemoveDelShares(pool, shares) + require.Equal(t, + tokens+newPool.UnbondedTokens+newPool.BondedTokens, + pool.BondedTokens+pool.UnbondedTokens, + "Tokens were not conserved: %s", msg) +} + +func TestUpdateStatus(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 100 + + val := NewValidator(addr1, pk1, Description{}) + val, pool, _ = val.AddTokensFromDel(pool, 100) + assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(0), pool.BondedTokens) + assert.Equal(t, int64(0), pool.UnbondingTokens) + assert.Equal(t, int64(100), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(0), pool.BondedTokens) + assert.Equal(t, int64(100), pool.UnbondingTokens) + assert.Equal(t, int64(0), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(100), pool.BondedTokens) + assert.Equal(t, int64(0), pool.UnbondingTokens) + assert.Equal(t, int64(0), pool.UnbondedTokens) +} + +func TestPossibleOverflow(t *testing.T) { + poolShares := sdk.NewRat(2159) + delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + LooseTokens: 100, + BondedShares: poolShares, + UnbondedShares: sdk.ZeroRat(), + BondedTokens: poolShares.Evaluate(), + UnbondedTokens: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + tokens := int64(71) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newValidator, _, _ := val.AddTokensFromDel(pool, tokens) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", + msg, newValidator.DelegatorShareExRate(pool)) +} + +// run random operations in a random order on a random single-validator state, assert invariants hold +func TestSingleValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(41)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 1) + require.Equal(t, 1, len(validatorsOrig)) + + // sanity check + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0]) + + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + require.Equal(t, 1, len(validatorsOrig), "j %v", j) + require.Equal(t, 1, len(validatorsMod), "j %v", j) + validatorsMod[0] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + } + } +} + +// run random operations in a random order on a random multi-validator state, assert invariants hold +func TestMultiValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(42)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 100) + + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + index := int(r.Int31n(int32(len(validatorsOrig)))) + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index]) + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + validatorsMod[index] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + + } + } +} diff --git a/x/stake/types/wire.go b/x/stake/types/wire.go new file mode 100644 index 000000000..86f9f5f09 --- /dev/null +++ b/x/stake/types/wire.go @@ -0,0 +1,27 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) + cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) + cdc.RegisterConcrete(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil) + cdc.RegisterConcrete(MsgCompleteUnbonding{}, "cosmos-sdk/CompleteUnbonding", nil) + cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) + cdc.RegisterConcrete(MsgCompleteRedelegate{}, "cosmos-sdk/CompleteRedelegate", nil) +} + +// generic sealed codec to be used throughout sdk +var MsgCdc *wire.Codec + +func init() { + cdc := wire.NewCodec() + RegisterWire(cdc) + wire.RegisterCrypto(cdc) + MsgCdc = cdc + //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 +} diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go deleted file mode 100644 index b9540a2fe..000000000 --- a/x/stake/validator_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package stake - -import ( - "fmt" - "math/rand" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAddTokensValidatorBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Bonded) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) -} - -func TestAddTokensValidatorUnbonding(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) -} - -func TestAddTokensValidatorUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonded) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) -} - -// TODO refactor to make simpler like the AddToken tests above -func TestRemoveShares(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - valA := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(sdk.NewRat(9)), - DelegatorShares: sdk.NewRat(9), - } - poolA.BondedTokens = valA.PoolShares.Bonded().EvaluateInt() - poolA.BondedShares = valA.PoolShares.Bonded() - assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - valB, poolB, coinsB := valA.removeDelShares(poolA, sdk.NewRat(10)) - - // coins were created - assert.Equal(t, coinsB.Int64(), int64(10)) - // pool shares were removed - assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) - // conservation of tokens - assert.Equal(t, poolB.UnbondedTokens.Add(poolB.BondedTokens).Add(coinsB), poolA.UnbondedTokens.Add(poolA.BondedTokens)) - - // specific case from random tests - poolShares := sdk.NewRat(5102) - delShares := sdk.NewRat(115) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: sdk.NewRat(248305), - UnbondedShares: sdk.NewRat(232147), - BondedTokens: sdk.NewInt(248305), - UnbondedTokens: sdk.NewInt(232147), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - shares := sdk.NewRat(29) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) - _, newPool, tokens := val.removeDelShares(pool, shares) - require.Equal(t, - tokens.Add(newPool.UnbondedTokens).Add(newPool.BondedTokens), - pool.BondedTokens.Add(pool.UnbondedTokens), - "Tokens were not conserved: %s", msg) -} - -func TestUpdateStatus(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool, _ = val.addTokensFromDel(pool, sdk.NewInt(100)) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(100), pool.UnbondedTokens.Int64()) - - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens.Int64()) - assert.Equal(t, int64(100), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondedTokens.Int64()) - - val, pool = val.UpdateStatus(pool, sdk.Bonded) - assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(100), pool.BondedTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondedTokens.Int64()) -} - -//________________________________________________________________________________ -// TODO refactor this random setup - -// generate a random validator -func randomValidator(r *rand.Rand, i int) Validator { - - poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) - delShares := sdk.NewRat(int64(r.Int31n(10000))) - - var pShares PoolShares - if r.Float64() < float64(0.5) { - pShares = NewBondedShares(poolSharesAmt) - } else { - pShares = NewUnbondedShares(poolSharesAmt) - } - return Validator{ - Owner: addrs[i], - PubKey: pks[i], - PoolShares: pShares, - DelegatorShares: delShares, - } -} - -// generate a random staking state -func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { - pool := InitialPool() - - validators := make([]Validator, numValidators) - for i := 0; i < numValidators; i++ { - validator := randomValidator(r, i) - if validator.Status() == sdk.Bonded { - pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) - pool.BondedTokens = pool.BondedTokens.Add(validator.PoolShares.Bonded().EvaluateInt()) - } else if validator.Status() == sdk.Unbonded { - pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) - pool.UnbondedTokens = pool.UnbondedTokens.Add(validator.PoolShares.Unbonded().EvaluateInt()) - } - validators[i] = validator - } - return pool, validators -} - -// any operation that transforms staking state -// takes in RNG instance, pool, validator -// returns updated pool, updated validator, delta tokens, descriptive message -type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, sdk.Int, string) - -// operation: bond or unbond a validator depending on current status -func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - var msg string - var newStatus sdk.BondStatus - if val.Status() == sdk.Bonded { - msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Unbonded - - } else if val.Status() == sdk.Unbonded { - msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Bonded - } - val, pool = val.UpdateStatus(pool, newStatus) - return pool, val, sdk.ZeroInt(), msg -} - -// operation: add a random number of tokens to a validator -func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - tokens := sdk.NewInt(int64(r.Int31n(1000))) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - val, pool, _ = val.addTokensFromDel(pool, tokens) - msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg) - return pool, val, tokens.Neg(), msg // tokens are removed so for accounting must be negative -} - -// operation: remove a random number of shares from a validator -func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - var shares sdk.Rat - for { - shares = sdk.NewRat(int64(r.Int31n(1000))) - if shares.LT(val.DelegatorShares) { - break - } - } - - msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) - - val, pool, tokens := val.removeDelShares(pool, shares) - return pool, val, tokens, msg -} - -// pick a random staking operation -func randomOperation(r *rand.Rand) Operation { - operations := []Operation{ - OpBondOrUnbond, - OpAddTokens, - OpRemoveShares, - } - r.Shuffle(len(operations), func(i, j int) { - operations[i], operations[j] = operations[j], operations[i] - }) - return operations[0] -} - -// ensure invariants that should always be true are true -func assertInvariants(t *testing.T, msg string, - pOrig Pool, cOrig Validators, pMod Pool, vMods Validators, tokens sdk.Int) { - - // total tokens conserved - require.Equal(t, - pOrig.UnbondedTokens.Add(pOrig.BondedTokens), - pMod.UnbondedTokens.Add(pMod.BondedTokens).Add(tokens), - "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", - msg, - pOrig.BondedShares, pOrig.UnbondedShares, - pMod.BondedShares, pMod.UnbondedShares, - pOrig.UnbondedTokens, pOrig.BondedTokens, - pMod.UnbondedTokens, pMod.BondedTokens, tokens) - - // nonnegative bonded shares - require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), - "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative unbonded shares - require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), - "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative bonded ex rate - require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative bondedShareExRate: %d", - msg, pMod.bondedShareExRate().Evaluate()) - - // nonnegative unbonded ex rate - require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative unbondedShareExRate: %d", - msg, pMod.unbondedShareExRate().Evaluate()) - - for _, vMod := range vMods { - - // nonnegative ex rate - require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", - msg, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative poolShares - require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.PoolShares.Bonded(), - vMod.DelegatorShares, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative delShares - require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.DelegatorShares, - vMod.PoolShares.Bonded(), - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - } - -} - -func TestPossibleOverflow(t *testing.T) { - poolShares := sdk.NewRat(2159) - delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: poolShares, - UnbondedShares: sdk.ZeroRat(), - BondedTokens: poolShares.EvaluateInt(), - UnbondedTokens: sdk.ZeroInt(), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - tokens := sdk.NewInt(71) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newValidator, _, _ := val.addTokensFromDel(pool, tokens) - - msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg) - require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", - msg, newValidator.DelegatorShareExRate(pool)) -} - -// run random operations in a random order on a random single-validator state, assert invariants hold -func TestSingleValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(41)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 1) - require.Equal(t, 1, len(validatorsOrig)) - - // sanity check - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, sdk.ZeroInt()) - - for j := 0; j < 5; j++ { - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0]) - - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - require.Equal(t, 1, len(validatorsOrig), "j %v", j) - require.Equal(t, 1, len(validatorsMod), "j %v", j) - validatorsMod[0] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - } - } -} - -// run random operations in a random order on a random multi-validator state, assert invariants hold -func TestMultiValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(42)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 100) - - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, sdk.ZeroInt()) - - for j := 0; j < 5; j++ { - index := int(r.Int31n(int32(len(validatorsOrig)))) - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[index]) - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - validatorsMod[index] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - - } - } -} diff --git a/x/stake/view_slash_keeper.go b/x/stake/view_slash_keeper.go deleted file mode 100644 index cb7d16ce9..000000000 --- a/x/stake/view_slash_keeper.go +++ /dev/null @@ -1,29 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// keeper to view information & slash validators -// will be used by governance module -type ViewSlashKeeper struct { - keeper Keeper -} - -// NewViewSlashKeeper creates a keeper restricted to -// viewing information & slashing validators -func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { - return ViewSlashKeeper{k} -} - -// load a delegator bond -func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context, - delegatorAddr sdk.Address, validatorAddr sdk.Address) (bond Delegation, found bool) { - return v.keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) -} - -// load n delegator bonds -func (v ViewSlashKeeper) GetDelegations(ctx sdk.Context, - delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - return v.keeper.GetDelegations(ctx, delegator, maxRetrieve) -} diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go deleted file mode 100644 index a65c3fb38..000000000 --- a/x/stake/view_slash_keeper_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package stake - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// tests GetDelegation, GetDelegations -func TestViewSlashBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = Validator{ - Owner: addrVals[i], - PubKey: pks[i], - PoolShares: NewUnbondedShares(sdk.NewRat(amt)), - DelegatorShares: sdk.NewRat(amt), - } - } - - // first add a validators[0] to delegate too - keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - viewSlashKeeper := NewViewSlashKeeper(keeper) - - // check the empty keeper first - _, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - keeper.updateValidator(ctx, validators[1]) - keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegation(ctx, bond1to2) - keeper.setDelegation(ctx, bond1to3) - keeper.setDelegation(ctx, bond2to1) - keeper.setDelegation(ctx, bond2to2) - keeper.setDelegation(ctx, bond2to3) - - // test all bond retrieve capabilities - resBonds := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - -} diff --git a/x/stake/wire.go b/x/stake/wire.go deleted file mode 100644 index c0b0be71f..000000000 --- a/x/stake/wire.go +++ /dev/null @@ -1,20 +0,0 @@ -package stake - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - 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 msgCdc = wire.NewCodec() - -func init() { - RegisterWire(msgCdc) - wire.RegisterCrypto(msgCdc) -} From 6018e719d22a404f91ee581fb8e671187619a6b3 Mon Sep 17 00:00:00 2001 From: Joon Date: Wed, 27 Jun 2018 10:21:12 -0700 Subject: [PATCH 19/22] Merge PR #1069: Oracle Module fix prefixstore iterator in progress fix mock validator fix NewContext add to changelog apply requests fix mock --- CHANGELOG.md | 1 + examples/democoin/mock/validator.go | 122 ++++++++++ examples/democoin/x/assoc/validator_set.go | 107 +++++++++ .../democoin/x/assoc/validator_set_test.go | 71 ++++++ examples/democoin/x/oracle/README.md | 58 +++++ examples/democoin/x/oracle/errors.go | 31 +++ examples/democoin/x/oracle/handler.go | 105 +++++++++ examples/democoin/x/oracle/keeper.go | 111 +++++++++ examples/democoin/x/oracle/keeper_keys.go | 23 ++ examples/democoin/x/oracle/oracle_test.go | 221 ++++++++++++++++++ examples/democoin/x/oracle/types.go | 33 +++ store/prefixstore.go | 12 +- 12 files changed, 894 insertions(+), 1 deletion(-) create mode 100644 examples/democoin/mock/validator.go create mode 100644 examples/democoin/x/assoc/validator_set.go create mode 100644 examples/democoin/x/assoc/validator_set_test.go create mode 100644 examples/democoin/x/oracle/README.md create mode 100644 examples/democoin/x/oracle/errors.go create mode 100644 examples/democoin/x/oracle/handler.go create mode 100644 examples/democoin/x/oracle/keeper.go create mode 100644 examples/democoin/x/oracle/keeper_keys.go create mode 100644 examples/democoin/x/oracle/oracle_test.go create mode 100644 examples/democoin/x/oracle/types.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a60d162ee..2c55365cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ FEATURES * [tests] Add WaitForNextNBlocksTM helper method * [types] Switches internal representation of Int/Uint/Rat to use pointers * [gaiad] unsafe_reset_all now resets addrbook.json +* [democoin] add x/oracle, x/assoc FIXES * [gaia] Added self delegation for validators in the genesis creation diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go new file mode 100644 index 000000000..29cdd8b16 --- /dev/null +++ b/examples/democoin/mock/validator.go @@ -0,0 +1,122 @@ +package mock + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/go-crypto" +) + +// Validator implements sdk.Validator +type Validator struct { + Address sdk.Address + Power sdk.Rat +} + +// Implements sdk.Validator +func (v Validator) GetStatus() sdk.BondStatus { + return sdk.Bonded +} + +// Implements sdk.Validator +func (v Validator) GetOwner() sdk.Address { + return v.Address +} + +// Implements sdk.Validator +func (v Validator) GetPubKey() crypto.PubKey { + return nil +} + +// Implements sdk.Validator +func (v Validator) GetPower() sdk.Rat { + return v.Power +} + +// Implements sdk.Validator +func (v Validator) GetDelegatorShares() sdk.Rat { + return sdk.ZeroRat() +} + +// Implements sdk.Validator +func (v Validator) GetBondHeight() int64 { + return 0 +} + +// Implements sdk.Validator +func (v Validator) GetMoniker() string { + return "" +} + +// Implements sdk.Validator +type ValidatorSet struct { + Validators []Validator +} + +// IterateValidators implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { + for i, val := range vs.Validators { + if fn(int64(i), val) { + break + } + } +} + +// IterateValidatorsBonded implements sdk.ValidatorSet +func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { + vs.IterateValidators(ctx, fn) +} + +// Validator implements sdk.ValidatorSet +func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { + for _, val := range vs.Validators { + if bytes.Equal(val.Address, addr) { + return val + } + } + return nil +} + +// TotalPower implements sdk.ValidatorSet +func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Rat { + res := sdk.ZeroRat() + for _, val := range vs.Validators { + res = res.Add(val.Power) + } + return res +} + +// Helper function for adding new validator +func (vs *ValidatorSet) AddValidator(val Validator) { + vs.Validators = append(vs.Validators, val) +} + +// Helper function for removing exsting validator +func (vs *ValidatorSet) RemoveValidator(addr sdk.Address) { + pos := -1 + for i, val := range vs.Validators { + if bytes.Equal(val.Address, addr) { + pos = i + break + } + } + if pos == -1 { + return + } + vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...) +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, amt sdk.Rat) { + panic("not implemented") +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { + panic("not implemented") +} + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + panic("not implemented") +} diff --git a/examples/democoin/x/assoc/validator_set.go b/examples/democoin/x/assoc/validator_set.go new file mode 100644 index 000000000..019ac8795 --- /dev/null +++ b/examples/democoin/x/assoc/validator_set.go @@ -0,0 +1,107 @@ +package assoc + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +// ValidatorSet defines +type ValidatorSet struct { + sdk.ValidatorSet + + key sdk.KVStoreGetter + cdc *wire.Codec + + maxAssoc int + addrLen int +} + +var _ sdk.ValidatorSet = ValidatorSet{} + +// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet +func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { + if maxAssoc < 0 || addrLen < 0 { + panic("Cannot use negative integer for NewValidatorSet") + } + return ValidatorSet{ + ValidatorSet: valset, + + key: key, + cdc: cdc, + + maxAssoc: maxAssoc, + addrLen: addrLen, + } +} + +// Implements sdk.ValidatorSet +func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) (res sdk.Validator) { + store := valset.key.KVStore(ctx) + base := store.Get(GetBaseKey(addr)) + res = valset.ValidatorSet.Validator(ctx, base) + if res == nil { + res = valset.ValidatorSet.Validator(ctx, addr) + } + return +} + +// GetBaseKey :: sdk.Address -> sdk.Address +func GetBaseKey(addr sdk.Address) []byte { + return append([]byte{0x00}, addr...) +} + +// GetAssocPrefix :: sdk.Address -> (sdk.Address -> byte) +func GetAssocPrefix(base sdk.Address) []byte { + return append([]byte{0x01}, base...) +} + +// GetAssocKey :: (sdk.Address, sdk.Address) -> byte +func GetAssocKey(base sdk.Address, assoc sdk.Address) []byte { + return append(append([]byte{0x01}, base...), assoc...) +} + +// Associate associates new address with validator address +func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool { + if len(base) != valset.addrLen || len(assoc) != valset.addrLen { + return false + } + store := valset.key.KVStore(ctx) + // If someone already owns the associated address + if store.Get(GetBaseKey(assoc)) != nil { + return false + } + store.Set(GetBaseKey(assoc), base) + store.Set(GetAssocKey(base, assoc), []byte{0x00}) + return true +} + +// Dissociate removes association between addresses +func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool { + if len(base) != valset.addrLen || len(assoc) != valset.addrLen { + return false + } + store := valset.key.KVStore(ctx) + // No associated address found for given validator + if !bytes.Equal(store.Get(GetBaseKey(assoc)), base) { + return false + } + store.Delete(GetBaseKey(assoc)) + store.Delete(GetAssocKey(base, assoc)) + return true +} + +// Associations returns all associated addresses with a validator +func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.Address) (res []sdk.Address) { + store := valset.key.KVStore(ctx) + res = make([]sdk.Address, valset.maxAssoc) + iter := sdk.KVStorePrefixIterator(store, GetAssocPrefix(base)) + i := 0 + for ; iter.Valid(); iter.Next() { + key := iter.Key() + res[i] = key[len(key)-valset.addrLen:] + i++ + } + return res[:i] +} diff --git a/examples/democoin/x/assoc/validator_set_test.go b/examples/democoin/x/assoc/validator_set_test.go new file mode 100644 index 000000000..014f5650c --- /dev/null +++ b/examples/democoin/x/assoc/validator_set_test.go @@ -0,0 +1,71 @@ +package assoc + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + abci "github.com/tendermint/abci/types" + dbm "github.com/tendermint/tmlibs/db" + + "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +func defaultContext(key sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + return ctx +} + +func TestValidatorSet(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx := defaultContext(key) + + addr1 := []byte("addr1") + addr2 := []byte("addr2") + + base := &mock.ValidatorSet{[]mock.Validator{ + {addr1, sdk.NewRat(1)}, + {addr2, sdk.NewRat(2)}, + }} + + valset := NewValidatorSet(wire.NewCodec(), sdk.NewPrefixStoreGetter(key, []byte("assoc")), base, 1, 5) + + assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + assoc1 := []byte("asso1") + assoc2 := []byte("asso2") + + assert.True(t, valset.Associate(ctx, addr1, assoc1)) + assert.True(t, valset.Associate(ctx, addr2, assoc2)) + + assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1)) + assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2)) + + assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + assocs := valset.Associations(ctx, addr1) + assert.Equal(t, 1, len(assocs)) + assert.True(t, bytes.Equal(assoc1, assocs[0])) + + assert.False(t, valset.Associate(ctx, addr1, assoc2)) + assert.False(t, valset.Associate(ctx, addr2, assoc1)) + + valset.Dissociate(ctx, addr1, assoc1) + valset.Dissociate(ctx, addr2, assoc2) + + assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) + assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) + + assert.Nil(t, valset.Validator(ctx, assoc1)) + assert.Nil(t, valset.Validator(ctx, assoc2)) +} diff --git a/examples/democoin/x/oracle/README.md b/examples/democoin/x/oracle/README.md new file mode 100644 index 000000000..eec02d724 --- /dev/null +++ b/examples/democoin/x/oracle/README.md @@ -0,0 +1,58 @@ +# Oracle Module + +`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state. + +## Integration + +See `x/oracle/oracle_test.go` for the code that using Oracle + +To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended. + +```go +type MyPayload struct { + Data int + Nonce int +} +``` + +When you write a payload, its `.Type()` should return same name with your module is registered on the router. It is because `oracle.Msg` inherits `.Type()` from its embedded payload and it should be handled on the user modules. + +Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`. + +```go +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case oracle.Msg: + return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error { + switch p := p.(type) { + case MyPayload: + return handleMyPayload(ctx, keeper, p) + } + } + } + } +} +``` + +In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`. + +```go +func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { + return Keeper { + cdc: cdc, + key: key, + + // ValidatorSet to get validators infor + valset: valset, + + // The keeper will pass payload + // when more than 2/3 signed on it + supermaj: supermaj, + // The keeper will prune votes after 100 blocks from last sign + timeout: timeout, + } +} +``` + +Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events. diff --git a/examples/democoin/x/oracle/errors.go b/examples/democoin/x/oracle/errors.go new file mode 100644 index 000000000..0507ed226 --- /dev/null +++ b/examples/democoin/x/oracle/errors.go @@ -0,0 +1,31 @@ +package oracle + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Oracle errors reserve 1101-1199 +const ( + CodeNotValidator sdk.CodeType = 1101 + CodeAlreadyProcessed sdk.CodeType = 1102 + CodeAlreadySigned sdk.CodeType = 1103 + CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest +) + +// ---------------------------------------- +// Error constructors + +// ErrNotValidator called when the signer of a Msg is not a validator +func ErrNotValidator(codespace sdk.CodespaceType, address sdk.Address) sdk.Error { + return sdk.NewError(codespace, CodeNotValidator, address.String()) +} + +// ErrAlreadyProcessed called when a payload is already processed +func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeAlreadyProcessed, "") +} + +// ErrAlreadySigned called when the signer is trying to double signing +func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeAlreadySigned, "") +} diff --git a/examples/democoin/x/oracle/handler.go b/examples/democoin/x/oracle/handler.go new file mode 100644 index 000000000..8b94a1894 --- /dev/null +++ b/examples/democoin/x/oracle/handler.go @@ -0,0 +1,105 @@ +package oracle + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Handler handles payload after it passes voting process +type Handler func(ctx sdk.Context, p Payload) sdk.Error + +func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info { + info.Power = info.Power.Add(val.GetPower()) + + // Return if the voted power is not bigger than required power + totalPower := valset.TotalPower(ctx) + requiredPower := totalPower.Mul(keeper.supermaj) + if !info.Power.GT(requiredPower) { + return info + } + + // Check if the validators hash has been changed during the vote process + // and recalculate voted power + hash := ctx.BlockHeader().ValidatorsHash + if !bytes.Equal(hash, info.Hash) { + info.Power = sdk.ZeroRat() + info.Hash = hash + prefix := GetSignPrefix(p, keeper.cdc) + store := keeper.key.KVStore(ctx) + iter := sdk.KVStorePrefixIterator(store, prefix) + for ; iter.Valid(); iter.Next() { + if valset.Validator(ctx, iter.Value()) != nil { + store.Delete(iter.Key()) + continue + } + info.Power = info.Power.Add(val.GetPower()) + } + if !info.Power.GT(totalPower.Mul(keeper.supermaj)) { + return info + } + } + + info.Status = Processed + return info +} + +// Handle is used by other modules to handle Msg +func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result { + valset := keeper.valset + + signer := o.Signer + payload := o.Payload + + // Check the oracle is not in process + info := keeper.Info(ctx, payload) + if info.Status != Pending { + return ErrAlreadyProcessed(codespace).Result() + } + + // Check if it is reporting timeout + now := ctx.BlockHeight() + if now > info.LastSigned+keeper.timeout { + info = Info{Status: Timeout} + keeper.setInfo(ctx, payload, info) + keeper.clearSigns(ctx, payload) + return sdk.Result{} + } + info.LastSigned = ctx.BlockHeight() + + // Check the signer is a validater + val := valset.Validator(ctx, signer) + if val == nil { + return ErrNotValidator(codespace, signer).Result() + } + + // Check double signing + if keeper.signed(ctx, payload, signer) { + return ErrAlreadySigned(codespace).Result() + } + + keeper.sign(ctx, payload, signer) + + info = keeper.update(ctx, val, valset, payload, info) + if info.Status == Processed { + info = Info{Status: Processed} + } + + keeper.setInfo(ctx, payload, info) + + if info.Status == Processed { + keeper.clearSigns(ctx, payload) + cctx, write := ctx.CacheContext() + err := h(cctx, payload) + if err != nil { + return sdk.Result{ + Code: sdk.ABCICodeOK, + Log: err.ABCILog(), + } + } + write() + + } + + return sdk.Result{} +} diff --git a/examples/democoin/x/oracle/keeper.go b/examples/democoin/x/oracle/keeper.go new file mode 100644 index 000000000..cc15ffdc8 --- /dev/null +++ b/examples/democoin/x/oracle/keeper.go @@ -0,0 +1,111 @@ +package oracle + +import ( + "github.com/cosmos/cosmos-sdk/wire" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Keeper of the oracle store +type Keeper struct { + key sdk.KVStoreGetter + cdc *wire.Codec + + valset sdk.ValidatorSet + + supermaj sdk.Rat + timeout int64 +} + +// NewKeeper constructs a new keeper +func NewKeeper(key sdk.KVStoreGetter, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { + if timeout < 0 { + panic("Timeout should not be negative") + } + + return Keeper{ + key: key, + cdc: cdc, + + valset: valset, + + supermaj: supermaj, + timeout: timeout, + } +} + +// InfoStatus - current status of an Info +type InfoStatus int8 + +// Define InfoStatus +const ( + Pending = InfoStatus(iota) + Processed + Timeout +) + +// Info for each payload +type Info struct { + Power sdk.Rat + Hash []byte + LastSigned int64 + Status InfoStatus +} + +// EmptyInfo construct an empty Info +func EmptyInfo(ctx sdk.Context) Info { + return Info{ + Power: sdk.ZeroRat(), + Hash: ctx.BlockHeader().ValidatorsHash, + LastSigned: ctx.BlockHeight(), + Status: Pending, + } +} + +// Info returns the information about a payload +func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { + store := keeper.key.KVStore(ctx) + + key := GetInfoKey(p, keeper.cdc) + bz := store.Get(key) + if bz == nil { + return EmptyInfo(ctx) + } + keeper.cdc.MustUnmarshalBinary(bz, &res) + + return +} + +func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { + store := keeper.key.KVStore(ctx) + + key := GetInfoKey(p, keeper.cdc) + bz := keeper.cdc.MustMarshalBinary(info) + store.Set(key, bz) +} + +func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.Address) { + store := keeper.key.KVStore(ctx) + + key := GetSignKey(p, signer, keeper.cdc) + store.Set(key, signer) +} + +func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.Address) bool { + store := keeper.key.KVStore(ctx) + + key := GetSignKey(p, signer, keeper.cdc) + return store.Has(key) +} + +func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) { + store := keeper.key.KVStore(ctx) + + prefix := GetSignPrefix(p, keeper.cdc) + + iter := sdk.KVStorePrefixIterator(store, prefix) + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } + iter.Close() +} diff --git a/examples/democoin/x/oracle/keeper_keys.go b/examples/democoin/x/oracle/keeper_keys.go new file mode 100644 index 000000000..8c62c95de --- /dev/null +++ b/examples/democoin/x/oracle/keeper_keys.go @@ -0,0 +1,23 @@ +package oracle + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +// GetInfoKey returns the key for OracleInfo +func GetInfoKey(p Payload, cdc *wire.Codec) []byte { + bz := cdc.MustMarshalBinary(p) + return append([]byte{0x00}, bz...) +} + +// GetSignPrefix returns the prefix for signs +func GetSignPrefix(p Payload, cdc *wire.Codec) []byte { + bz := cdc.MustMarshalBinary(p) + return append([]byte{0x01}, bz...) +} + +// GetSignKey returns the key for sign +func GetSignKey(p Payload, signer sdk.Address, cdc *wire.Codec) []byte { + return append(GetSignPrefix(p, cdc), signer...) +} diff --git a/examples/democoin/x/oracle/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go new file mode 100644 index 000000000..27c5aa08b --- /dev/null +++ b/examples/democoin/x/oracle/oracle_test.go @@ -0,0 +1,221 @@ +package oracle + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + dbm "github.com/tendermint/tmlibs/db" + + "github.com/cosmos/cosmos-sdk/examples/democoin/mock" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +func defaultContext(keys ...sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + for _, key := range keys { + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + } + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + return ctx +} + +type seqOracle struct { + Seq int + Nonce int +} + +func (o seqOracle) Type() string { + return "seq" +} + +func (o seqOracle) ValidateBasic() sdk.Error { + return nil +} + +func makeCodec() *wire.Codec { + var cdc = wire.NewCodec() + + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(Msg{}, "test/Oracle", nil) + + cdc.RegisterInterface((*Payload)(nil), nil) + cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil) + + return cdc +} + +func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case Msg: + return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error { + switch p := p.(type) { + case seqOracle: + return handleSeqOracle(ctx, key, p) + default: + return sdk.ErrUnknownRequest("") + } + }, ctx, msg, codespace) + default: + return sdk.ErrUnknownRequest("").Result() + } + } +} + +func getSequence(ctx sdk.Context, key sdk.StoreKey) int { + store := ctx.KVStore(key) + seqbz := store.Get([]byte("seq")) + + var seq int + if seqbz == nil { + seq = 0 + } else { + wire.NewCodec().MustUnmarshalBinary(seqbz, &seq) + } + + return seq +} + +func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { + store := ctx.KVStore(key) + + seq := getSequence(ctx, key) + if seq != o.Seq { + return sdk.NewError(sdk.CodespaceUndefined, 1, "") + } + + bz := wire.NewCodec().MustMarshalBinary(seq + 1) + store.Set([]byte("seq"), bz) + + return nil +} + +func TestOracle(t *testing.T) { + cdc := makeCodec() + + addr1 := []byte("addr1") + addr2 := []byte("addr2") + addr3 := []byte("addr3") + addr4 := []byte("addr4") + valset := &mock.ValidatorSet{[]mock.Validator{ + mock.Validator{addr1, sdk.NewRat(7)}, + mock.Validator{addr2, sdk.NewRat(7)}, + mock.Validator{addr3, sdk.NewRat(1)}, + }} + + key := sdk.NewKVStoreKey("testkey") + ctx := defaultContext(key) + + bz, err := json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + ork := NewKeeper(sdk.NewPrefixStoreGetter(key, []byte("oracle")), cdc, valset, sdk.NewRat(2, 3), 100) + h := seqHandler(ork, key, sdk.CodespaceUndefined) + + // Nonmock.Validator signed, transaction failed + msg := Msg{seqOracle{0, 0}, []byte("randomguy")} + res := h(ctx, msg) + assert.False(t, res.IsOK()) + assert.Equal(t, 0, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr1 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 0, getSequence(ctx, key)) + + // Double signed, transaction failed + res = h(ctx, msg) + assert.False(t, res.IsOK()) + assert.Equal(t, 0, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr2 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // Already processed, transaction failed + msg.Signer = addr3 + res = h(ctx, msg) + assert.False(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg = Msg{seqOracle{100, 1}, addr1} + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // More than 2/3 signed but payload is invalid + msg.Signer = addr2 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.NotEqual(t, "", res.Log) + assert.Equal(t, 1, getSequence(ctx, key)) + + // Already processed, transaction failed + msg.Signer = addr3 + res = h(ctx, msg) + assert.False(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // Should handle mock.Validator set change + valset.AddValidator(mock.Validator{addr4, sdk.NewRat(12)}) + bz, err = json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + // Less than 2/3 signed, msg not processed + msg = Msg{seqOracle{1, 2}, addr1} + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr2 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 1, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr4 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 2, getSequence(ctx, key)) + + // Should handle mock.Validator set change while oracle process is happening + msg = Msg{seqOracle{2, 3}, addr4} + + // Less than 2/3 signed, msg not processed + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 2, getSequence(ctx, key)) + + // Signed mock.Validator is kicked out + valset.RemoveValidator(addr4) + bz, err = json.Marshal(valset) + require.Nil(t, err) + ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) + + // Less than 2/3 signed, msg not processed + msg.Signer = addr1 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 2, getSequence(ctx, key)) + + // More than 2/3 signed, msg processed + msg.Signer = addr2 + res = h(ctx, msg) + assert.True(t, res.IsOK()) + assert.Equal(t, 3, getSequence(ctx, key)) +} diff --git a/examples/democoin/x/oracle/types.go b/examples/democoin/x/oracle/types.go new file mode 100644 index 000000000..041b9ab34 --- /dev/null +++ b/examples/democoin/x/oracle/types.go @@ -0,0 +1,33 @@ +package oracle + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Msg - struct for voting on payloads +type Msg struct { + Payload + Signer sdk.Address +} + +// GetSignBytes implements sdk.Msg +func (msg Msg) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return bz +} + +// GetSigners implements sdk.Msg +func (msg Msg) GetSigners() []sdk.Address { + return []sdk.Address{msg.Signer} +} + +// Payload defines inner data for actual execution +type Payload interface { + Type() string + ValidateBasic() sdk.Error +} diff --git a/store/prefixstore.go b/store/prefixstore.go index 8c0624f35..c9f124a88 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -46,14 +46,24 @@ func (s prefixStore) Prefix(prefix []byte) KVStore { // Implements KVStore func (s prefixStore) Iterator(start, end []byte) Iterator { + if end == nil { + end = sdk.PrefixEndBytes(s.prefix) + } else { + end = append(s.prefix, end...) + } return prefixIterator{ prefix: s.prefix, - iter: s.store.Iterator(start, end), + iter: s.store.Iterator(append(s.prefix, start...), end), } } // Implements KVStore func (s prefixStore) ReverseIterator(start, end []byte) Iterator { + if end == nil { + end = sdk.PrefixEndBytes(s.prefix) + } else { + end = append(s.prefix, end...) + } return prefixIterator{ prefix: s.prefix, iter: s.store.ReverseIterator(start, end), From 3dc23871247b470c7f22df7ac8b42664ff4dc81f Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 27 Jun 2018 10:39:57 -0700 Subject: [PATCH 20/22] Merge PR #1404: Add more context for errors when no address is provided --- types/account.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/account.go b/types/account.go index 00f6cc524..cbaf87beb 100644 --- a/types/account.go +++ b/types/account.go @@ -80,7 +80,7 @@ func MustBech32ifyValPub(pub crypto.PubKey) string { // create an Address from a string func GetAccAddressHex(address string) (addr Address, err error) { if len(address) == 0 { - return addr, errors.New("must use provide address") + return addr, errors.New("decoding bech32 address failed: must provide an address") } bz, err := hex.DecodeString(address) if err != nil { @@ -116,7 +116,7 @@ func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) { // create an Address from a hex string func GetValAddressHex(address string) (addr Address, err error) { if len(address) == 0 { - return addr, errors.New("must use provide address") + return addr, errors.New("decoding bech32 address failed: must provide an address") } bz, err := hex.DecodeString(address) if err != nil { @@ -152,7 +152,7 @@ func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { // decode a bytestring from a bech32-encoded string func GetFromBech32(bech32str, prefix string) ([]byte, error) { if len(bech32str) == 0 { - return nil, errors.New("must provide non-empty string") + return nil, errors.New("decoding bech32 address failed: must provide an address") } hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { From 49f421db1936c68d8405001055ff955858f15334 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 27 Jun 2018 15:33:56 -0700 Subject: [PATCH 21/22] Merge PR #1332: benchmarks: Add benchmark for block time to bank module * benchmarks: Add benchmark folder, and single benchmark for block time * Move benchmark into module * Fix merge conflict errors * Fix spelling * Add instructions to run benchmark * Update auth_app_test.go --- CHANGELOG.md | 2 + baseapp/baseapp_test.go | 2 +- x/auth/mock/auth_app_test.go | 71 +++++++++++++++++++++++++++++++---- x/auth/mock/simulate_block.go | 20 +++++++++- x/bank/app_test.go | 12 +++++- x/bank/bench_test.go | 41 ++++++++++++++++++++ 6 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 x/bank/bench_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c55365cf..7f73eaa84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ FEATURES * Supported proposal types: just binary (pass/fail) TextProposals for now * Proposals need deposits to be votable; deposits are burned if proposal fails * Delegators delegate votes to validator by default but can override (for their stake) +* Add benchmarks for signing and delivering a block with a single bank transaction + * Run with `cd x/bank && go test --bench=.` * [tools] make get_tools installs tendermint's linter, and gometalinter * [tools] Switch gometalinter to the stable version * [tools] Add checking for misspellings and for incorrectly formatted files in circle CI diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index c939996c7..dc46b38d1 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -3,7 +3,6 @@ package baseapp import ( "encoding/json" "fmt" - "github.com/cosmos/cosmos-sdk/x/bank" "os" "testing" @@ -20,6 +19,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/bank" ) func defaultLogger() log.Logger { diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go index 4442200f1..8da93c967 100644 --- a/x/auth/mock/auth_app_test.go +++ b/x/auth/mock/auth_app_test.go @@ -3,14 +3,38 @@ package mock import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/auth" + abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" ) +// A mock transaction that has a validation which can fail. +type testMsg struct { + signers []sdk.Address + positiveNum int64 +} + +// TODO: Clean this up, make it public +const msgType = "testMsg" + +func (tx testMsg) Type() string { return msgType } +func (tx testMsg) GetMsg() sdk.Msg { return tx } +func (tx testMsg) GetMemo() string { return "" } +func (tx testMsg) GetSignBytes() []byte { return nil } +func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } +func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } +func (tx testMsg) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + // test auth module messages var ( @@ -20,19 +44,50 @@ var ( addr2 = priv2.PubKey().Address() coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} - sendMsg1 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } + testMsg1 = testMsg{signers: []sdk.Address{addr1}, positiveNum: 1} ) // initialize the mock application for this module func getMockApp(t *testing.T) *App { mapp := NewApp() - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) - + mapp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) return mapp } + +func TestMsgPrivKeys(t *testing.T) { + mapp := getMockApp(t) + mapp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + // Construct some genesis bytes to reflect basecoin/types/AppAccount + // Give 77 foocoin to the first key + coins := sdk.Coins{sdk.NewCoin("foocoin", 77)} + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: coins, + } + accs := []auth.Account{acc1} + + // Construct genesis state + SetGenesis(mapp, accs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc1, res1.(*auth.BaseAccount)) + + // Run a CheckDeliver + SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{0}, true, priv1) + + // signing a SendMsg with the wrong privKey should be an auth error + mapp.BeginBlock(abci.RequestBeginBlock{}) + tx := GenTx([]sdk.Msg{testMsg1}, []int64{0}, []int64{1}, priv2) + res := mapp.Deliver(tx) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the correct priv key should still work + res = SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{1}, true, priv1) + + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), res.Code, res.Log) +} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go index e2e67b4cf..7e8f50843 100644 --- a/x/auth/mock/simulate_block.go +++ b/x/auth/mock/simulate_block.go @@ -54,6 +54,23 @@ func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyE return auth.NewStdTx(msgs, fee, sigs, memo) } +// generate a set of signed transactions a msg, that differ only by having the +// sequence numbers incremented between every transaction. +func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKeyEd25519) []auth.StdTx { + txs := make([]auth.StdTx, numToGenerate, numToGenerate) + for i := 0; i < numToGenerate; i++ { + txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) + incrementAllSequenceNumbers(initSeqNums) + } + return txs +} + +func incrementAllSequenceNumbers(initSeqNums []int64) { + for i := 0; i < len(initSeqNums); i++ { + initSeqNums[i]++ + } +} + // check a transaction result func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { tx := GenTx(msgs, accnums, seq, priv...) @@ -62,7 +79,7 @@ func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int } // simulate a block -func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { +func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) sdk.Result { // Sign the tx tx := GenTx(msgs, accnums, seq, priv...) @@ -86,4 +103,5 @@ func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnum app.EndBlock(abci.RequestEndBlock{}) app.Commit() + return res } diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 6107126b4..b38ef4a45 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -77,14 +77,22 @@ var ( // initialize the mock application for this module func getMockApp(t *testing.T) *mock.App { + mapp, err := getBenchmarkMockApp() + require.NoError(t, err) + return mapp +} + +// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking +// Any long term API support commitments do not apply to this function. +func getBenchmarkMockApp() (*mock.App, error) { mapp := mock.NewApp() RegisterWire(mapp.Cdc) coinKeeper := NewKeeper(mapp.AccountMapper) mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) - return mapp + err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + return mapp, err } func TestMsgSendWithAccounts(t *testing.T) { diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go new file mode 100644 index 000000000..97a5c4526 --- /dev/null +++ b/x/bank/bench_test.go @@ -0,0 +1,41 @@ +package bank + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + + abci "github.com/tendermint/abci/types" +) + +func BenchmarkOneBankSendTxPerBlock(b *testing.B) { + benchmarkApp, _ := getBenchmarkMockApp() + + // Add an account at genesis + acc := &auth.BaseAccount{ + Address: addr1, + // Some value conceivably higher than the benchmarks would ever go + Coins: sdk.Coins{sdk.NewCoin("foocoin", 100000000000)}, + } + accs := []auth.Account{acc} + + // Construct genesis state + mock.SetGenesis(benchmarkApp, accs) + // Precompute all txs + txs := mock.GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []int64{0}, []int64{int64(0)}, b.N, priv1) + b.ResetTimer() + // Run this with a profiler, so its easy to distinguish what time comes from + // Committing, and what time comes from Check/Deliver Tx. + for i := 0; i < b.N; i++ { + benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) + x := benchmarkApp.Check(txs[i]) + if !x.IsOK() { + panic("something is broken in checking transaction") + } + benchmarkApp.Deliver(txs[i]) + benchmarkApp.EndBlock(abci.RequestEndBlock{}) + benchmarkApp.Commit() + } +} From 9b4838d96e846b2e26e7acc7632c6ac458b056fe Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 27 Jun 2018 15:45:10 -0700 Subject: [PATCH 22/22] Merge PR #1367: Set ChainID on InitChain * Added chain-id to context in InitChain * Fix bug in test * fmt * Appease linter * updated changelog * Remove chainID hack * setCheckState in InitChain * Fix bug * Fix initialization errors in example tests * Initialize app tests with default stake genesis * fix comments --- CHANGELOG.md | 1 + baseapp/baseapp.go | 19 +++++++++++-------- baseapp/baseapp_test.go | 18 ++++++++++++++---- examples/basecoin/app/app_test.go | 13 +++++++++++++ examples/democoin/app/app_test.go | 1 + 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f73eaa84..2a2ed1de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ FIXES * Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler * Fixed bug where `democli account` didn't decode the account data correctly * \#1343 - fixed unnecessary parallelism in CI +* \#1367 - set ChainID in InitChain * \#1353 - CLI: Show pool shares fractions in human-readable format * \#1258 - printing big.rat's can no longer overflow int64 diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b1c7e5460..9c4d100cd 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -224,9 +224,6 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error { } */ - // initialize Check state - app.setCheckState(abci.Header{}) - return nil } @@ -287,12 +284,13 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp // Implements ABCI // InitChain runs the initialization logic directly on the CommitMultiStore and commits it. func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { + // Initialize the deliver state and check state with ChainID and run initChain + app.setDeliverState(abci.Header{ChainID: req.ChainId}) + app.setCheckState(abci.Header{ChainID: req.ChainId}) + if app.initChainer == nil { return } - - // Initialize the deliver state and run initChain - app.setDeliverState(abci.Header{}) app.initChainer(app.deliverState.ctx, req) // no error // NOTE: we don't commit, but BeginBlock for block 1 @@ -379,10 +377,15 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { // Initialize the DeliverTx state. // If this is the first block, it should already - // be initialized in InitChain. It may also be nil - // if this is a test and InitChain was never called. + // be initialized in InitChain. + // Otherwise app.deliverState will be nil, since it + // is reset on Commit. if app.deliverState == nil { app.setDeliverState(req.Header) + } else { + // In the first block, app.deliverState.ctx will already be initialized + // by InitChain. Context is now updated with Header information. + app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header) } if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index dc46b38d1..c208ec3b8 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -202,7 +202,15 @@ func TestInitChainer(t *testing.T) { // set initChainer and try again - should see the value app.SetInitChainer(initChainer) - app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty + app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty + + // assert that chainID is set correctly in InitChain + chainID := app.deliverState.ctx.ChainID() + assert.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain") + + chainID = app.checkState.ctx.ChainID() + assert.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") + app.Commit() res = app.Query(query) assert.Equal(t, value, res.Value) @@ -378,13 +386,15 @@ func TestSimulateTx(t *testing.T) { return ttx, nil }) + app.InitChain(abci.RequestInitChain{}) + nBlocks := 3 for blockN := 0; blockN < nBlocks; blockN++ { // block1 header.Height = int64(blockN + 1) app.BeginBlock(abci.RequestBeginBlock{Header: header}) result := app.Simulate(tx) - require.Equal(t, result.Code, sdk.ABCICodeOK) + require.Equal(t, result.Code, sdk.ABCICodeOK, result.Log) require.Equal(t, int64(80), result.GasUsed) counter-- encoded, err := json.Marshal(tx) @@ -397,8 +407,8 @@ func TestSimulateTx(t *testing.T) { require.Equal(t, queryResult.Code, uint32(sdk.ABCICodeOK)) var res sdk.Result app.cdc.MustUnmarshalBinary(queryResult.Value, &res) - require.Equal(t, sdk.ABCICodeOK, res.Code) - require.Equal(t, int64(160), res.GasUsed) + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + require.Equal(t, int64(160), res.GasUsed, res.Log) app.EndBlock(abci.RequestEndBlock{}) app.Commit() } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 23bc531c0..c3ba39b3d 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -2,7 +2,9 @@ package app import ( "os" + "fmt" "testing" + "encoding/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -12,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/stake" + gen "github.com/cosmos/cosmos-sdk/x/stake/types" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -71,6 +74,16 @@ func TestGenesis(t *testing.T) { // reload app and ensure the account is still there bapp = NewBasecoinApp(logger, db) + // Initialize stake data with default genesis state + stakedata := gen.DefaultGenesisState() + genState, err := json.Marshal(stakedata) + if err != nil { + panic(err) + } + + // InitChain with default stake data. Initializes deliverState and checkState context + bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte(fmt.Sprintf("{\"stake\": %s}", string(genState)))}) + ctx = bapp.BaseApp.NewContext(true, abci.Header{}) res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) assert.Equal(t, acc, res1) diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index 01264399a..a477ef79b 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -55,6 +55,7 @@ func TestGenesis(t *testing.T) { // reload app and ensure the account is still there bapp = NewDemocoinApp(logger, db) + bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) ctx = bapp.BaseApp.NewContext(true, abci.Header{}) res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) assert.Equal(t, acc, res1)