diff --git a/.circleci/config.yml b/.circleci/config.yml index 9efb455e1..ed1d54f2d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -326,13 +326,24 @@ jobs: GAIAD_VERSION="stable" elif [ "${CIRCLE_BRANCH}" == "develop" ]; then GAIAD_VERSION="develop" - else - GAIAD_VERSION=`/tmp/workspace/bin/gaiad version` fi docker build -t tendermint/gaia:$GAIAD_VERSION . docker login -u $DOCKER_USER -p $DOCKER_PASS docker push tendermint/gaia:$GAIAD_VERSION + docker_tagged: + <<: *linux_defaults + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: | + docker build -t tendermint/gaia:$CIRCLE_TAG . + docker login -u $DOCKER_USER -p $DOCKER_PASS + docker push tendermint/gaia:$CIRCLE_TAG + workflows: version: 2 test-suite: @@ -345,6 +356,16 @@ workflows: - develop requires: - setup_dependencies + - docker_tagged: + filters: + tags: + only: + - /^v.*/ + branches: + ignore: + - /.*/ + requires: + - setup_dependencies - macos_ci: filters: branches: diff --git a/CHANGELOG.md b/CHANGELOG.md index a0596873d..a3eeb79a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,112 @@ # Changelog +## 0.33.0 + +BREAKING CHANGES + +* Gaia REST API + * [\#3641](https://github.com/cosmos/cosmos-sdk/pull/3641) Remove the ability to use a Keybase from the REST API client: + * `password` and `generate_only` have been removed from the `base_req` object + * All txs that used to sign or use the Keybase now only generate the tx + * `keys` routes completely removed + * [\#3692](https://github.com/cosmos/cosmos-sdk/pull/3692) Update tx encoding and broadcasting endpoints: + * Remove duplicate broadcasting endpoints in favor of POST @ `/txs` + * The `Tx` field now accepts a `StdTx` and not raw tx bytes + * Move encoding endpoint to `/txs/encode` + +* Gaia + * [\#3787](https://github.com/cosmos/cosmos-sdk/pull/3787) Fork the `x/bank` module into the Gaia application with only a + modified message handler, where the modified message handler behaves the same as + the standard `x/bank` message handler except for `MsgMultiSend` that must burn + exactly 9 atoms and transfer 1 atom, and `MsgSend` is disabled. + * [\#3789](https://github.com/cosmos/cosmos-sdk/pull/3789) Update validator creation flow: + * Remove `NewMsgCreateValidatorOnBehalfOf` and corresponding business logic + * Ensure the validator address equals the delegator address during + `MsgCreateValidator#ValidateBasic` + +* SDK + * [\#3750](https://github.com/cosmos/cosmos-sdk/issues/3750) Track outstanding rewards per-validator instead of globally, + and fix the main simulation issue, which was that slashes of + re-delegations to a validator were not correctly accounted for + in fee distribution when the redelegation in question had itself + been slashed (from a fault committed by a different validator) + in the same BeginBlock. Outstanding rewards are now available + on a per-validator basis in REST. + * [\#3669](https://github.com/cosmos/cosmos-sdk/pull/3669) Ensure consistency in message naming, codec registration, and JSON + tags. + * [\#3788](https://github.com/cosmos/cosmos-sdk/pull/3788) Change order of operations for greater accuracy when calculating delegation share token value + * [\#3788](https://github.com/cosmos/cosmos-sdk/pull/3788) DecCoins.Cap -> DecCoins.Intersect + * [\#3666](https://github.com/cosmos/cosmos-sdk/pull/3666) Improve coins denom validation. + * [\#3751](https://github.com/cosmos/cosmos-sdk/pull/3751) Disable (temporarily) support for ED25519 account key pairs. + +* Tendermint + * [\#3804] Update to Tendermint `v0.31.0-dev0` + +FEATURES + +* SDK + * [\#3719](https://github.com/cosmos/cosmos-sdk/issues/3719) DBBackend can now be set at compile time. + Defaults: goleveldb. Supported: cleveldb. + +IMPROVEMENTS + +* Gaia REST API + * Update the `TxResponse` type allowing for the `Logs` result to be JSON decoded automatically. + +* Gaia CLI + * [\#3653](https://github.com/cosmos/cosmos-sdk/pull/3653) Prompt user confirmation prior to signing and broadcasting a transaction. + * [\#3670](https://github.com/cosmos/cosmos-sdk/pull/3670) CLI support for showing bech32 addresses in Ledger devices + * [\#3711](https://github.com/cosmos/cosmos-sdk/pull/3711) Update `tx sign` to use `--from` instead of the deprecated `--name` + CLI flag. + * [\#3738](https://github.com/cosmos/cosmos-sdk/pull/3738) Improve multisig UX: + * `gaiacli keys show -o json` now includes constituent pubkeys, respective weights and threshold + * `gaiacli keys show --show-multisig` now displays constituent pubkeys, respective weights and threshold + * `gaiacli tx sign --validate-signatures` now displays multisig signers with their respective weights + * [\#3730](https://github.com/cosmos/cosmos-sdk/issues/3730) Improve workflow for + `gaiad gentx` with offline public keys, by outputting stdtx file that needs to be signed. + * [\#3761](https://github.com/cosmos/cosmos-sdk/issues/3761) Querying account related information using custom querier in auth module + +* SDK + * [\#3753](https://github.com/cosmos/cosmos-sdk/issues/3753) Remove no-longer-used governance penalty parameter + * [\#3679](https://github.com/cosmos/cosmos-sdk/issues/3679) Consistent operators across Coins, DecCoins, Int, Dec + replaced: Minus->Sub Plus->Add Div->Quo + * [\#3665](https://github.com/cosmos/cosmos-sdk/pull/3665) Overhaul sdk.Uint type in preparation for Coins Int -> Uint migration. + * [\#3691](https://github.com/cosmos/cosmos-sdk/issues/3691) Cleanup error messages + * [\#3456](https://github.com/cosmos/cosmos-sdk/issues/3456) Integrate in the Int.ToDec() convenience function + * [\#3300](https://github.com/cosmos/cosmos-sdk/pull/3300) Update the spec-spec, spec file reorg, and TOC updates. + * [\#3694](https://github.com/cosmos/cosmos-sdk/pull/3694) Push tagged docker images on docker hub when tag is created. + * [\#3716](https://github.com/cosmos/cosmos-sdk/pull/3716) Update file permissions the client keys directory and contents to `0700`. + * [\#3681](https://github.com/cosmos/cosmos-sdk/issues/3681) Migrate ledger-cosmos-go from ZondaX to Cosmos organization + +* Tendermint + * [\#3699](https://github.com/cosmos/cosmos-sdk/pull/3699) Upgrade to Tendermint 0.30.1 + +BUG FIXES + +* Gaia CLI + * [\#3731](https://github.com/cosmos/cosmos-sdk/pull/3731) `keys add --interactive` bip32 passphrase regression fix + * [\#3714](https://github.com/cosmos/cosmos-sdk/issues/3714) Fix USB raw access issues with gaiacli when installed via snap + +* Gaia + * [\#3777](https://github.com/cosmso/cosmos-sdk/pull/3777) `gaiad export` no longer panics when the database is empty + * [\#3806](https://github.com/cosmos/cosmos-sdk/pull/3806) Properly return errors from a couple of struct Unmarshal functions + +* SDK + * [\#3728](https://github.com/cosmos/cosmos-sdk/issues/3728) Truncate decimal multiplication & division in distribution to ensure + no more than the collected fees / inflation are distributed + * [\#3727](https://github.com/cosmos/cosmos-sdk/issues/3727) Return on zero-length (including []byte{}) PrefixEndBytes() calls + * [\#3559](https://github.com/cosmos/cosmos-sdk/issues/3559) fix occasional failing due to non-determinism in lcd test TestBonding + where validator is unexpectedly slashed throwing off test calculations + * [\#3411](https://github.com/cosmos/cosmos-sdk/pull/3411) Include the `RequestInitChain.Time` in the block header init during + `InitChain`. + * [\#3717](https://github.com/cosmos/cosmos-sdk/pull/3717) Update the vesting specification and implementation to cap deduction from + `DelegatedVesting` by at most `DelegatedVesting`. This accounts for the case where + the undelegation amount may exceed the original delegation amount due to + truncation of undelegation tokens. + * [\#3717](https://github.com/cosmos/cosmos-sdk/pull/3717) Ignore unknown proposers in allocating rewards for proposers, in case + unbonding period was just 1 block and proposer was already deleted. + * [\#3726](https://github.com/cosmos/cosmos-sdk/pull/3724) Cap(clip) reward to remaining coins in AllocateTokens. + ## 0.32.0 BREAKING CHANGES @@ -18,19 +125,19 @@ BREAKING CHANGES IMPROVEMENTS * SDK - * [\#3311] Reconcile the `DecCoin/s` API with the `Coin/s` API. - * [\#3614] Add coin denom length checks to the coins constructors. + * [\#3311](https://github.com/cosmos/cosmos-sdk/pull/3311) Reconcile the `DecCoin/s` API with the `Coin/s` API. + * [\#3614](https://github.com/cosmos/cosmos-sdk/pull/3614) Add coin denom length checks to the coins constructors. * [\#3621](https://github.com/cosmos/cosmos-sdk/issues/3621) remove many inter-module dependancies - * [\#3601] JSON-stringify the ABCI log response which includes the log and message + * [\#3601](https://github.com/cosmos/cosmos-sdk/pull/3601) JSON-stringify the ABCI log response which includes the log and message index. - * [\#3604] Improve SDK funds related error messages and allow for unicode in + * [\#3604](https://github.com/cosmos/cosmos-sdk/pull/3604) Improve SDK funds related error messages and allow for unicode in JSON ABCI log. * [\#3620](https://github.com/cosmos/cosmos-sdk/pull/3620) Version command shows build tags - * [\#3638] Add Bcrypt benchmarks & justification of security parameter choice - * [\#3648] Add JSON struct tags to vesting accounts. + * [\#3638](https://github.com/cosmos/cosmos-sdk/pull/3638) Add Bcrypt benchmarks & justification of security parameter choice + * [\#3648](https://github.com/cosmos/cosmos-sdk/pull/3648) Add JSON struct tags to vesting accounts. * Tendermint - * [\#3618] Upgrade to Tendermint 0.30.03 + * [\#3618](https://github.com/cosmos/cosmos-sdk/pull/3618) Upgrade to Tendermint 0.30.03 BUG FIXES diff --git a/Gopkg.lock b/Gopkg.lock index 7ea599db9..42a2b21e7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -46,6 +46,14 @@ pruneopts = "UT" revision = "52158e4697b87de16ed390e1bdaf813e581008fa" +[[projects]] + digest = "1:a2b34d1436ac24a0db176f84c015d80438602eeec12f2f8358bb72749711ab52" + name = "github.com/cosmos/ledger-cosmos-go" + packages = ["."] + pruneopts = "UT" + revision = "e2f595b3b7b222e1cbe9daf89e73e4dcab02d54c" + version = "v0.9.8" + [[projects]] digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" @@ -59,8 +67,8 @@ name = "github.com/ethereum/go-ethereum" packages = ["crypto/secp256k1"] pruneopts = "UT" - revision = "7fa3509e2eaf1a4ebc12344590e5699406690f15" - version = "v1.8.22" + revision = "c942700427557e3ff6de3aaf6b916e2f056c1ec2" + version = "v1.8.23" [[projects]] digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" @@ -132,12 +140,12 @@ version = "v1.1.0" [[projects]] - branch = "master" - digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009" + digest = "1:e4f5819333ac698d294fe04dbf640f84719658d5c7ce195b10060cc37292ce79" name = "github.com/golang/snappy" packages = ["."] pruneopts = "UT" - revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" + revision = "2a8bb927dd31d8daada140a5d09578521ce5c36a" + version = "v0.0.1" [[projects]] digest = "1:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea" @@ -183,12 +191,12 @@ version = "v1.0" [[projects]] - branch = "master" - digest = "1:39b27d1381a30421f9813967a5866fba35dc1d4df43a6eefe3b7a5444cb07214" + digest = "1:a74b5a8e34ee5843cd6e65f698f3e75614f812ff170c2243425d75bc091e9af2" name = "github.com/jmhodges/levigo" packages = ["."] pruneopts = "UT" - revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" + revision = "853d788c5c416eaaee5b044570784a96c7a26975" + version = "v1.0.0" [[projects]] branch = "master" @@ -207,12 +215,12 @@ version = "v1.8.0" [[projects]] - digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" + digest = "1:3bb9c8451d199650bfd303e0068d86f135952fead374ad87c09a9b8a2cc4bd7c" name = "github.com/mattn/go-isatty" packages = ["."] pruneopts = "UT" - revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" - version = "v0.0.4" + revision = "369ecd8cea9851e459abb67eb171853e3986591e" + version = "v0.0.6" [[projects]] digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" @@ -295,16 +303,17 @@ [[projects]] branch = "master" - digest = "1:c65f369bae3dff3a0382e38f3fe4f62cdfecba59cb6429ee323b75afdd4f3ba3" + digest = "1:bfc66a1fd78dc8f14d419fd52a5a42db1d572e887ebcc80b98c1a515a8f2d837" name = "github.com/prometheus/procfs" packages = [ ".", "internal/util", + "iostats", "nfs", "xfs", ] pruneopts = "UT" - revision = "de1b801bf34b80cd00f14087dc5a994bfe0296bc" + revision = "bbced9601137e764853b2fad7ec3e2dc4c504e02" [[projects]] digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64" @@ -357,12 +366,12 @@ version = "v0.0.3" [[projects]] - digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb" + digest = "1:1b753ec16506f5864d26a28b43703c58831255059644351bbcb019b843950900" name = "github.com/spf13/jwalterweatherman" packages = ["."] pruneopts = "UT" - revision = "4a4406e478ca629068e7768fc33f3f044173c0a6" - version = "v1.0.0" + revision = "94f6ae3ed3bceceafa716478c5fbf8d29ca601a1" + version = "v1.1.0" [[projects]] digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" @@ -428,15 +437,15 @@ version = "v0.14.1" [[projects]] - digest = "1:e1cc8dd891e64aab63b0c09f2f12456cbe2cd9cbd9d96dfae3281245f05c2428" + digest = "1:1bb088f6291e5426e3874a60bca0e481a91a5633395d7e0c427ec3e49b626e7b" name = "github.com/tendermint/iavl" packages = ["."] pruneopts = "UT" - revision = "de0740903a67b624d887f9055d4c60175dcfa758" - version = "v0.12.0" + revision = "ac7c35c12e8633a1e9fd0b52a00b900b40f32cd3" + version = "v0.12.1" [[projects]] - digest = "1:89f6fe8d02b427996828fbf43720ed1297a2e92c930b98dd302767b5ad796579" + digest = "1:088088db7ae1174ce66abecbca6924e969cd05632053532ed7dadc7cc35f7885" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -502,7 +511,7 @@ "version", ] pruneopts = "UT" - revision = "v0.30.0" + revision = "v0.31.0-dev0" [[projects]] digest = "1:b73f5e117bc7c6e8fc47128f20db48a873324ad5cfeeebfc505e85c58682b5e4" @@ -512,14 +521,6 @@ revision = "302fd402163c34626286195dfa9adac758334acc" version = "v0.9.0" -[[projects]] - digest = "1:fca24169988a61ea725d1326de30910d8049fe68bcbc194d28803f9a76dda380" - name = "github.com/zondax/ledger-cosmos-go" - packages = ["."] - pruneopts = "UT" - revision = "69fdb8ce5e5b9d9c3b22b9248e117b231d4f06dd" - version = "v0.9.7" - [[projects]] digest = "1:f8e4c0b959174a1fa5946b12f1f2ac7ea5651bef20a9e4a8dac55dbffcaa6cd6" name = "github.com/zondax/ledger-go" @@ -611,7 +612,7 @@ revision = "383e8b2c3b9e36c4076b235b32537292176bae20" [[projects]] - digest = "1:9ab5a33d8cb5c120602a34d2e985ce17956a4e8c2edce7e6961568f95e40c09a" + digest = "1:cbc746de4662c66fd24a037501bd65aa0f8ad0bfca0c92576e0abb88864e3741" name = "google.golang.org/grpc" packages = [ ".", @@ -647,8 +648,8 @@ "tap", ] pruneopts = "UT" - revision = "a02b0774206b209466313a0b525d2c738fe407eb" - version = "v1.18.0" + revision = "2fdaae294f38ed9a121193c51ec99fecd3b13eb7" + version = "v1.19.0" [[projects]] digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" @@ -666,6 +667,7 @@ "github.com/bgentry/speakeasy", "github.com/btcsuite/btcd/btcec", "github.com/cosmos/go-bip39", + "github.com/cosmos/ledger-cosmos-go", "github.com/gogo/protobuf/proto", "github.com/golang/protobuf/proto", "github.com/gorilla/mux", @@ -717,7 +719,6 @@ "github.com/tendermint/tendermint/types", "github.com/tendermint/tendermint/types/time", "github.com/tendermint/tendermint/version", - "github.com/zondax/ledger-cosmos-go", "golang.org/x/crypto/bcrypt", ] solver-name = "gps-cdcl" diff --git a/Gopkg.toml b/Gopkg.toml index e24077be5..4b1fdca78 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -36,15 +36,15 @@ [[override]] name = "github.com/tendermint/iavl" - version = "v0.12.0" + version = "~v0.12.0" [[override]] name = "github.com/tendermint/tendermint" - revision = "v0.30.0" + revision = "v0.31.0-dev0" [[constraint]] - name = "github.com/zondax/ledger-cosmos-go" - version = "=v0.9.7" + name = "github.com/cosmos/ledger-cosmos-go" + version = "=v0.9.8" ## deps without releases: diff --git a/Makefile b/Makefile index 0b8c041a9..d939fde1c 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,7 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') COMMIT := $(shell git log -1 --format='%H') -BUILD_TAGS = netgo CAT := $(if $(filter $(OS),Windows_NT),type,cat) -BUILD_FLAGS = -tags "$(BUILD_TAGS)" -ldflags \ - '-X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ - -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ - -X github.com/cosmos/cosmos-sdk/version.VendorDirHash=$(shell $(CAT) vendor-deps) \ - -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(BUILD_TAGS)"' LEDGER_ENABLED ?= true GOTOOLS = \ github.com/golang/dep/cmd/dep \ @@ -16,6 +10,53 @@ GOTOOLS = \ github.com/rakyll/statik GOBIN ?= $(GOPATH)/bin +# process build tags + +build_tags = netgo +ifeq ($(LEDGER_ENABLED),true) + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif +endif + +ifeq ($(WITH_CLEVELDB),yes) + build_tags += gcc +endif +build_tags += $(BUILD_TAGS) +build_tags := $(strip $(build_tags)) + +# process linker flags + +ldflags = -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ + -X github.com/cosmos/cosmos-sdk/version.VendorDirHash=$(shell $(CAT) vendor-deps) \ + -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags)" + +ifeq ($(WITH_CLEVELDB),yes) + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=cleveldb +endif +ldflags += $(LDFLAGS) +ldflags := $(strip $(ldflags)) + +BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' + all: devtools vendor-deps install test_lint test # The below include contains the tools target. @@ -29,29 +70,6 @@ ci: devtools vendor-deps install test_cover test_lint test ######################################## ### Build/Install -ifeq ($(LEDGER_ENABLED),true) - ifeq ($(OS),Windows_NT) - GCCEXE = $(shell where gcc.exe 2> NUL) - ifeq ($(GCCEXE),) - $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - BUILD_TAGS += ledger - endif - else - UNAME_S = $(shell uname -s) - ifeq ($(UNAME_S),OpenBSD) - $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) - else - GCC = $(shell command -v gcc 2> /dev/null) - ifeq ($(GCC),) - $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - BUILD_TAGS += ledger - endif - endif - endif -endif - build: ifeq ($(OS),Windows_NT) go build $(BUILD_FLAGS) -o build/gaiad.exe ./cmd/gaia/cmd/gaiad diff --git a/PENDING.md b/PENDING.md index cd7bf241b..591311315 100644 --- a/PENDING.md +++ b/PENDING.md @@ -1,52 +1,57 @@ -## PENDING +# PENDING CHANGELOG -BREAKING CHANGES + -* Gaia REST API +## BREAKING CHANGES -* Gaia CLI +### Gaia REST API -* Gaia +### Gaia CLI -* SDK +### Gaia -* Tendermint +### SDK +### Tendermint -FEATURES + -* Gaia REST API +## FEATURES -* Gaia CLI +### Gaia REST API -* Gaia +### Gaia CLI -* SDK +### Gaia -* Tendermint +### SDK +### Tendermint -IMPROVEMENTS + -* Gaia REST API +## IMPROVEMENTS -* Gaia CLI +### Gaia REST API -* Gaia +### Gaia CLI -* SDK +### Gaia -* Tendermint +### SDK +### Tendermint -BUG FIXES + -* Gaia REST API +## BUG FIXES -* Gaia CLI +### Gaia REST API -* Gaia +### Gaia CLI -* SDK +### Gaia -* Tendermint +### SDK + +### Tendermint diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 3b61342c8..b6694a503 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -318,16 +318,17 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp // InitChain implements the ABCI interface. It runs the initialization logic // directly on the CommitMultiStore. func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { - // stash the consensus params in the cms main store and memoize if req.ConsensusParams != nil { app.setConsensusParams(req.ConsensusParams) app.storeConsensusParams(req.ConsensusParams) } - // 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}) + initHeader := abci.Header{ChainID: req.ChainId, Time: req.Time} + + // initialize the deliver state and check state with a correct header + app.setDeliverState(initHeader) + app.setCheckState(initHeader) if app.initChainer == nil { return @@ -628,15 +629,9 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con return } -type indexedABCILog struct { - MsgIndex int `json:"msg_index"` - Success bool `json:"success"` - Log string `json:"log"` -} - // runMsgs iterates through all the messages and executes them. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) { - idxlogs := make([]indexedABCILog, 0, len(msgs)) // a list of JSON-encoded logs with msg index + idxlogs := make([]sdk.ABCIMessageLog, 0, len(msgs)) // a list of JSON-encoded logs with msg index var data []byte // NOTE: we just append them all (?!) var tags sdk.Tags // also just append them all @@ -665,7 +660,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type())) tags = append(tags, msgResult.Tags...) - idxLog := indexedABCILog{MsgIndex: msgIdx, Log: msgResult.Log} + idxLog := sdk.ABCIMessageLog{MsgIndex: msgIdx, Log: msgResult.Log} // stop execution and return on first failed message if !msgResult.IsOK() { diff --git a/client/context/context.go b/client/context/context.go index 7364adbff..beffad0c4 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -53,6 +53,7 @@ type CLIContext struct { FromAddress sdk.AccAddress FromName string Indent bool + SkipConfirm bool } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -96,6 +97,7 @@ func NewCLIContext() CLIContext { FromAddress: fromAddress, FromName: fromName, Indent: viper.GetBool(client.FlagIndentResponse), + SkipConfirm: viper.GetBool(client.FlagSkipConfirmation), } } @@ -254,6 +256,7 @@ func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) { switch ctx.OutputFormat { case "text": out = []byte(toPrint.String()) + case "json": if ctx.Indent { out, err = ctx.Codec.MarshalJSONIndent(toPrint, "", " ") diff --git a/client/context/query.go b/client/context/query.go index a2fc7ff78..7f625e801 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -66,15 +66,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { return nil, errors.New("account decoder required but not provided") } - res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore) + res, err := ctx.queryAccount(address) if err != nil { return nil, err - } else if len(res) == 0 { - return nil, ErrInvalidAccount(address) } - account, err := ctx.AccDecoder(res) - if err != nil { + var account auth.Account + if err := ctx.Codec.UnmarshalJSON(res, &account); err != nil { return nil, err } @@ -117,32 +115,33 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (uint64, error) { // error is returned if it does not. func (ctx CLIContext) EnsureAccountExists() error { addr := ctx.GetFromAddress() - accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) - if err != nil { - return err - } - - if len(accountBytes) == 0 { - return ErrInvalidAccount(addr) - } - - return nil + return ctx.EnsureAccountExistsFromAddr(addr) } // EnsureAccountExistsFromAddr ensures that an account exists for a given // address. Instead of using the context's from name, a direct address is // given. An error is returned if it does not. func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { - accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) + _, err := ctx.queryAccount(addr) + return err +} + +// queryAccount queries an account using custom query endpoint of auth module +// returns an error if result is `null` otherwise account data +func (ctx CLIContext) queryAccount(addr sdk.AccAddress) ([]byte, error) { + bz, err := ctx.Codec.MarshalJSON(auth.NewQueryAccountParams(addr)) if err != nil { - return err + return nil, err } - if len(accountBytes) == 0 { - return ErrInvalidAccount(addr) + route := fmt.Sprintf("custom/%s/%s", ctx.AccountStore, auth.QueryAccount) + + res, err := ctx.QueryWithData(route, bz) + if err != nil { + return nil, err } - return nil + return res, nil } // query performs a query from a Tendermint node with the provided store name diff --git a/client/flags.go b/client/flags.go index c87a8933c..62d912908 100644 --- a/client/flags.go +++ b/client/flags.go @@ -45,6 +45,7 @@ const ( FlagSSLCertFile = "ssl-certfile" FlagSSLKeyFile = "ssl-keyfile" FlagOutputDocument = "output-document" // inspired by wget -O + FlagSkipConfirmation = "yes" ) // LineBreak can be included in a command list to provide a blank line @@ -89,9 +90,14 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT") + c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation") + // --gas can accept integers and "simulate" c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf( - "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", GasFlagAuto, DefaultGasLimit)) + "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", + GasFlagAuto, DefaultGasLimit, + )) + viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) diff --git a/client/input.go b/client/input.go index f229d7d3b..5e041ff59 100644 --- a/client/input.go +++ b/client/input.go @@ -9,7 +9,7 @@ import ( "errors" "github.com/bgentry/speakeasy" - "github.com/mattn/go-isatty" + isatty "github.com/mattn/go-isatty" ) // MinPassLength is the minimum acceptable password length @@ -90,8 +90,9 @@ func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) { for { if inputIsTty() { - fmt.Print(fmt.Sprintf("%s [y/n]:", prompt)) + fmt.Print(fmt.Sprintf("%s [Y/n]: ", prompt)) } + response, err := readLineFromBuf(buf) if err != nil { return false, err diff --git a/client/keys/add.go b/client/keys/add.go index a6f374e2a..c11316d1d 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -2,10 +2,7 @@ package keys import ( "bytes" - "encoding/json" "fmt" - "io/ioutil" - "net/http" "os" "sort" @@ -13,15 +10,13 @@ import ( "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/rest" "errors" - "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/cosmos/go-bip39" + bip39 "github.com/cosmos/go-bip39" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/multisig" @@ -147,7 +142,7 @@ func runAddCmd(_ *cobra.Command, args []string) error { } pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) - if _, err := kb.CreateOffline(name, pk); err != nil { + if _, err := kb.CreateMulti(name, pk); err != nil { return err } @@ -247,7 +242,7 @@ func runAddCmd(_ *cobra.Command, args []string) error { } } - info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, encryptPassword, account, index) + info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, encryptPassword, account, index) if err != nil { return err } @@ -268,7 +263,7 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error { switch output { case OutputFormatText: fmt.Fprintln(os.Stderr) - printKeyInfo(info, Bech32KeyOutput) + printKeyInfo(info, keys.Bech32KeyOutput) // print mnemonic unless requested not to. if showMnemonic { @@ -278,7 +273,7 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error { fmt.Fprintln(os.Stderr, mnemonic) } case OutputFormatJSON: - out, err := Bech32KeyOutput(info) + out, err := keys.Bech32KeyOutput(info) if err != nil { return err } @@ -304,191 +299,3 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error { return nil } - -///////////////////////////// -// REST - -// function to just create a new seed to display in the UI before actually persisting it in the keybase -func generateMnemonic(algo keys.SigningAlgo) string { - kb := keys.NewInMemory() - pass := app.DefaultKeyPass - name := "inmemorykey" - _, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo) - return seed -} - -// CheckAndWriteErrorResponse will check for errors and return -// a given error message when corresponding -//TODO: Move to utils/rest or similar -func CheckAndWriteErrorResponse(w http.ResponseWriter, httpErr int, err error) bool { - if err != nil { - w.WriteHeader(httpErr) - _, _ = w.Write([]byte(err.Error())) - return true - } - return false -} - -// add new key REST handler -func AddNewKeyRequestHandler(indent bool) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var kb keys.Keybase - var m AddNewKey - - kb, err := NewKeyBaseFromHomeFlag() - if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) { - return - } - - body, err := ioutil.ReadAll(r.Body) - if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) { - return - } - - err = json.Unmarshal(body, &m) - if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) { - return - } - - // Check parameters - if m.Name == "" { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName()) - return - } - if m.Password == "" { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword()) - return - } - - mnemonic := m.Mnemonic - // if mnemonic is empty, generate one - if mnemonic == "" { - mnemonic = generateMnemonic(keys.Secp256k1) - } - if !bip39.IsMnemonicValid(mnemonic) { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic()) - } - - if m.Account < 0 || m.Account > maxValidAccountValue { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber()) - return - } - - if m.Index < 0 || m.Index > maxValidIndexalue { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber()) - return - } - - _, err = kb.Get(m.Name) - if err == nil { - CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(m.Name)) - return - } - - // create account - account := uint32(m.Account) - index := uint32(m.Index) - info, err := kb.CreateAccount(m.Name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index) - if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) { - return - } - - keyOutput, err := Bech32KeyOutput(info) - if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) { - return - } - - keyOutput.Mnemonic = mnemonic - - rest.PostProcessResponse(w, cdc, keyOutput, indent) - } -} - -// Seed REST request handler -func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - algoType := vars["type"] - - // algo type defaults to secp256k1 - if algoType == "" { - algoType = "secp256k1" - } - - algo := keys.SigningAlgo(algoType) - seed := generateMnemonic(algo) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(seed)) -} - -// RecoverRequestHandler performs key recover request -func RecoverRequestHandler(indent bool) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - name := vars["name"] - var m RecoverKey - - body, err := ioutil.ReadAll(r.Body) - if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) { - return - } - - err = cdc.UnmarshalJSON(body, &m) - if CheckAndWriteErrorResponse(w, http.StatusBadRequest, err) { - return - } - - kb, err := NewKeyBaseFromHomeFlag() - CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) - - if name == "" { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingName()) - return - } - if m.Password == "" { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingPassword()) - return - } - - mnemonic := m.Mnemonic - if !bip39.IsMnemonicValid(mnemonic) { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidMnemonic()) - } - - if m.Mnemonic == "" { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errMissingMnemonic()) - return - } - - if m.Account < 0 || m.Account > maxValidAccountValue { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidAccountNumber()) - return - } - - if m.Index < 0 || m.Index > maxValidIndexalue { - CheckAndWriteErrorResponse(w, http.StatusBadRequest, errInvalidIndexNumber()) - return - } - - _, err = kb.Get(name) - if err == nil { - CheckAndWriteErrorResponse(w, http.StatusConflict, errKeyNameConflict(name)) - return - } - - account := uint32(m.Account) - index := uint32(m.Index) - - info, err := kb.CreateAccount(name, mnemonic, keys.DefaultBIP39Passphrase, m.Password, account, index) - if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) { - return - } - - keyOutput, err := Bech32KeyOutput(info) - if CheckAndWriteErrorResponse(w, http.StatusInternalServerError, err) { - return - } - - rest.PostProcessResponse(w, cdc, keyOutput, indent) - } -} diff --git a/client/keys/add_test.go b/client/keys/add_test.go index bca21393a..8a3e8a42d 100644 --- a/client/keys/add_test.go +++ b/client/keys/add_test.go @@ -2,13 +2,9 @@ package keys import ( "bufio" - "net/http" "strings" "testing" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" @@ -61,39 +57,3 @@ func Test_runAddCmdBasic(t *testing.T) { err = runAddCmd(cmd, []string{"keyname2"}) assert.NoError(t, err) } - -type MockResponseWriter struct { - dataHeaderStatus int - dataBody []byte -} - -func (MockResponseWriter) Header() http.Header { - panic("Unexpected call!") -} - -func (w *MockResponseWriter) Write(data []byte) (int, error) { - w.dataBody = append(w.dataBody, data...) - return len(data), nil -} - -func (w *MockResponseWriter) WriteHeader(statusCode int) { - w.dataHeaderStatus = statusCode -} - -func TestCheckAndWriteErrorResponse(t *testing.T) { - mockRW := MockResponseWriter{} - - mockRW.WriteHeader(100) - assert.Equal(t, 100, mockRW.dataHeaderStatus) - - detected := CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, errors.New("some ERROR")) - require.True(t, detected) - require.Equal(t, http.StatusBadRequest, mockRW.dataHeaderStatus) - require.Equal(t, "some ERROR", string(mockRW.dataBody[:])) - - mockRW = MockResponseWriter{} - detected = CheckAndWriteErrorResponse(&mockRW, http.StatusBadRequest, nil) - require.False(t, detected) - require.Equal(t, 0, mockRW.dataHeaderStatus) - require.Equal(t, "", string(mockRW.dataBody[:])) -} diff --git a/client/keys/codec_test.go b/client/keys/codec_test.go index 3af946e59..56f12471d 100644 --- a/client/keys/codec_test.go +++ b/client/keys/codec_test.go @@ -6,35 +6,37 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys" ) type testCases struct { - Keys []KeyOutput - Answers []KeyOutput + Keys []keys.KeyOutput + Answers []keys.KeyOutput JSON [][]byte } func getTestCases() testCases { return testCases{ - []KeyOutput{ - {"A", "B", "C", "D", "E"}, - {"A", "B", "C", "D", ""}, - {"", "B", "C", "D", ""}, - {"", "", "", "", ""}, + []keys.KeyOutput{ + {"A", "B", "C", "D", "E", 0, nil}, + {"A", "B", "C", "D", "", 0, nil}, + {"", "B", "C", "D", "", 0, nil}, + {"", "", "", "", "", 0, nil}, }, - make([]KeyOutput, 4), + make([]keys.KeyOutput, 4), [][]byte{ - []byte(`{"name":"A","type":"B","address":"C","pub_key":"D","mnemonic":"E"}`), - []byte(`{"name":"A","type":"B","address":"C","pub_key":"D"}`), - []byte(`{"name":"","type":"B","address":"C","pub_key":"D"}`), - []byte(`{"name":"","type":"","address":"","pub_key":""}`), + []byte(`{"name":"A","type":"B","address":"C","pubkey":"D","mnemonic":"E"}`), + []byte(`{"name":"A","type":"B","address":"C","pubkey":"D"}`), + []byte(`{"name":"","type":"B","address":"C","pubkey":"D"}`), + []byte(`{"name":"","type":"","address":"","pubkey":""}`), }, } } func TestMarshalJSON(t *testing.T) { type args struct { - o KeyOutput + o keys.KeyOutput } data := getTestCases() diff --git a/client/keys/delete.go b/client/keys/delete.go index 614bcce7c..3803a3a18 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -2,19 +2,14 @@ package keys import ( "bufio" - "encoding/json" "errors" "fmt" - "net/http" "os" "github.com/spf13/viper" - "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/spf13/cobra" ) @@ -101,51 +96,3 @@ func confirmDeletion(buf *bufio.Reader) error { } return nil } - -//////////////////////// -// REST - -// delete key request REST body -type DeleteKeyBody struct { - Password string `json:"password"` -} - -// delete key REST handler -func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - name := vars["name"] - var kb keys.Keybase - var m DeleteKeyBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(err.Error())) - return - } - - kb, err = NewKeyBaseFromHomeFlag() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - err = kb.Delete(name, m.Password, false) - if keyerror.IsErrKeyNotFound(err) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(err.Error())) - return - } else if keyerror.IsErrWrongPassword(err) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(err.Error())) - return - } else if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - w.WriteHeader(http.StatusOK) -} diff --git a/client/keys/list.go b/client/keys/list.go index 4dda3a86b..db4cf0fe2 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -1,9 +1,6 @@ package keys import ( - "net/http" - - "github.com/cosmos/cosmos-sdk/types/rest" "github.com/spf13/cobra" ) @@ -29,36 +26,3 @@ func runListCmd(cmd *cobra.Command, args []string) error { } return err } - -///////////////////////// -// REST - -// query key list REST handler -func QueryKeysRequestHandler(indent bool) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - kb, err := NewKeyBaseFromHomeFlag() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - infos, err := kb.List() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - // an empty list will be JSONized as null, but we want to keep the empty list - if len(infos) == 0 { - rest.PostProcessResponse(w, cdc, []string{}, indent) - return - } - keysOutput, err := Bech32KeysOutput(infos) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - rest.PostProcessResponse(w, cdc, keysOutput, indent) - } -} diff --git a/client/keys/root.go b/client/keys/root.go index 5b70025fd..e49b57d4a 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -1,7 +1,6 @@ package keys import ( - "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -30,14 +29,3 @@ func Commands() *cobra.Command { ) return cmd } - -// resgister REST routes -func RegisterRoutes(r *mux.Router, indent bool) { - r.HandleFunc("/keys", QueryKeysRequestHandler(indent)).Methods("GET") - r.HandleFunc("/keys", AddNewKeyRequestHandler(indent)).Methods("POST") - r.HandleFunc("/keys/seed", SeedRequestHandler).Methods("GET") - r.HandleFunc("/keys/{name}/recover", RecoverRequestHandler(indent)).Methods("POST") - r.HandleFunc("/keys/{name}", GetKeyRequestHandler(indent)).Methods("GET") - r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT") - r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE") -} diff --git a/client/keys/root_test.go b/client/keys/root_test.go index ef7671593..1cc2db425 100644 --- a/client/keys/root_test.go +++ b/client/keys/root_test.go @@ -4,8 +4,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - - "github.com/gorilla/mux" ) func TestCommands(t *testing.T) { @@ -15,8 +13,3 @@ func TestCommands(t *testing.T) { // Commands are registered assert.Equal(t, 7, len(rootCommands.Commands())) } - -func TestRegisterRoutes(t *testing.T) { - fakeRouter := mux.Router{} - RegisterRoutes(&fakeRouter, false) -} diff --git a/client/keys/show.go b/client/keys/show.go index 903dfeb19..e1d253cd3 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -1,24 +1,19 @@ package keys import ( - "fmt" - "net/http" - - "github.com/tendermint/tendermint/crypto" - - "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/cosmos/cosmos-sdk/types/rest" - "errors" + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" + + tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/libs/cli" - - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" - sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -28,36 +23,32 @@ const ( FlagPublicKey = "pubkey" // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. FlagBechPrefix = "bech" + // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. + FlagDevice = "device" + + flagMultiSigThreshold = "multisig-threshold" + flagShowMultiSig = "show-multisig" - flagMultiSigThreshold = "multisig-threshold" defaultMultiSigKeyName = "multi" ) -var _ keys.Info = (*multiSigKey)(nil) - -type multiSigKey struct { - name string - key crypto.PubKey -} - -func (m multiSigKey) GetName() string { return m.name } -func (m multiSigKey) GetType() keys.KeyType { return keys.TypeLocal } -func (m multiSigKey) GetPubKey() crypto.PubKey { return m.key } -func (m multiSigKey) GetAddress() sdk.AccAddress { return sdk.AccAddress(m.key.Address()) } - func showKeysCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "show [name]", + Use: "show [name [name...]]", Short: "Show key info for the given name", - Long: `Return public details of one local key.`, - Args: cobra.MinimumNArgs(1), - RunE: runShowCmd, + Long: `Return public details of a single local key. If multiple names are +provided, then an ephemeral multisig key will be created under the name "multi" +consisting of all the keys provided by name and multisig threshold.`, + Args: cobra.MinimumNArgs(1), + RunE: runShowCmd, } - cmd.Flags().String(FlagBechPrefix, "acc", "The Bech32 prefix encoding for a key (acc|val|cons)") - cmd.Flags().BoolP(FlagAddress, "a", false, "output the address only (overrides --output)") - cmd.Flags().BoolP(FlagPublicKey, "p", false, "output the public key only (overrides --output)") + cmd.Flags().String(FlagBechPrefix, sdk.PrefixAccount, "The Bech32 prefix encoding for a key (acc|val|cons)") + cmd.Flags().BoolP(FlagAddress, "a", false, "Output the address only (overrides --output)") + cmd.Flags().BoolP(FlagPublicKey, "p", false, "Output the public key only (overrides --output)") + cmd.Flags().BoolP(FlagDevice, "d", false, "Output the address in the device") cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") + cmd.Flags().BoolP(flagShowMultiSig, "m", false, "Output multisig pubkey constituents, threshold, and weights") return cmd } @@ -71,12 +62,13 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return err } } else { - pks := make([]crypto.PubKey, len(args)) + pks := make([]tmcrypto.PubKey, len(args)) for i, keyName := range args { info, err := GetKeyInfo(keyName) if err != nil { return err } + pks[i] = info.GetPubKey() } @@ -85,15 +77,15 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { if err != nil { return err } + multikey := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) - info = multiSigKey{ - name: defaultMultiSigKeyName, - key: multikey, - } + info = keys.NewMultiInfo(defaultMultiSigKeyName, multikey) } isShowAddr := viper.GetBool(FlagAddress) isShowPubKey := viper.GetBool(FlagPublicKey) + isShowDevice := viper.GetBool(FlagDevice) + isShowMultiSig := viper.GetBool(flagShowMultiSig) isOutputSet := false tmp := cmd.Flag(cli.OutputFlag) @@ -119,10 +111,32 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { printKeyAddress(info, bechKeyOut) case isShowPubKey: printPubKey(info, bechKeyOut) + case isShowMultiSig: + printMultiSigKeyInfo(info, bechKeyOut) default: printKeyInfo(info, bechKeyOut) } + if isShowDevice { + if isShowPubKey { + return fmt.Errorf("the device flag (-d) can only be used for addresses not pubkeys") + } + if viper.GetString(FlagBechPrefix) != "acc" { + return fmt.Errorf("the device flag (-d) can only be used for accounts") + } + // Override and show in the device + if info.GetType() != keys.TypeLedger { + return fmt.Errorf("the device flag (-d) can only be used for accounts stored in devices") + } + + hdpath, err := info.GetPath() + if err != nil { + return nil + } + + return crypto.LedgerShowAddress(*hdpath, info.GetPubKey()) + } + return nil } @@ -139,56 +153,13 @@ func validateMultisigThreshold(k, nKeys int) error { func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { - case "acc": - return Bech32KeyOutput, nil - case "val": - return Bech32ValKeyOutput, nil - case "cons": - return Bech32ConsKeyOutput, nil + case sdk.PrefixAccount: + return keys.Bech32KeyOutput, nil + case sdk.PrefixValidator: + return keys.Bech32ValKeyOutput, nil + case sdk.PrefixConsensus: + return keys.Bech32ConsKeyOutput, nil } return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix) } - -/////////////////////////// -// REST - -// get key REST handler -func GetKeyRequestHandler(indent bool) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - name := vars["name"] - bechPrefix := r.URL.Query().Get(FlagBechPrefix) - - if bechPrefix == "" { - bechPrefix = "acc" - } - - bechKeyOut, err := getBechKeyOut(bechPrefix) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(err.Error())) - return - } - - info, err := GetKeyInfo(name) - if keyerror.IsErrKeyNotFound(err) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(err.Error())) - return - } else if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - keyOutput, err := bechKeyOut(info) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - rest.PostProcessResponse(w, cdc, keyOutput, indent) - } -} diff --git a/client/keys/show_test.go b/client/keys/show_test.go index 21d3048ba..f12efb7c5 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -3,29 +3,28 @@ package keys import ( "testing" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/cli" - - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/stretchr/testify/assert" - "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/multisig" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/libs/cli" ) func Test_multiSigKey_Properties(t *testing.T) { tmpKey1 := secp256k1.GenPrivKeySecp256k1([]byte("mySecret")) - - tmp := multiSigKey{ - name: "myMultisig", - key: tmpKey1.PubKey(), - } + pk := multisig.NewPubKeyMultisigThreshold(1, []crypto.PubKey{tmpKey1.PubKey()}) + tmp := keys.NewMultiInfo("myMultisig", pk) assert.Equal(t, "myMultisig", tmp.GetName()) - assert.Equal(t, keys.TypeLocal, tmp.GetType()) - assert.Equal(t, "015ABFFB09DB738A45745A91E8C401423ECE4016", tmp.GetPubKey().Address().String()) - assert.Equal(t, "cosmos1q9dtl7cfmdec53t5t2g733qpgglvusqk6xdntl", tmp.GetAddress().String()) + assert.Equal(t, keys.TypeMulti, tmp.GetType()) + assert.Equal(t, "79BF2B5B418A85329EC2149D1854D443F56F5A9F", tmp.GetPubKey().Address().String()) + assert.Equal(t, "cosmos10xljkk6p32zn98kzzjw3s4x5g06k7k5lz6flcv", tmp.GetAddress().String()) } func Test_showKeysCmd(t *testing.T) { @@ -64,21 +63,36 @@ func Test_runShowCmd(t *testing.T) { assert.EqualError(t, err, "invalid Bech32 prefix encoding provided: ") // Now try single key - set bech to acc - viper.Set(FlagBechPrefix, "acc") + viper.Set(FlagBechPrefix, sdk.PrefixAccount) err = runShowCmd(cmd, []string{fakeKeyName1}) assert.NoError(t, err) // Now try multisig key - set bech to acc - viper.Set(FlagBechPrefix, "acc") + viper.Set(FlagBechPrefix, sdk.PrefixAccount) err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) assert.EqualError(t, err, "threshold must be a positive integer") // Now try multisig key - set bech to acc + threshold=2 - viper.Set(FlagBechPrefix, "acc") + viper.Set(FlagBechPrefix, sdk.PrefixAccount) viper.Set(flagMultiSigThreshold, 2) err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) assert.NoError(t, err) + // Now try multisig key - set bech to acc + threshold=2 + viper.Set(FlagBechPrefix, "acc") + viper.Set(FlagDevice, true) + viper.Set(flagMultiSigThreshold, 2) + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for accounts stored in devices") + + viper.Set(FlagBechPrefix, "val") + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for accounts") + + viper.Set(FlagPublicKey, true) + err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}) + assert.EqualError(t, err, "the device flag (-d) can only be used for addresses not pubkeys") + // TODO: Capture stdout and compare } @@ -119,9 +133,9 @@ func Test_getBechKeyOut(t *testing.T) { }{ {"empty", args{""}, nil, true}, {"wrong", args{"???"}, nil, true}, - {"acc", args{"acc"}, Bech32KeyOutput, false}, - {"val", args{"val"}, Bech32ValKeyOutput, false}, - {"cons", args{"cons"}, Bech32ConsKeyOutput, false}, + {"acc", args{sdk.PrefixAccount}, keys.Bech32KeyOutput, false}, + {"val", args{sdk.PrefixValidator}, keys.Bech32ValKeyOutput, false}, + {"cons", args{sdk.PrefixConsensus}, keys.Bech32ConsKeyOutput, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/client/keys/types.go b/client/keys/types.go index d4a1032c2..75ecabe83 100644 --- a/client/keys/types.go +++ b/client/keys/types.go @@ -1,13 +1,6 @@ package keys // used for outputting keys.Info over REST -type KeyOutput struct { - Name string `json:"name"` - Type string `json:"type"` - Address string `json:"address"` - PubKey string `json:"pub_key"` - Mnemonic string `json:"mnemonic,omitempty"` -} // AddNewKey request a new key type AddNewKey struct { diff --git a/client/keys/update.go b/client/keys/update.go index d3a03f22c..392286d48 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -1,18 +1,11 @@ package keys import ( - "encoding/json" "fmt" - "net/http" - - "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" ) func updateKeyCommand() *cobra.Command { @@ -52,55 +45,3 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { fmt.Println("Password successfully updated!") return nil } - -/////////////////////// -// REST - -// update key request REST body -type UpdateKeyBody struct { - NewPassword string `json:"new_password"` - OldPassword string `json:"old_password"` -} - -// update key REST handler -func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - name := vars["name"] - var kb keys.Keybase - var m UpdateKeyBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(err.Error())) - return - } - - kb, err = NewKeyBaseFromHomeFlag() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - getNewpass := func() (string, error) { return m.NewPassword, nil } - - err = kb.Update(name, m.OldPassword, getNewpass) - if keyerror.IsErrKeyNotFound(err) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(err.Error())) - return - } else if keyerror.IsErrWrongPassword(err) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(err.Error())) - return - } else if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) -} diff --git a/client/keys/utils.go b/client/keys/utils.go index 976654ba2..5391e704f 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keys" - sdk "github.com/cosmos/cosmos-sdk/types" ) // available output formats. @@ -21,7 +20,7 @@ const ( defaultKeyDBName = "keys" ) -type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error) +type bechKeyOutFn func(keyInfo keys.Info) (keys.KeyOutput, error) // GetKeyInfo returns key info for a given name. An error is returned if the // keybase cannot be retrieved or getting the info fails. @@ -90,68 +89,22 @@ func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) { return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil } -// create a list of KeyOutput in bech32 format -func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) { - kos := make([]KeyOutput, len(infos)) - for i, info := range infos { - ko, err := Bech32KeyOutput(info) - if err != nil { - return nil, err - } - kos[i] = ko - } - return kos, nil +func printKeyTextHeader() { + fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\tPUBKEY:\n") } -// create a KeyOutput in bech32 format -func Bech32KeyOutput(info keys.Info) (KeyOutput, error) { - accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) - bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey()) - if err != nil { - return KeyOutput{}, err - } - - return KeyOutput{ - Name: info.GetName(), - Type: info.GetType().String(), - Address: accAddr.String(), - PubKey: bechPubKey, - }, nil +func printMultiSigKeyTextHeader() { + fmt.Printf("WEIGHT:\tTHRESHOLD:\tADDRESS:\t\t\t\t\tPUBKEY:\n") } -// Bech32ConsKeyOutput returns key output for a consensus node's key -// information. -func Bech32ConsKeyOutput(keyInfo keys.Info) (KeyOutput, error) { - consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) - - bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) +func printMultiSigKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { + ko, err := bechKeyOut(keyInfo) if err != nil { - return KeyOutput{}, err + panic(err) } - return KeyOutput{ - Name: keyInfo.GetName(), - Type: keyInfo.GetType().String(), - Address: consAddr.String(), - PubKey: bechPubKey, - }, nil -} - -// Bech32ValKeyOutput returns key output for a validator's key information. -func Bech32ValKeyOutput(keyInfo keys.Info) (KeyOutput, error) { - valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) - - bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) - if err != nil { - return KeyOutput{}, err - } - - return KeyOutput{ - Name: keyInfo.GetName(), - Type: keyInfo.GetType().String(), - Address: valAddr.String(), - PubKey: bechPubKey, - }, nil + printMultiSigKeyTextHeader() + printMultiSigKeyOutput(ko) } func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { @@ -162,8 +115,9 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { switch viper.Get(cli.OutputFlag) { case OutputFormatText: - fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") + printKeyTextHeader() printKeyOutput(ko) + case "json": out, err := MarshalJSON(ko) if err != nil { @@ -175,29 +129,38 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { } func printInfos(infos []keys.Info) { - kos, err := Bech32KeysOutput(infos) + kos, err := keys.Bech32KeysOutput(infos) if err != nil { panic(err) } + switch viper.Get(cli.OutputFlag) { case OutputFormatText: - fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") + printKeyTextHeader() for _, ko := range kos { printKeyOutput(ko) } + case OutputFormatJSON: out, err := MarshalJSON(kos) if err != nil { panic(err) } + fmt.Println(string(out)) } } -func printKeyOutput(ko KeyOutput) { +func printKeyOutput(ko keys.KeyOutput) { fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey) } +func printMultiSigKeyOutput(ko keys.KeyOutput) { + for _, pk := range ko.PubKeys { + fmt.Printf("%d\t%d\t\t%s\t%s\n", pk.Weight, ko.Threshold, pk.Address, pk.PubKey) + } +} + func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(info) if err != nil { diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index c0fd09c64..88845a731 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/tests" @@ -23,7 +23,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" - authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" "github.com/cosmos/cosmos-sdk/x/bank" dclcommon "github.com/cosmos/cosmos-sdk/x/distribution/client/common" distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" @@ -48,119 +47,6 @@ func init() { version.Version = os.Getenv("VERSION") } -func TestSeedsAreDifferent(t *testing.T) { - kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) - require.NoError(t, err) - addr, _ := CreateAddr(t, name1, pw, kb) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) - defer cleanup() - - mnemonic1 := getKeysSeed(t, port) - mnemonic2 := getKeysSeed(t, port) - - require.NotEqual(t, mnemonic1, mnemonic2) -} - -func TestKeyRecover(t *testing.T) { - kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) - require.NoError(t, err) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) - defer cleanup() - - myName1 := "TestKeyRecover_1" - myName2 := "TestKeyRecover_2" - - mnemonic := getKeysSeed(t, port) - expectedInfo, _ := kb.CreateAccount(myName1, mnemonic, "", pw, 0, 0) - expectedAddress := expectedInfo.GetAddress().String() - expectedPubKey := sdk.MustBech32ifyAccPub(expectedInfo.GetPubKey()) - - // recover key - doRecoverKey(t, port, myName2, pw, mnemonic, 0, 0) - - keys := getKeys(t, port) - - require.Equal(t, expectedAddress, keys[0].Address) - require.Equal(t, expectedPubKey, keys[0].PubKey) -} - -func TestKeyRecoverHDPath(t *testing.T) { - kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) - require.NoError(t, err) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}, true) - defer cleanup() - - mnemonic := getKeysSeed(t, port) - - for account := uint32(0); account < 50; account += 13 { - for index := uint32(0); index < 50; index += 15 { - name1Idx := fmt.Sprintf("name1_%d_%d", account, index) - name2Idx := fmt.Sprintf("name2_%d_%d", account, index) - - expectedInfo, _ := kb.CreateAccount(name1Idx, mnemonic, "", pw, account, index) - expectedAddress := expectedInfo.GetAddress().String() - expectedPubKey := sdk.MustBech32ifyAccPub(expectedInfo.GetPubKey()) - - // recover key - doRecoverKey(t, port, name2Idx, pw, mnemonic, account, index) - - keysName2Idx := getKey(t, port, name2Idx) - - require.Equal(t, expectedAddress, keysName2Idx.Address) - require.Equal(t, expectedPubKey, keysName2Idx.PubKey) - } - } -} - -func TestKeys(t *testing.T) { - kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) - require.NoError(t, err) - addr1, _ := CreateAddr(t, name1, pw, kb) - addr1Bech32 := addr1.String() - - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr1}, true) - defer cleanup() - - // get new seed & recover key - mnemonic2 := getKeysSeed(t, port) - doRecoverKey(t, port, name2, pw, mnemonic2, 0, 0) - - // add key - mnemonic3 := mnemonic2 - resp := doKeysPost(t, port, name3, pw, mnemonic3, 0, 0) - - addr3Bech32 := resp.Address - _, err = sdk.AccAddressFromBech32(addr3Bech32) - require.NoError(t, err, "Failed to return a correct bech32 address") - - // test if created account is the correct account - expectedInfo3, _ := kb.CreateAccount(name3, mnemonic3, "", pw, 0, 0) - expectedAddress3 := sdk.AccAddress(expectedInfo3.GetPubKey().Address()).String() - require.Equal(t, expectedAddress3, addr3Bech32) - - // existing keys - require.Equal(t, name1, getKey(t, port, name1).Name, "Did not serve keys name correctly") - require.Equal(t, addr1Bech32, getKey(t, port, name1).Address, "Did not serve keys Address correctly") - require.Equal(t, name2, getKey(t, port, name2).Name, "Did not serve keys name correctly") - require.Equal(t, addr3Bech32, getKey(t, port, name2).Address, "Did not serve keys Address correctly") - require.Equal(t, name3, getKey(t, port, name3).Name, "Did not serve keys name correctly") - require.Equal(t, addr3Bech32, getKey(t, port, name3).Address, "Did not serve keys Address correctly") - - // select key - key := getKey(t, port, name3) - require.Equal(t, name3, key.Name, "Did not serve keys name correctly") - require.Equal(t, addr3Bech32, key.Address, "Did not serve keys Address correctly") - - // update key - updateKey(t, port, name3, pw, altPw, false) - - // here it should say unauthorized as we changed the password before - updateKey(t, port, name3, pw, altPw, true) - - // delete key - deleteKey(t, port, name3, altPw) -} - func TestVersion(t *testing.T) { // skip the test if the VERSION environment variable has not been set if version.Version == "" { @@ -242,7 +128,7 @@ func TestCoinSend(t *testing.T) { // query sender acc = getAccount(t, port, addr) coins := acc.GetCoins() - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, sdk.DefaultBondDenom, coins[0].Denom) require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) @@ -255,7 +141,7 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), coins2[0].Amount.Int64()) // test failure with too little gas - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "100", 0, false, false, fees) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "100", 0, false, true, fees) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) require.Nil(t, err) @@ -268,11 +154,11 @@ func TestCoinSend(t *testing.T) { require.Equal(t, http.StatusBadRequest, res.StatusCode, body) // test failure with 0 gas - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "0", 0, false, false, fees) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "0", 0, false, true, fees) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // test failure with wrong adjustment - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, client.GasFlagAuto, 0.1, false, false, fees) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, client.GasFlagAuto, 0.1, false, true, fees) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) @@ -291,7 +177,7 @@ func TestCoinSend(t *testing.T) { // run successful tx gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, gas, 1.0, false, false, fees) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, gas, 1.0, false, true, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -301,7 +187,7 @@ func TestCoinSend(t *testing.T) { require.Equal(t, uint32(0), resultTx.Code) acc = getAccount(t, port, addr) - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.Equal(t, expectedBalance.Amount.SubRaw(1), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) } @@ -316,13 +202,15 @@ func TestCoinSendAccAuto(t *testing.T) { initialBalance := acc.GetCoins() // send a transfer tx without specifying account number and sequence - res, body, _ := doTransferWithGasAccAuto(t, port, seed, name1, memo, pw, "200000", 1.0, false, false, fees) + res, body, _ := doTransferWithGasAccAuto( + t, port, seed, name1, memo, pw, addr, "200000", 1.0, false, true, fees, + ) require.Equal(t, http.StatusOK, res.StatusCode, body) // query sender acc = getAccount(t, port, addr) coins := acc.GetCoins() - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, sdk.DefaultBondDenom, coins[0].Denom) require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) @@ -336,7 +224,7 @@ func TestCoinMultiSendGenerateOnly(t *testing.T) { defer cleanup() // generate only - res, body, _ := doTransferWithGas(t, port, seed, "", memo, "", addr, "200000", 1, false, true, fees) + res, body, _ := doTransferWithGas(t, port, seed, "", memo, "", addr, "200000", 1, false, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) var stdTx auth.StdTx @@ -356,6 +244,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { require.NoError(t, err) addr, seed := CreateAddr(t, name1, pw, kb) cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() acc := getAccount(t, port, addr) @@ -371,95 +260,57 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { // generate tx gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, "", addr, gas, 1, false, true, fees) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, "", addr, gas, 1, false, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) - var msg auth.StdTx - require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) - require.Equal(t, len(msg.Msgs), 1) - require.Equal(t, msg.Msgs[0].Route(), "bank") - require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr}) - require.Equal(t, 0, len(msg.Signatures)) - require.Equal(t, memo, msg.Memo) - require.NotZero(t, msg.Fee.Gas) + var tx auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &tx)) + require.Equal(t, len(tx.Msgs), 1) + require.Equal(t, tx.Msgs[0].Route(), "bank") + require.Equal(t, tx.Msgs[0].GetSigners(), []sdk.AccAddress{addr}) + require.Equal(t, 0, len(tx.Signatures)) + require.Equal(t, memo, tx.Memo) + require.NotZero(t, tx.Fee.Gas) - gasEstimate := int64(msg.Fee.Gas) - accnum := acc.GetAccountNumber() - sequence := acc.GetSequence() - - // sign tx - var signedMsg auth.StdTx - - payload := authrest.SignBody{ - Tx: msg, - BaseReq: rest.NewBaseReq( - name1, pw, "", viper.GetString(client.FlagChainID), "", "", - accnum, sequence, nil, nil, false, false, - ), - } - json, err := cdc.MarshalJSON(payload) - require.Nil(t, err) - - res, body = Request(t, port, "POST", "/tx/sign", json) - require.Equal(t, http.StatusOK, res.StatusCode, body) - require.Nil(t, cdc.UnmarshalJSON([]byte(body), &signedMsg)) - require.Equal(t, len(msg.Msgs), len(signedMsg.Msgs)) - require.Equal(t, msg.Msgs[0].Type(), signedMsg.Msgs[0].Type()) - require.Equal(t, msg.Msgs[0].GetSigners(), signedMsg.Msgs[0].GetSigners()) - require.Equal(t, 1, len(signedMsg.Signatures)) - - // broadcast tx - broadcastPayload := struct { - Tx auth.StdTx `json:"tx"` - Return string `json:"return"` - }{Tx: signedMsg, Return: "block"} - json, err = cdc.MarshalJSON(broadcastPayload) - require.Nil(t, err) - res, body = Request(t, port, "POST", "/tx/broadcast", json) - require.Equal(t, http.StatusOK, res.StatusCode, body) + gasEstimate := int64(tx.Fee.Gas) + _, body = signAndBroadcastGenTx(t, port, name1, pw, body, acc, 1.0, false) // check if tx was committed - var resultTx sdk.TxResponse - require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx)) - require.Equal(t, uint32(0), resultTx.Code) - require.Equal(t, gasEstimate, resultTx.GasWanted) + var txResp sdk.TxResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &txResp)) + require.Equal(t, uint32(0), txResp.Code) + require.Equal(t, gasEstimate, txResp.GasWanted) } func TestEncodeTx(t *testing.T) { - // Setup kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) require.NoError(t, err) addr, seed := CreateAddr(t, name1, pw, kb) cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) defer cleanup() - // Make a transaction to test with - res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, "2", 1, false, true, fees) + res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, "2", 1, false, false, fees) var tx auth.StdTx cdc.UnmarshalJSON([]byte(body), &tx) - // Build the request - encodeReq := struct { - Tx auth.StdTx `json:"tx"` - }{Tx: tx} - encodedJSON, _ := cdc.MarshalJSON(encodeReq) - res, body = Request(t, port, "POST", "/tx/encode", encodedJSON) + req := clienttx.EncodeReq{Tx: tx} + encodedJSON, _ := cdc.MarshalJSON(req) + res, body = Request(t, port, "POST", "/txs/encode", encodedJSON) // Make sure it came back ok, and that we can decode it back to the transaction - // 200 response + // 200 response. require.Equal(t, http.StatusOK, res.StatusCode, body) encodeResp := struct { Tx string `json:"tx"` }{} - // No error decoding the JSON require.Nil(t, cdc.UnmarshalJSON([]byte(body), &encodeResp)) - // Check that the base64 decodes + // verify that the base64 decodes decodedBytes, err := base64.StdEncoding.DecodeString(encodeResp.Tx) require.Nil(t, err) - // Check that the transaction decodes as expected + // check that the transaction decodes as expected var decodedTx auth.StdTx require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx)) require.Equal(t, memo, decodedTx.Memo) @@ -562,7 +413,7 @@ func TestValidatorsQuery(t *testing.T) { foundVal = true } - require.True(t, foundVal, "pk %v, operator %v", operAddrs[0], validators[0].OperatorAddr) + require.True(t, foundVal, "pk %v, operator %v", operAddrs[0], validators[0].OperatorAddress) } func TestValidatorQuery(t *testing.T) { @@ -572,7 +423,7 @@ func TestValidatorQuery(t *testing.T) { require.Equal(t, 1, len(operAddrs)) validator := getValidator(t, port, operAddrs[0]) - require.Equal(t, validator.OperatorAddr, operAddrs[0], "The returned validator does not hold the correct data") + require.Equal(t, validator.OperatorAddress, operAddrs[0], "The returned validator does not hold the correct data") } func TestBonding(t *testing.T) { @@ -588,7 +439,7 @@ func TestBonding(t *testing.T) { require.Equal(t, 2, len(operAddrs)) amt := sdk.TokensFromTendermintPower(60) - amtDec := sdk.NewDecFromInt(amt) + amtDec := amt.ToDec() validator := getValidator(t, port, operAddrs[0]) acc := getAccount(t, port, addr) @@ -612,7 +463,7 @@ func TestBonding(t *testing.T) { // verify balance acc = getAccount(t, port, addr) coins := acc.GetCoins() - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(delTokens), coins.AmountOf(sdk.DefaultBondDenom)) expectedBalance = coins[0] @@ -630,11 +481,11 @@ func TestBonding(t *testing.T) { bondedValidators := getDelegatorValidators(t, port, addr) require.Len(t, bondedValidators, 1) - require.Equal(t, operAddrs[0], bondedValidators[0].OperatorAddr) + require.Equal(t, operAddrs[0], bondedValidators[0].OperatorAddress) require.Equal(t, validator.DelegatorShares.Add(amtDec).String(), bondedValidators[0].DelegatorShares.String()) bondedValidator := getDelegatorValidator(t, port, addr, operAddrs[0]) - require.Equal(t, operAddrs[0], bondedValidator.OperatorAddr) + require.Equal(t, operAddrs[0], bondedValidator.OperatorAddress) // testing unbonding unbondingTokens := sdk.TokensFromTendermintPower(30) @@ -646,7 +497,7 @@ func TestBonding(t *testing.T) { // sender should have not received any coins as the unbonding has only just begun acc = getAccount(t, port, addr) coins = acc.GetCoins() - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.True(t, expectedBalance.Amount.LT(coins.AmountOf(sdk.DefaultBondDenom)) || expectedBalance.Amount.Equal(coins.AmountOf(sdk.DefaultBondDenom)), @@ -664,7 +515,7 @@ func TestBonding(t *testing.T) { ubd := getUnbondingDelegation(t, port, addr, operAddrs[0]) require.Len(t, ubd.Entries, 1) - require.Equal(t, delTokens.DivRaw(2), ubd.Entries[0].Balance) + require.Equal(t, delTokens.QuoRaw(2), ubd.Entries[0].Balance) // test redelegation rdTokens := sdk.TokensFromTendermintPower(30) @@ -675,7 +526,7 @@ func TestBonding(t *testing.T) { // verify balance after paying fees acc = getAccount(t, port, addr) - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.True(t, expectedBalance.Amount.LT(coins.AmountOf(sdk.DefaultBondDenom)) || expectedBalance.Amount.Equal(coins.AmountOf(sdk.DefaultBondDenom)), @@ -692,10 +543,18 @@ func TestBonding(t *testing.T) { require.Equal(t, resultTx.Height, txs[0].Height) // query delegations, unbondings and redelegations from validator and delegator - rdShares := sdk.NewDecFromInt(rdTokens) delegatorDels = getDelegatorDelegations(t, port, addr) require.Len(t, delegatorDels, 1) - require.Equal(t, rdShares, delegatorDels[0].GetShares()) + require.Equal(t, operAddrs[1], delegatorDels[0].ValidatorAddress) + + // because the second validator never signs during these tests, if this + // this test takes a long time to run, eventually this second validator + // will get slashed, meaning that it's exchange rate is no-longer 1-to-1, + // hence we utilize the exchange rate in the following test + + validator2 := getValidator(t, port, operAddrs[1]) + delTokensAfterRedelegation := validator2.ShareTokens(delegatorDels[0].GetShares()) + require.Equal(t, rdTokens.ToDec(), delTokensAfterRedelegation) redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1]) require.Len(t, redelegation, 1) @@ -759,7 +618,7 @@ func TestSubmitProposal(t *testing.T) { // verify balance acc = getAccount(t, port, addr) - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) // query proposal @@ -795,7 +654,7 @@ func TestDeposit(t *testing.T) { // verify balance acc = getAccount(t, port, addr) coins := acc.GetCoins() - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), coins.AmountOf(sdk.DefaultBondDenom)) expectedBalance = coins[0] @@ -810,7 +669,7 @@ func TestDeposit(t *testing.T) { // verify balance after deposit and fee acc = getAccount(t, port, addr) - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(depositTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) // query tx @@ -852,7 +711,7 @@ func TestVote(t *testing.T) { // verify balance acc = getAccount(t, port, addr) coins := acc.GetCoins() - expectedBalance := initialBalance[0].Minus(fees[0]) + expectedBalance := initialBalance[0].Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), coins.AmountOf(sdk.DefaultBondDenom)) expectedBalance = coins[0] @@ -868,7 +727,7 @@ func TestVote(t *testing.T) { // verify balance after vote and fee acc = getAccount(t, port, addr) coins = acc.GetCoins() - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.Equal(t, expectedBalance.Amount, coins.AmountOf(sdk.DefaultBondDenom)) expectedBalance = coins[0] @@ -892,7 +751,7 @@ func TestVote(t *testing.T) { // verify balance acc = getAccount(t, port, addr) coins = acc.GetCoins() - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.Equal(t, expectedBalance.Amount.Sub(delTokens), coins.AmountOf(sdk.DefaultBondDenom)) expectedBalance = coins[0] @@ -905,7 +764,7 @@ func TestVote(t *testing.T) { // verify balance acc = getAccount(t, port, addr) - expectedBalance = expectedBalance.Minus(fees[0]) + expectedBalance = expectedBalance.Sub(fees[0]) require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) tally = getTally(t, port, proposalID) @@ -939,7 +798,7 @@ func TestProposalsQuery(t *testing.T) { defer cleanup() depositParam := getDepositParam(t, port) - halfMinDeposit := depositParam.MinDeposit.AmountOf(sdk.DefaultBondDenom).DivRaw(2) + halfMinDeposit := depositParam.MinDeposit.AmountOf(sdk.DefaultBondDenom).QuoRaw(2) getVotingParam(t, port) getTallyingParam(t, port) @@ -1086,7 +945,7 @@ func TestDistributionFlow(t *testing.T) { operAddr := sdk.AccAddress(valAddr) var rewards sdk.DecCoins - res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) @@ -1108,7 +967,7 @@ func TestDistributionFlow(t *testing.T) { require.Equal(t, uint32(0), resultTx.Code) // Query outstanding rewards changed - res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 82a6af083..0c6b78da8 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -3,14 +3,12 @@ swagger: "2.0" info: version: "3.0" title: Gaia-Lite for Cosmos - description: A REST interface for state queries, transaction generation, signing, and broadcast. + description: A REST interface for state queries, transaction generation and broadcasting. tags: - name: ICS0 description: Tendermint APIs, such as query blocks, transactions and validatorset - - name: ICS1 - description: Key management APIs - name: ICS20 - description: Create, sign and broadcast transactions + description: Create and broadcast transactions - name: ICS21 description: Stake module APIs - name: ICS22 @@ -243,8 +241,8 @@ paths: post: tags: - ICS0 - summary: broadcast Tx - description: broadcast tx with tendermint rpc + summary: Broadcast a signed tx + description: Broadcast a signed tx to a full node consumes: - application/json produces: @@ -252,93 +250,28 @@ paths: parameters: - in: body name: txBroadcast - description: Build a StdTx transaction and serilize it to a byte array with amino, then the `"tx"` field in the post body will be the base64 encoding of the byte array. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away). + description: The tx must be a signed StdTx. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away). required: true schema: type: object properties: tx: - type: string + $ref: "#/definitions/StdTx" return: type: string example: block responses: 200: - description: Broadcast tx result + description: Tx broadcasting result schema: $ref: "#/definitions/BroadcastTxCommitResult" 500: description: Internal Server Error - /tx/sign: + /txs/encode: post: tags: - - ICS20 - summary: Sign a Tx - description: Sign a Tx providing locally stored account and according password - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: sendToken - description: sign tx - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - tx: - $ref: "#/definitions/StdTx" - append_sig: - type: boolean - example: true - responses: - 200: - description: The signed Tx - schema: - $ref: "#/definitions/StdTx" - 400: - description: The Tx was malformated or key doesn't exist - 401: - description: Key password is wrong - 500: - description: Server internal error - /tx/broadcast: - post: - tags: - - ICS20 - summary: Send a signed Tx - description: Send a signed Tx to a Gaiad full node - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: txBroadcast - description: broadcast tx - required: true - schema: - type: object - properties: - tx: - $ref: "#/definitions/StdTx" - responses: - 202: - description: Tx was send and will probably be added to the next block - schema: - $ref: "#/definitions/BroadcastTxCommitResult" - 400: - description: The Tx was malformated - 500: - description: Server internal error - /tx/encode: - post: - tags: - - ICS20 - summary: Encode a transaction to wire format + - ICS0 + summary: Encode a transaction to the Amino wire format description: Encode a transaction (signed or not) from JSON to base64-encoded Amino serialized bytes consumes: - application/json @@ -347,7 +280,7 @@ paths: parameters: - in: body name: tx - description: The transaction to encode + description: The tx to encode required: true schema: type: object @@ -356,15 +289,15 @@ paths: $ref: "#/definitions/StdTx" responses: 200: - description: Transaction was successfully decoded and re-encoded + description: The tx was successfully decoded and re-encoded schema: type: object properties: tx: type: string - example: The base64-encoded Amino-serialized bytes for the transaction + example: The base64-encoded Amino-serialized bytes for the tx 400: - description: The Tx was malformated + description: The tx was malformated 500: description: Server internal error /bank/balances/{address}: @@ -393,8 +326,7 @@ paths: description: Server internal error /bank/accounts/{address}/transfers: post: - summary: Send coins (build -> sign -> send) - description: Send coins (build -> sign -> send) + summary: Send coins from one account to another tags: - ICS20 consumes: @@ -409,7 +341,7 @@ paths: type: string - in: body name: account - description: The password of the account to remove from the KMS + description: The sender and tx information required: true schema: type: object @@ -422,188 +354,13 @@ paths: $ref: "#/definitions/Coin" responses: 202: - description: Tx was send and will probably be added to the next block + description: Tx was succesfully generated schema: - $ref: "#/definitions/BroadcastTxCommitResult" + $ref: "#/definitions/StdTx" 400: description: Invalid request - 401: - description: Key password is wrong 500: description: Server internal error - /keys: - get: - summary: List of accounts stored locally - tags: - - ICS1 - produces: - - application/json - responses: - 200: - description: Array of accounts - schema: - type: array - items: - $ref: "#/definitions/KeyOutput" - 500: - description: Server internal error - post: - summary: Create a new account locally - tags: - - ICS1 - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: account - description: The account to create - schema: - type: object - required: - - name - - password - - seed - properties: - name: - type: string - password: - type: string - seed: - type: string - responses: - 200: - description: Returns account information of the created key - schema: - $ref: "#/definitions/KeyOutput" - 400: - description: Invalid request - 409: - description: Key name confliction - 500: - description: Server internal error - /keys/seed: - get: - summary: Create a new seed to create a new account with - tags: - - ICS1 - responses: - 200: - description: 24 word Seed - schema: - type: string - example: blossom pool issue kidney elevator blame furnace winter account merry vessel security depend exact travel bargain problem jelly rural net again mask roast chest - /keys/{name}/recover: - post: - summary: Recover a account from a seed - tags: - - ICS1 - consumes: - - application/json - produces: - - application/json - parameters: - - in: path - name: name - description: Account name - required: true - type: string - - in: body - name: pwdAndSeed - description: Provide password and seed to recover a key - schema: - type: object - required: - - password - - seed - properties: - password: - type: string - seed: - type: string - responses: - 200: - description: Returns account information of the recovered key - schema: - $ref: "#/definitions/KeyOutput" - 400: - description: Invalid request - 409: - description: Key name confliction - 500: - description: Server internal error - /keys/{name}: - parameters: - - in: path - name: name - description: Account name - required: true - type: string - get: - summary: Get a certain locally stored account - tags: - - ICS1 - produces: - - application/json - responses: - 200: - description: Locally stored account - schema: - $ref: "#/definitions/KeyOutput" - 404: - description: Key doesn't exist - put: - summary: Update the password for this account in the KMS - tags: - - ICS1 - consumes: - - application/json - parameters: - - in: body - name: account - description: The new and old password - schema: - type: object - required: - - new_password - - old_password - properties: - new_password: - type: string - old_password: - type: string - responses: - 200: - description: Updated password - 401: - description: Key password is wrong - 404: - description: Key doesn't exist - delete: - summary: Remove an account - tags: - - ICS1 - consumes: - - application/json - parameters: - - in: body - name: account - description: The password of the account to remove from the KMS - schema: - type: object - required: - - password - properties: - password: - type: string - responses: - 200: - description: Removed account - 401: - description: Key password is wrong - 404: - description: Key doesn't exist /auth/accounts/{address}: get: summary: Get the account information on blockchain @@ -679,9 +436,9 @@ paths: properties: base_req: $ref: "#/definitions/BaseReq" - delegator_addr: + delegator_address: $ref: "#/definitions/Address" - validator_addr: + validator_address: $ref: "#/definitions/ValidatorAddress" delegation: $ref: "#/definitions/Coin" @@ -764,9 +521,9 @@ paths: properties: base_req: $ref: "#/definitions/BaseReq" - delegator_addr: + delegator_address: $ref: "#/definitions/Address" - validator_addr: + validator_address: $ref: "#/definitions/ValidatorAddress" shares: type: string @@ -861,17 +618,17 @@ paths: parameters: - in: body name: delegation - description: The password of the account to remove from the KMS + description: The sender and tx information schema: type: object properties: base_req: $ref: "#/definitions/BaseReq" - delegator_addr: + delegator_address: $ref: "#/definitions/Address" - validator_src_addr: + validator_src_addressess: $ref: "#/definitions/ValidatorAddress" - validator_dst_addr: + validator_dst_address: $ref: "#/definitions/ValidatorAddress" shares: type: string @@ -884,13 +641,11 @@ paths: - application/json responses: 200: - description: OK + description: Tx was succesfully generated schema: - $ref: "#/definitions/BroadcastTxCommitResult" + $ref: "#/definitions/StdTx" 400: description: Invalid delegator address or redelegation request body - 401: - description: Key password is wrong 500: description: Internal Server Error /staking/delegators/{delegatorAddr}/validators: @@ -1170,16 +925,14 @@ paths: type: object properties: base_req: - $ref: "#/definitions/BaseReq" + $ref: "#/definitions/StdTx" responses: 200: - description: OK + description: Tx was succesfully generated schema: $ref: "#/definitions/BroadcastTxCommitResult" 400: description: Invalid validator address or base_req - 401: - description: Key password is wrong 500: description: Internal Server Error /slashing/parameters: @@ -1246,13 +999,11 @@ paths: $ref: "#/definitions/Coin" responses: 200: - description: OK + description: Tx was succesfully generated schema: - $ref: "#/definitions/BroadcastTxCommitResult" + $ref: "#/definitions/StdTx" 400: description: Invalid proposal body - 401: - description: Key password is wrong 500: description: Internal Server Error get: @@ -1807,6 +1558,28 @@ paths: description: Invalid validator address 500: description: Internal Server Error + /distribution/validators/{validatorAddr}/outstanding_rewards: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Fee distribution outstanding rewards of a single validator + tags: + - ICS24 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Coin" + 500: + description: Internal Server Error /distribution/validators/{validatorAddr}/rewards: parameters: - in: path @@ -1815,8 +1588,8 @@ paths: required: true type: string get: - summary: Commission and self-delegation rewards of a single a validator - description: Query the commission and self-delegation rewards of a validator. + summary: Commission and self-delegation rewards of a single validator + description: Query the commission and self-delegation rewards of validator. tags: - ICS24 produces: @@ -1879,22 +1652,6 @@ paths: type: string 500: description: Internal Server Error - /distribution/outstanding_rewards: - get: - summary: Fee distribution outstanding rewards - tags: - - ICS24 - produces: - - application/json - responses: - 200: - description: OK - schema: - type: array - items: - $ref: "#/definitions/Coin" - 500: - description: Internal Server Error definitions: CheckTxResult: type: object @@ -2189,9 +1946,6 @@ definitions: type: string example: "cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc" description: Sender address or Keybase name to generate a transaction - password: - type: string - example: "12345678" memo: type: string example: "Sent via Cosmos Voyager 🚀" @@ -2214,10 +1968,6 @@ definitions: type: array items: $ref: "#/definitions/Coin" - generate_only: - type: boolean - example: false - description: Create a JSON transaction that can be signed client side instead of actually signing and broadcasting simulate: type: boolean example: false @@ -2358,9 +2108,9 @@ definitions: Delegation: type: object properties: - delegator_addr: + delegator_address: type: string - validator_addr: + validator_address: type: string shares: type: string @@ -2369,9 +2119,9 @@ definitions: UnbondingDelegation: type: object properties: - delegator_addr: + delegator_address: type: string - validator_addr: + validator_address: type: string initial_balance: type: string @@ -2384,11 +2134,11 @@ definitions: Redelegation: type: object properties: - delegator_addr: + delegator_address: type: string - validator_src_addr: + validator_src_address: type: string - validator_dst_addr: + validator_dst_address: type: string creation_height: type: integer @@ -2405,7 +2155,7 @@ definitions: ValidatorDistInfo: type: object properties: - operator_addr: + operator_address: $ref: "#/definitions/ValidatorAddress" self_bond_rewards: type: array diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index cbb97ebae..507c6af03 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -19,10 +19,13 @@ import ( "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -397,7 +400,6 @@ func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec, t *testing // NOTE: If making updates here also update cmd/gaia/cmd/gaiacli/main.go func registerRoutes(rs *RestServer) { - keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent) rpc.RegisterRoutes(rs.CliCtx, rs.Mux) tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) authrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey) @@ -557,7 +559,7 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput { // POST /keys Create a new account locally func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput { - pk := keys.AddNewKey{name, password, mnemonic, account, index} + pk := clientkeys.AddNewKey{name, password, mnemonic, account, index} req, err := cdc.MarshalJSON(pk) require.NoError(t, err) @@ -583,7 +585,7 @@ func getKeysSeed(t *testing.T, port string) string { // POST /keys/{name}/recove Recover a account from a seed func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) { - pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)} + pk := clientkeys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)} req, err := cdc.MarshalJSON(pk) require.NoError(t, err) @@ -611,7 +613,7 @@ func getKey(t *testing.T, port, name string) keys.KeyOutput { // PUT /keys/{name} Update the password for this account in the KMS func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) { - kr := keys.UpdateKeyReq{oldPassword, newPassword} + kr := clientkeys.UpdateKeyReq{oldPassword, newPassword} req, err := cdc.MarshalJSON(kr) require.NoError(t, err) keyEndpoint := fmt.Sprintf("/keys/%s", name) @@ -625,7 +627,7 @@ func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail b // DELETE /keys/{name} Remove an account func deleteKey(t *testing.T, port, name, password string) { - dk := keys.DeleteKeyReq{password} + dk := clientkeys.DeleteKeyReq{password} req, err := cdc.MarshalJSON(dk) require.NoError(t, err) keyEndpoint := fmt.Sprintf("/keys/%s", name) @@ -647,52 +649,42 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { // ICS 20 - Tokens // ---------------------------------------------------------------------- -// POST /tx/sign Sign a Tx -func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence uint64, msg auth.StdTx) auth.StdTx { - var signedMsg auth.StdTx - payload := authrest.SignBody{ - Tx: msg, - BaseReq: rest.NewBaseReq( - name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false, - ), - } - json, err := cdc.MarshalJSON(payload) - require.Nil(t, err) - res, body := Request(t, port, "POST", "/tx/sign", json) - require.Equal(t, http.StatusOK, res.StatusCode, body) - require.Nil(t, cdc.UnmarshalJSON([]byte(body), &signedMsg)) - return signedMsg -} - // POST /tx/broadcast Send a signed Tx -func doBroadcast(t *testing.T, port string, msg auth.StdTx) sdk.TxResponse { - tx := authrest.BroadcastReq{Tx: msg, Return: "block"} - req, err := cdc.MarshalJSON(tx) - require.Nil(t, err) - res, body := Request(t, port, "POST", "/tx/broadcast", req) - require.Equal(t, http.StatusOK, res.StatusCode, body) - var resultTx sdk.TxResponse - require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx)) - return resultTx -} +func doBroadcast(t *testing.T, port string, tx auth.StdTx) (*http.Response, string) { + txReq := clienttx.BroadcastReq{Tx: tx, Return: "block"} -// GET /bank/balances/{address} Get the account balances - -// POST /bank/accounts/{address}/transfers Send coins (build -> sign -> send) -func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx sdk.TxResponse) { - res, body, receiveAddr := doTransferWithGas(t, port, seed, name, memo, password, addr, "", 1.0, false, false, fees) - require.Equal(t, http.StatusOK, res.StatusCode, body) - - err := cdc.UnmarshalJSON([]byte(body), &resultTx) + req, err := cdc.MarshalJSON(txReq) require.Nil(t, err) - return receiveAddr, resultTx + return Request(t, port, "POST", "/txs", req) } +// doTransfer performs a balance transfer with auto gas calculation. It also signs +// the tx and broadcasts it. +func doTransfer( + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, fees sdk.Coins, +) (sdk.AccAddress, sdk.TxResponse) { + + resp, body, recvAddr := doTransferWithGas( + t, port, seed, name, memo, pwd, addr, "", 1.0, false, true, fees, + ) + require.Equal(t, http.StatusOK, resp.StatusCode, resp) + + var txResp sdk.TxResponse + err := cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return recvAddr, txResp +} + +// doTransferWithGas performs a balance transfer with a specified gas value. The +// broadcast parameter determines if the tx should only be generated or also +// signed and broadcasted. The sending account's number and sequence are +// determined prior to generating the tx. func doTransferWithGas( - t *testing.T, port, seed, from, memo, password string, addr sdk.AccAddress, - gas string, gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins, -) (res *http.Response, body string, receiveAddr sdk.AccAddress) { + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, + gas string, gasAdjustment float64, simulate, broadcast bool, fees sdk.Coins, +) (resp *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := crkeys.NewInMemory() @@ -708,15 +700,9 @@ func doTransferWithGas( sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - if generateOnly { - // generate only txs do not use a Keybase so the address must be used - from = addr.String() - } - + from := addr.String() baseReq := rest.NewBaseReq( - from, password, memo, chainID, gas, - fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, - generateOnly, simulate, + from, memo, chainID, gas, fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, simulate, ) sr := bankrest.SendReq{ @@ -727,17 +713,28 @@ func doTransferWithGas( req, err := cdc.MarshalJSON(sr) require.NoError(t, err) - res, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) - return + // generate tx + resp, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) + if !broadcast { + return resp, body, receiveAddr + } + + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, gasAdjustment, simulate) + return resp, body, receiveAddr } +// doTransferWithGasAccAuto is similar to doTransferWithGas except that it +// automatically determines the account's number and sequence when generating the +// tx. func doTransferWithGasAccAuto( - t *testing.T, port, seed, from, memo, password string, gas string, - gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins, -) (res *http.Response, body string, receiveAddr sdk.AccAddress) { + t *testing.T, port, seed, name, memo, pwd string, addr sdk.AccAddress, + gas string, gasAdjustment float64, simulate, broadcast bool, fees sdk.Coins, +) (resp *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := crkeys.NewInMemory() + acc := getAccount(t, port, addr) receiveInfo, _, err := kb.CreateMnemonic( "receive_address", crkeys.English, gapp.DefaultKeyPass, crkeys.SigningAlgo("secp256k1"), @@ -747,9 +744,9 @@ func doTransferWithGasAccAuto( receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) chainID := viper.GetString(client.FlagChainID) + from := addr.String() baseReq := rest.NewBaseReq( - from, password, memo, chainID, gas, - fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate, + from, memo, chainID, gas, fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, simulate, ) sr := bankrest.SendReq{ @@ -760,8 +757,45 @@ func doTransferWithGasAccAuto( req, err := cdc.MarshalJSON(sr) require.NoError(t, err) - res, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) - return + resp, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) + if !broadcast { + return resp, body, receiveAddr + } + + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, gasAdjustment, simulate) + return resp, body, receiveAddr +} + +// signAndBroadcastGenTx accepts a successfully generated unsigned tx, signs it, +// and broadcasts it. +func signAndBroadcastGenTx( + t *testing.T, port, name, pwd, genTx string, acc auth.Account, gasAdjustment float64, simulate bool, +) (resp *http.Response, body string) { + + chainID := viper.GetString(client.FlagChainID) + + var tx auth.StdTx + err := cdc.UnmarshalJSON([]byte(genTx), &tx) + require.Nil(t, err) + + txbldr := txbuilder.NewTxBuilder( + utils.GetTxEncoder(cdc), + acc.GetAccountNumber(), + acc.GetSequence(), + tx.Fee.Gas, + gasAdjustment, + simulate, + chainID, + tx.Memo, + tx.Fee.Amount, + nil, + ) + + signedTx, err := txbldr.SignStdTx(name, pwd, tx, false) + require.NoError(t, err) + + return doBroadcast(t, port, signedTx) } // ---------------------------------------------------------------------- @@ -769,112 +803,135 @@ func doTransferWithGasAccAuto( // ---------------------------------------------------------------------- // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation -func doDelegate(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doDelegate( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, + valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) msg := msgDelegationsInput{ - BaseReq: baseReq, - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: sdk.NewCoin(sdk.DefaultBondDenom, amount), + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + Delegation: sdk.NewCoin(sdk.DefaultBondDenom, amount), } + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) - var result sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &result) - require.Nil(t, err) + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return result + // sign and broadcast + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } type msgDelegationsInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 + Delegation sdk.Coin `json:"delegation"` } // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation -func doUndelegate(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doUndelegate( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, + valAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) msg := msgUndelegateInput{ - BaseReq: baseReq, - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - SharesAmount: sdk.NewDecFromInt(amount), + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + SharesAmount: amount.ToDec(), } + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - var result sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &result) - require.Nil(t, err) + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return result + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } type msgUndelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation -func doBeginRedelegation(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount sdk.Int, - fees sdk.Coins) (resultTx sdk.TxResponse) { +func doBeginRedelegation( + t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, valSrcAddr, + valDstAddr sdk.ValAddress, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) msg := stakingrest.MsgBeginRedelegateInput{ - BaseReq: baseReq, - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - SharesAmount: sdk.NewDecFromInt(amount), + BaseReq: baseReq, + DelegatorAddress: delAddr, + ValidatorSrcAddress: valSrcAddr, + ValidatorDstAddress: valDstAddr, + SharesAmount: amount.ToDec(), } + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) + resp, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - var result sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &result) - require.Nil(t, err) + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return result + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } type msgBeginRedelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` // in bech32 + ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } // GET /staking/delegators/{delegatorAddr}/delegations Get all delegations from a delegator @@ -1073,15 +1130,18 @@ func getStakingParams(t *testing.T, port string) staking.Params { // ICS 22 - Gov // ---------------------------------------------------------------------- // POST /gov/proposals Submit a proposal -func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, - amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doSubmitProposal( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) pr := govrest.PostProposalReq{ Title: "Test", Description: "test", @@ -1095,14 +1155,17 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA require.NoError(t, err) // submitproposal - res, body := Request(t, port, "POST", "/gov/proposals", req) - require.Equal(t, http.StatusOK, res.StatusCode, body) + resp, body := Request(t, port, "POST", "/gov/proposals", req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - var results sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &results) - require.Nil(t, err) + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return results + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } // GET /gov/proposals Query proposals @@ -1161,15 +1224,18 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat } // POST /gov/proposals/{proposalId}/deposits Deposit tokens to a proposal -func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, - amount sdk.Int, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doDeposit( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + proposalID uint64, amount sdk.Int, fees sdk.Coins, +) sdk.TxResponse { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) dr := govrest.DepositReq{ Depositor: proposerAddr, Amount: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, amount)}, @@ -1179,14 +1245,17 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk req, err := cdc.MarshalJSON(dr) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) + resp, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - var results sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &results) - require.Nil(t, err) + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return results + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } // GET /gov/proposals/{proposalId}/deposits Query deposits @@ -1210,14 +1279,19 @@ func getTally(t *testing.T, port string, proposalID uint64) gov.TallyResult { } // POST /gov/proposals/{proposalId}/votes Vote a proposal -func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doVote( + t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress, + proposalID uint64, option string, fees sdk.Coins, +) sdk.TxResponse { + // get the account to get the sequence acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) vr := govrest.VoteReq{ Voter: proposerAddr, Option: option, @@ -1227,14 +1301,17 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac req, err := cdc.MarshalJSON(vr) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) + resp, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - var results sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &results) - require.Nil(t, err) + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return results + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } // GET /gov/proposals/{proposalId}/votes Query voters @@ -1339,24 +1416,32 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing. // TODO: Test this functionality, it is not currently in any of the tests // POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator -func doUnjail(t *testing.T, port, seed, name, password string, - valAddr sdk.ValAddress, fees sdk.Coins) (resultTx sdk.TxResponse) { - chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false) +func doUnjail( + t *testing.T, port, seed, name, pwd string, valAddr sdk.ValAddress, fees sdk.Coins, +) sdk.TxResponse { + acc := getAccount(t, port, sdk.AccAddress(valAddr.Bytes())) + from := acc.GetAddress().String() + chainID := viper.GetString(client.FlagChainID) + + baseReq := rest.NewBaseReq(from, "", chainID, "", "", 1, 1, fees, nil, false) ur := slashingrest.UnjailReq{ BaseReq: baseReq, } req, err := cdc.MarshalJSON(ur) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) - var results sdk.TxResponse - err = cdc.UnmarshalJSON([]byte(body), &results) - require.Nil(t, err) + resp, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return results + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err = cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } type unjailReq struct { @@ -1366,27 +1451,34 @@ type unjailReq struct { // ICS24 - fee distribution // POST /distribution/delegators/{delgatorAddr}/rewards Withdraw delegator rewards -func doWithdrawDelegatorAllRewards(t *testing.T, port, seed, name, password string, - delegatorAddr sdk.AccAddress, fees sdk.Coins) (resultTx sdk.TxResponse) { +func doWithdrawDelegatorAllRewards( + t *testing.T, port, seed, name, pwd string, delegatorAddr sdk.AccAddress, fees sdk.Coins, +) sdk.TxResponse { // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + from := acc.GetAddress().String() + baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) wr := struct { BaseReq rest.BaseReq `json:"base_req"` }{BaseReq: baseReq} req := cdc.MustMarshalJSON(wr) - res, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req) - require.Equal(t, http.StatusOK, res.StatusCode, body) - var results sdk.TxResponse - cdc.MustUnmarshalJSON([]byte(body), &results) + resp, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req) + require.Equal(t, http.StatusOK, resp.StatusCode, body) - return results + resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false) + require.Equal(t, http.StatusOK, resp.StatusCode, body) + + var txResp sdk.TxResponse + err := cdc.UnmarshalJSON([]byte(body), &txResp) + require.NoError(t, err) + + return txResp } func mustParseDecCoins(dcstring string) sdk.DecCoins { diff --git a/client/rest/rest.go b/client/rest/rest.go index d5cc7bab1..81d2cc57b 100644 --- a/client/rest/rest.go +++ b/client/rest/rest.go @@ -7,7 +7,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/x/auth" @@ -17,84 +16,6 @@ import ( //----------------------------------------------------------------------------- // Building / Sending utilities -// CompleteAndBroadcastTxREST implements a utility function that facilitates -// sending a series of messages in a signed tx. In addition, it will handle -// tx gas simulation and estimation. -// -// NOTE: Also see CompleteAndBroadcastTxCLI. -func CompleteAndBroadcastTxREST(w http.ResponseWriter, cliCtx context.CLIContext, - baseReq rest.BaseReq, msgs []sdk.Msg, cdc *codec.Codec) { - - gasAdj, ok := rest.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - - simAndExec, gas, err := client.ParseGas(baseReq.Gas) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(baseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - txBldr := authtxb.NewTxBuilder( - utils.GetTxEncoder(cdc), baseReq.AccountNumber, - baseReq.Sequence, gas, gasAdj, baseReq.Simulate, - baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices, - ) - - txBldr, err = utils.PrepareTxBuilder(txBldr, cliCtx) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - if baseReq.Simulate || simAndExec { - if gasAdj < 0 { - rest.WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error()) - return - } - - txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if baseReq.Simulate { - rest.WriteSimulationResponse(w, cdc, txBldr.Gas()) - return - } - } - - txBytes, err := txBldr.BuildAndSign(cliCtx.GetFromName(), baseReq.Password, msgs) - if keyerror.IsErrKeyNotFound(err) { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } else if keyerror.IsErrWrongPassword(err) { - rest.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } else if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) -} - // WriteGenerateStdTxResponse writes response for the generate only mode. func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, cliCtx context.CLIContext, br rest.BaseReq, msgs []sdk.Msg) { @@ -115,7 +36,7 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, br.Simulate, br.ChainID, br.Memo, br.Fees, br.GasPrices, ) - if simAndExec { + if br.Simulate || simAndExec { if gasAdj < 0 { rest.WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error()) return @@ -126,6 +47,11 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + + if br.Simulate { + rest.WriteSimulationResponse(w, cdc, txBldr.Gas()) + return + } } stdMsg, err := txBldr.BuildSignMsg(msgs) diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index e75a8cdc4..a0437b1cd 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -2,12 +2,19 @@ package tx import ( "net/http" + "strings" + "github.com/spf13/cobra" + amino "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth" "io/ioutil" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" ) @@ -20,44 +27,93 @@ const ( flagBlock = "block" ) -// BroadcastBody Tx Broadcast Body -type BroadcastBody struct { - TxBytes []byte `json:"tx"` - Return string `json:"return"` +// BroadcastReq defines a tx broadcasting request. +type BroadcastReq struct { + Tx auth.StdTx `json:"tx"` + Return string `json:"return"` } -// BroadcastTxRequest REST Handler -// nolint: gocyclo +// BroadcastTxRequest implements a tx broadcasting handler that is responsible +// for broadcasting a valid and signed tx to a full node. The tx can be +// broadcasted via a sync|async|block mechanism. func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m BroadcastBody + var req BroadcastReq + body, err := ioutil.ReadAll(r.Body) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &m) + + err = cdc.UnmarshalJSON(body, &req) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } + + txBytes, err := cdc.MarshalBinaryLengthPrefixed(req.Tx) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + var res interface{} - switch m.Return { + switch req.Return { case flagBlock: - res, err = cliCtx.BroadcastTx(m.TxBytes) + res, err = cliCtx.BroadcastTx(txBytes) + case flagSync: - res, err = cliCtx.BroadcastTxSync(m.TxBytes) + res, err = cliCtx.BroadcastTxSync(txBytes) + case flagAsync: - res, err = cliCtx.BroadcastTxAsync(m.TxBytes) + res, err = cliCtx.BroadcastTxAsync(txBytes) + default: rest.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async") return } + if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } + +// GetBroadcastCommand returns the tx broadcast command. +func GetBroadcastCommand(codec *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "broadcast [file_path]", + Short: "Broadcast transactions generated offline", + Long: strings.TrimSpace(`Broadcast transactions created with the --generate-only +flag and signed with the sign command. Read a transaction from [file_path] and +broadcast it to a node. If you supply a dash (-) argument in place of an input +filename, the command reads from standard input. + +$ gaiacli tx broadcast ./mytxn.json +`), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + cliCtx := context.NewCLIContext().WithCodec(codec) + stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0]) + if err != nil { + return + } + + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) + if err != nil { + return + } + + res, err := cliCtx.BroadcastTx(txBytes) + cliCtx.PrintOutput(res) + return err + }, + } + + return client.PostCommands(cmd)[0] +} diff --git a/client/tx/encode.go b/client/tx/encode.go new file mode 100644 index 000000000..75670aade --- /dev/null +++ b/client/tx/encode.go @@ -0,0 +1,107 @@ +package tx + +import ( + "encoding/base64" + "io/ioutil" + "net/http" + + "github.com/spf13/cobra" + amino "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +type ( + // EncodeReq defines a tx encoding request. + EncodeReq struct { + Tx auth.StdTx `json:"tx"` + } + + // EncodeResp defines a tx encoding response. + EncodeResp struct { + Tx string `json:"tx"` + } +) + +// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular, +// it takes a json-formatted transaction, encodes it to the Amino wire protocol, +// and responds with base64-encoded bytes. +func EncodeTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req EncodeReq + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + err = cdc.UnmarshalJSON(body, &req) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // re-encode it via the Amino wire protocol + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(req.Tx) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + // base64 encode the encoded tx bytes + txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes) + + response := EncodeResp{Tx: txBytesBase64} + rest.PostProcessResponse(w, cdc, response, cliCtx.Indent) + } +} + +// txEncodeRespStr implements a simple Stringer wrapper for a encoded tx. +type txEncodeRespStr string + +func (txr txEncodeRespStr) String() string { + return string(txr) +} + +// GetEncodeCommand returns the encode command to take a JSONified transaction and turn it into +// Amino-serialized bytes +func GetEncodeCommand(codec *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "encode [file]", + Short: "Encode transactions generated offline", + Long: `Encode transactions created with the --generate-only flag and signed with the sign command. +Read a transaction from , serialize it to the Amino wire protocol, and output it as base64. +If you supply a dash (-) argument in place of an input filename, the command reads from standard input.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + cliCtx := context.NewCLIContext().WithCodec(codec) + + stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0]) + if err != nil { + return + } + + // re-encode it via the Amino wire protocol + txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) + if err != nil { + return err + } + + // base64 encode the encoded tx bytes + txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes) + + response := txEncodeRespStr(txBytesBase64) + cliCtx.PrintOutput(response) + + return nil + }, + } + + return client.PostCommands(cmd)[0] +} diff --git a/client/tx/root.go b/client/tx/root.go index 5cd7eca0b..104e0a57c 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -12,4 +12,5 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET") r.HandleFunc("/txs", BroadcastTxRequest(cliCtx, cdc)).Methods("POST") + r.HandleFunc("/txs/encode", EncodeTxRequestHandlerFn(cdc, cliCtx)).Methods("POST") } diff --git a/client/utils/utils.go b/client/utils/utils.go index 8f4283088..8d1c0b494 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -3,12 +3,13 @@ package utils import ( "bytes" "fmt" + "io/ioutil" "os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/client/context" @@ -40,8 +41,6 @@ func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder // QueryContext. It ensures that the account exists, has a proper number and // sequence set. In addition, it builds and signs a transaction with the // supplied messages. Finally, it broadcasts the signed transaction to a node. -// -// NOTE: Also see CompleteAndBroadcastTxREST. func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { txBldr, err := PrepareTxBuilder(txBldr, cliCtx) if err != nil { @@ -64,6 +63,22 @@ func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIConte return nil } + if !cliCtx.SkipConfirm { + stdSignMsg, err := txBldr.BuildSignMsg(msgs) + if err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "%s\n\n", cliCtx.Codec.MustMarshalJSON(stdSignMsg)) + + buf := client.BufferStdin() + ok, err := client.GetConfirmation("confirm transaction before signing and broadcasting", buf) + if err != nil || !ok { + fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") + return err + } + } + passphrase, err := keys.GetPassphrase(fromName) if err != nil { return err @@ -110,20 +125,27 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * // PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. // Don't perform online validation or lookups if offline is true. -func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool) (err error) { +func PrintUnsignedStdTx( + txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool, +) (err error) { + var stdTx auth.StdTx + if offline { stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) } else { stdTx, err = buildUnsignedStdTx(txBldr, cliCtx, msgs) } + if err != nil { return } + json, err := cliCtx.Codec.MarshalJSON(stdTx) if err == nil { fmt.Fprintf(cliCtx.Output, "%s\n", json) } + return } @@ -190,6 +212,23 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont return txBldr.SignStdTx(name, passphrase, stdTx, false) } +// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin. +func ReadStdTxFromFile(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) { + var bytes []byte + if filename == "-" { + bytes, err = ioutil.ReadAll(os.Stdin) + } else { + bytes, err = ioutil.ReadFile(filename) + } + if err != nil { + return + } + if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil { + return + } + return +} + func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress) (authtxb.TxBuilder, error) { if txBldr.AccountNumber() == 0 { diff --git a/client/utils/utils_test.go b/client/utils/utils_test.go index d5ed6205e..a0d0b9386 100644 --- a/client/utils/utils_test.go +++ b/client/utils/utils_test.go @@ -3,19 +3,19 @@ package utils import ( "encoding/json" "errors" + "io/ioutil" + "os" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/x/auth" - - "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" ) var ( @@ -110,3 +110,32 @@ func makeCodec() *codec.Codec { cdc.RegisterConcrete(sdk.TestMsg{}, "cosmos-sdk/Test", nil) return cdc } + +func TestReadStdTxFromFile(t *testing.T) { + cdc := codec.New() + sdk.RegisterCodec(cdc) + + // Build a test transaction + fee := auth.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}) + stdTx := auth.NewStdTx([]sdk.Msg{}, fee, []auth.StdSignature{}, "foomemo") + + // Write it to the file + encodedTx, _ := cdc.MarshalJSON(stdTx) + jsonTxFile := writeToNewTempFile(t, string(encodedTx)) + defer os.Remove(jsonTxFile.Name()) + + // Read it back + decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) + require.Nil(t, err) + require.Equal(t, decodedTx.Memo, "foomemo") +} + +func writeToNewTempFile(t *testing.T, data string) *os.File { + fp, err := ioutil.TempFile(os.TempDir(), "client_tx_test") + require.Nil(t, err) + + _, err = fp.WriteString(data) + require.Nil(t, err) + + return fp +} diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 096d68ba5..36044513f 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -11,6 +11,9 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + // TODO: Remove once transfers are enabled. + gaiabank "github.com/cosmos/cosmos-sdk/cmd/gaia/app/x/bank" + bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -149,14 +152,17 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b ) // register message routes + // + // TODO: Use standard bank router once transfers are enabled. app.Router(). - AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)). + AddRoute(bank.RouterKey, gaiabank.NewHandler(app.bankKeeper)). AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)). AddRoute(distr.RouterKey, distr.NewHandler(app.distrKeeper)). AddRoute(slashing.RouterKey, slashing.NewHandler(app.slashingKeeper)). AddRoute(gov.RouterKey, gov.NewHandler(app.govKeeper)) app.QueryRouter(). + AddRoute(auth.QuerierRoute, auth.NewQuerier(app.accountKeeper)). AddRoute(distr.QuerierRoute, distr.NewQuerier(app.distrKeeper)). AddRoute(gov.QuerierRoute, gov.NewQuerier(app.govKeeper)). AddRoute(slashing.QuerierRoute, slashing.NewQuerier(app.slashingKeeper, app.cdc)). @@ -324,7 +330,7 @@ func (app *GaiaApp) LoadHeight(height int64) error { return app.LoadVersion(height, app.keyMain) } -//______________________________________________________________________________________________ +// ______________________________________________________________________________________________ var _ sdk.StakingHooks = StakingHooks{} diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 3c3df5e14..3bd439396 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -89,7 +89,7 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st // withdraw all delegator rewards dels := app.stakingKeeper.GetAllDelegations(ctx) for _, delegation := range dels { - _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) } // clear validator slash events @@ -104,13 +104,20 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st // reinitialize all validators app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + feePool := app.distrKeeper.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Add(scraps) + app.distrKeeper.SetFeePool(ctx, feePool) + app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) return false }) // reinitialize all delegations for _, del := range dels { - app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddr, del.ValidatorAddr) + app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress) } // reset context height diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 3938c9dce..65a64033b 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -11,13 +11,12 @@ import ( "strings" "time" - "github.com/cosmos/cosmos-sdk/x/bank" - tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" @@ -360,8 +359,8 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm msg := msgs[0].(staking.MsgCreateValidator) // validate delegator and validator addresses and funds against the accounts in the state - delAddr := msg.DelegatorAddr.String() - valAddr := sdk.AccAddress(msg.ValidatorAddr).String() + delAddr := msg.DelegatorAddress.String() + valAddr := sdk.AccAddress(msg.ValidatorAddress).String() delAcc, delOk := addrMap[delAddr] _, valOk := addrMap[valAddr] diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 058347069..4d39b4e48 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -40,7 +40,7 @@ func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { require.Equal(t, 1, len(msgs)) msg := msgs[0].(staking.MsgCreateValidator) - acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr)) + acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddress)) acc.Coins = sdk.Coins{sdk.NewInt64Coin(defaultBondDenom, 150)} genAccs[i] = NewGenesisAccount(&acc) stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens.Add(sdk.NewInt(150)) // increase the supply diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 7e4d465fe..2cba35458 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -161,9 +161,9 @@ func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimest VotingPeriod: vp, }, TallyParams: gov.TallyParams{ - Threshold: sdk.NewDecWithPrec(5, 1), - Veto: sdk.NewDecWithPrec(334, 3), - GovernancePenalty: sdk.NewDecWithPrec(1, 2), + Quorum: sdk.NewDecWithPrec(334, 3), + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), }, } fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis) @@ -172,7 +172,7 @@ func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimest Pool: staking.InitialPool(), Params: staking.Params{ UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second, - MaxValidators: uint16(r.Intn(250)), + MaxValidators: uint16(r.Intn(250) + 1), BondDenom: sdk.DefaultBondDenom, }, } @@ -305,7 +305,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { var db dbm.DB dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") - db, _ = dbm.NewGoLevelDB("Simulation", dir) + db, _ = sdk.NewLevelDB("Simulation", dir) defer func() { db.Close() os.RemoveAll(dir) @@ -347,7 +347,7 @@ func TestFullGaiaSimulation(t *testing.T) { } var db dbm.DB dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") - db, _ = dbm.NewGoLevelDB("Simulation", dir) + db, _ = sdk.NewLevelDB("Simulation", dir) defer func() { db.Close() os.RemoveAll(dir) @@ -388,7 +388,7 @@ func TestGaiaImportExport(t *testing.T) { } var db dbm.DB dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") - db, _ = dbm.NewGoLevelDB("Simulation", dir) + db, _ = sdk.NewLevelDB("Simulation", dir) defer func() { db.Close() os.RemoveAll(dir) @@ -421,7 +421,7 @@ func TestGaiaImportExport(t *testing.T) { fmt.Printf("Importing genesis...\n") newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") - newDB, _ := dbm.NewGoLevelDB("Simulation-2", dir) + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) defer func() { newDB.Close() os.RemoveAll(newDir) @@ -483,7 +483,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) { logger = log.NewNopLogger() } dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") - db, _ := dbm.NewGoLevelDB("Simulation", dir) + db, _ := sdk.NewLevelDB("Simulation", dir) defer func() { db.Close() os.RemoveAll(dir) @@ -525,7 +525,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) { fmt.Printf("Importing genesis...\n") newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") - newDB, _ := dbm.NewGoLevelDB("Simulation-2", dir) + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) defer func() { newDB.Close() os.RemoveAll(newDir) diff --git a/cmd/gaia/app/x/bank/handler.go b/cmd/gaia/app/x/bank/handler.go new file mode 100644 index 000000000..d3aa3415d --- /dev/null +++ b/cmd/gaia/app/x/bank/handler.go @@ -0,0 +1,100 @@ +// Package bank contains a forked version of the bank module. It only contains +// a modified message handler to support a very limited form of transfers during +// mainnet launch -- MsgMultiSend messages. +// +// NOTE: This fork should be removed entirely once transfers are enabled and +// the Gaia router should be reset to using the original bank module handler. +package bank + +import ( + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +var ( + uatomDenom = "uatom" + atomsToUatoms = int64(1000000) + + // BurnedCoinsAccAddr represents the burn account address used for + // MsgMultiSend message during the period for which transfers are disabled. + // Its Bech32 address is cosmos1x4p90uuy63fqzsheamn48vq88q3eusykf0a69v. + BurnedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("bankBurnedCoins"))) +) + +// NewHandler returns a handler for "bank" type messages. +func NewHandler(k bank.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case bank.MsgSend: + return handleMsgSend(ctx, k, msg) + + case bank.MsgMultiSend: + return handleMsgMultiSend(ctx, k, msg) + + default: + errMsg := "Unrecognized bank Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// handleMsgSend implements a MsgSend message handler. It operates no differently +// than the standard bank module MsgSend message handler in that it transfers +// an amount from one account to another under the condition of transfers being +// enabled. +func handleMsgSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgSend) sdk.Result { + // No need to modify handleMsgSend as the forked module requires no changes, + // so we can just call the standard bank modules handleMsgSend since we know + // the message is of type MsgSend. + return bank.NewHandler(k)(ctx, msg) +} + +// handleMsgMultiSend implements a modified forked version of a MsgMultiSend +// message handler. If transfers are disabled, a modified version of MsgMultiSend +// is allowed where there must be a single input and only two outputs. The first +// of the two outputs must be to a specific burn address defined by +// burnedCoinsAccAddr. In addition, the output amounts must be of 9atom and +// 1uatom respectively. +func handleMsgMultiSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgMultiSend) sdk.Result { + // NOTE: totalIn == totalOut should already have been checked + if !k.GetSendEnabled(ctx) { + if !validateMultiSendTransfersDisabled(msg) { + return bank.ErrSendDisabled(k.Codespace()).Result() + } + } + + tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) + if err != nil { + return err.Result() + } + + return sdk.Result{ + Tags: tags, + } +} + +func validateMultiSendTransfersDisabled(msg bank.MsgMultiSend) bool { + nineAtoms := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 9*atomsToUatoms)} + oneAtom := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 1*atomsToUatoms)} + + if len(msg.Inputs) != 1 { + return false + } + if len(msg.Outputs) != 2 { + return false + } + + if !msg.Outputs[0].Address.Equals(BurnedCoinsAccAddr) { + return false + } + if !msg.Outputs[0].Coins.IsEqual(nineAtoms) { + return false + } + if !msg.Outputs[1].Coins.IsEqual(oneAtom) { + return false + } + + return true +} diff --git a/cmd/gaia/app/x/bank/handler_test.go b/cmd/gaia/app/x/bank/handler_test.go new file mode 100644 index 000000000..c799e03f8 --- /dev/null +++ b/cmd/gaia/app/x/bank/handler_test.go @@ -0,0 +1,216 @@ +package bank + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" +) + +var ( + addrs = []sdk.AccAddress{ + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + } + + initAmt = sdk.NewInt(atomsToUatoms * 100) +) + +type testInput struct { + ctx sdk.Context + accKeeper auth.AccountKeeper + bankKeeper bank.Keeper +} + +func newTestCodec() *codec.Codec { + cdc := codec.New() + + bank.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + + return cdc +} + +func createTestInput(t *testing.T) testInput { + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + + cdc := newTestCodec() + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ctx := sdk.NewContext(ms, abci.Header{Time: time.Now().UTC()}, false, log.NewNopLogger()) + + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + + require.NoError(t, ms.LoadLatestVersion()) + + paramsKeeper := params.NewKeeper(cdc, keyParams, tKeyParams) + accKeeper := auth.NewAccountKeeper( + cdc, + keyAcc, + paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, + ) + + bankKeeper := bank.NewBaseKeeper( + accKeeper, + paramsKeeper.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + ) + + for _, addr := range addrs { + _, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + require.NoError(t, err) + } + + return testInput{ctx, accKeeper, bankKeeper} +} + +func TestHandlerMsgSendTransfersDisabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + amt := sdk.NewInt(atomsToUatoms * 5) + msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)}) + + res := handler(input.ctx, msg) + require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) +} + +func TestHandlerMsgSendTransfersEnabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, true) + + handler := NewHandler(input.bankKeeper) + amt := sdk.NewInt(atomsToUatoms * 5) + msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)}) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(amt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(amt) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) +} + +func TestHandlerMsgMultiSendTransfersDisabledBurn(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(10 * atomsToUatoms) + burnAmt := sdk.NewInt(9 * atomsToUatoms) + transAmt := sdk.NewInt(1 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}), + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(totalAmt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(transAmt) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + burn := input.accKeeper.GetAccount(input.ctx, BurnedCoinsAccAddr) + require.Equal(t, burn.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}) +} + +func TestHandlerMsgMultiSendTransfersDisabledInvalidBurn(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(15 * atomsToUatoms) + burnAmt := sdk.NewInt(10 * atomsToUatoms) + transAmt := sdk.NewInt(5 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}), + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) +} + +func TestHandlerMsgMultiSendTransfersEnabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, true) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(10 * atomsToUatoms) + outAmt := sdk.NewInt(5 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}), + bank.NewOutput(addrs[2], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(totalAmt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + out1 := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(outAmt) + require.Equal(t, out1.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + out2 := input.accKeeper.GetAccount(input.ctx, addrs[2]) + balance = initAmt.Add(outAmt) + require.Equal(t, out2.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) +} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 0fec6eb6e..3b4784fe2 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -86,19 +86,19 @@ func TestGaiaCLIMinimumFees(t *testing.T) { barAddr := f.KeyAddress(keyBar) // Send a transaction that will get rejected - success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10)) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), "-y") require.False(f.T, success) tests.WaitForNextNBlocksTM(1, f.Port) // Ensure tx w/ correct fees pass txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)) - success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees, "-y") require.True(f.T, success) tests.WaitForNextNBlocksTM(1, f.Port) // Ensure tx w/ improper fees fails txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 1)) - success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees, "-y") require.False(f.T, success) // Cleanup testing directories @@ -120,7 +120,7 @@ func TestGaiaCLIGasPrices(t *testing.T) { badGasPrice, _ := sdk.NewDecFromStr("0.000003") success, _, _ := f.TxSend( keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), - fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice))) + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice)), "-y") require.False(t, success) // wait for a block confirmation @@ -129,7 +129,7 @@ func TestGaiaCLIGasPrices(t *testing.T) { // sufficient gas prices (tx passes) success, _, _ = f.TxSend( keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), - fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)), "-y") require.True(t, success) // wait for a block confirmation @@ -171,7 +171,7 @@ func TestGaiaCLIFeesDeduction(t *testing.T) { largeCoins := sdk.TokensFromTendermintPower(10000000) success, _, _ = f.TxSend( keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins), - fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))) + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") require.False(t, success) // Wait for a block @@ -184,7 +184,7 @@ func TestGaiaCLIFeesDeduction(t *testing.T) { // test success (transfer = coins + fees) success, _, _ = f.TxSend( keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 500), - fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))) + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") require.True(t, success) f.Cleanup() @@ -208,7 +208,7 @@ func TestGaiaCLISend(t *testing.T) { // Send some tokens from one account to the other sendTokens := sdk.TokensFromTendermintPower(10) - f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens)) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Ensure account balances match expected @@ -226,7 +226,7 @@ func TestGaiaCLISend(t *testing.T) { require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) // test autosequencing - f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens)) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Ensure account balances match expected @@ -236,7 +236,7 @@ func TestGaiaCLISend(t *testing.T) { require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom)) // test memo - f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'") + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'", "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Ensure account balances match expected @@ -248,6 +248,40 @@ func TestGaiaCLISend(t *testing.T) { f.Cleanup() } +func TestGaiaCLIConfirmTx(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other + sendTokens := sdk.TokensFromTendermintPower(10) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "Y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other (cancelling confirmation) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "n") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) +} + func TestGaiaCLIGasAuto(t *testing.T) { t.Parallel() f := InitFixtures(t) @@ -265,7 +299,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { // Test failure with auto gas disabled and very little gas set by hand sendTokens := sdk.TokensFromTendermintPower(10) - success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10") + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10", "-y") require.False(t, success) // Check state didn't change @@ -273,7 +307,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) // Test failure with negative gas - success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100") + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100", "-y") require.False(t, success) // Check state didn't change @@ -281,7 +315,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) // Test failure with 0 gas - success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0") + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0", "-y") require.False(t, success) // Check state didn't change @@ -289,7 +323,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) // Enable auto gas - success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto") + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "-y") require.NotEmpty(t, stderr) require.True(t, success) cdc := app.MakeCodec() @@ -320,7 +354,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) sendTokens := sdk.TokensFromTendermintPower(10) - f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens)) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) barAcc := f.QueryAccount(barAddr) @@ -342,7 +376,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { require.True(t, success) // Create the validator - f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens)) + f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Ensure funds were deducted properly @@ -351,7 +385,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { // Ensure that validator state is as expected validator := f.QueryStakingValidator(barVal) - require.Equal(t, validator.OperatorAddr, barVal) + require.Equal(t, validator.OperatorAddress, barVal) require.True(sdk.IntEq(t, newValTokens, validator.Tokens)) // Query delegations to the validator @@ -361,7 +395,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { // unbond a single share unbondTokens := sdk.TokensFromTendermintPower(1) - success = f.TxStakingUnbond(keyBar, unbondTokens.String(), barVal) + success = f.TxStakingUnbond(keyBar, unbondTokens.String(), barVal, "-y") require.True(t, success) tests.WaitForNextNBlocksTM(1, f.Port) @@ -403,7 +437,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // Test submit generate only for submit proposal proposalTokens := sdk.TokensFromTendermintPower(5) success, stdout, stderr := f.TxGovSubmitProposal( - keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only") + keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only", "-y") require.True(t, success) require.Empty(t, stderr) msg := unmarshalStdTx(t, stdout) @@ -416,7 +450,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.True(t, success) // Create the proposal - f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens)) + f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Ensure transaction tags can be queried @@ -451,7 +485,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Run the deposit transaction - f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens)) + f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // test query deposit @@ -486,7 +520,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Vote on the proposal - f.TxGovVote(1, gov.OptionYes, keyFoo) + f.TxGovVote(1, gov.OptionYes, keyFoo, "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Query the vote @@ -513,7 +547,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) // submit a second test proposal - f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens)) + f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens), "-y") tests.WaitForNextNBlocksTM(1, f.Port) // Test limit on proposals query @@ -538,7 +572,7 @@ func TestGaiaCLIQueryTxPagination(t *testing.T) { seq := accFoo.GetSequence() for i := 1; i <= 30; i++ { - success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq)) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq), "-y") require.True(t, success) seq++ } @@ -667,7 +701,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --validate-signatures success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--validate-signatures") require.False(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", fooAddr.String()), stdout) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", fooAddr.String()), stdout) // Test sign success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name()) @@ -684,7 +718,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --validate-signatures success, stdout, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") require.True(t, success) - require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\t[OK]\n\n", fooAddr.String(), + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\t\t\t[OK]\n\n", fooAddr.String(), fooAddr.String()), stdout) // Ensure foo has right amount of funds @@ -725,7 +759,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) { barAddr := f.KeyAddress(keyBar) // Send some tokens from one account to the other - success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") require.True(t, success) tests.WaitForNextNBlocksTM(1, f.Port) @@ -738,7 +772,7 @@ func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) { defer os.Remove(unsignedTxFile.Name()) // Sign with foo's key - success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") require.True(t, success) // Write the output to disk @@ -810,7 +844,7 @@ func TestGaiaCLIMultisignSortSignatures(t *testing.T) { barAddr := f.KeyAddress(keyBar) // Send some tokens from one account to the other - success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") require.True(t, success) tests.WaitForNextNBlocksTM(1, f.Port) @@ -872,7 +906,7 @@ func TestGaiaCLIMultisign(t *testing.T) { bazAddr := f.KeyAddress(keyBaz) // Send some tokens from one account to the other - success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") require.True(t, success) tests.WaitForNextNBlocksTM(1, f.Port) @@ -890,7 +924,7 @@ func TestGaiaCLIMultisign(t *testing.T) { defer os.Remove(unsignedTxFile.Name()) // Sign with foo's key - success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") require.True(t, success) // Write the output to disk @@ -898,7 +932,7 @@ func TestGaiaCLIMultisign(t *testing.T) { defer os.Remove(fooSignatureFile.Name()) // Sign with bar's key - success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") require.True(t, success) // Write the output to disk @@ -915,7 +949,7 @@ func TestGaiaCLIMultisign(t *testing.T) { defer os.Remove(signedTxFile.Name()) // Validate the multisignature - success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "-y") require.True(t, success) // Broadcast the transaction diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go index 22fe83ef4..21419ad96 100644 --- a/cmd/gaia/cli_test/test_helpers.go +++ b/cmd/gaia/cli_test/test_helpers.go @@ -14,10 +14,11 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" - "github.com/cosmos/cosmos-sdk/client/keys" + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" appInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" @@ -70,10 +71,13 @@ type Fixtures struct { func NewFixtures(t *testing.T) *Fixtures { tmpDir, err := ioutil.TempDir("", "gaia_integration_"+t.Name()+"_") require.NoError(t, err) + servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) + p2pAddr, _, err := server.FreeTCPAddr() require.NoError(t, err) + return &Fixtures{ T: t, GDHome: filepath.Join(tmpDir, ".gaiad"), @@ -257,7 +261,7 @@ func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var ko keys.KeyOutput - err := keys.UnmarshalJSON([]byte(out), &ko) + err := clientkeys.UnmarshalJSON([]byte(out), &ko) require.NoError(f.T, err) return ko } @@ -288,9 +292,17 @@ func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } +func (f *Fixtures) txSendWithConfirm( + from string, to sdk.AccAddress, amount sdk.Coin, confirm string, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf("gaiacli tx send %s %s %v --from=%s", to, amount, f.Flags(), from) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), confirm, app.DefaultKeyPass) +} + // TxSign is gaiacli tx sign func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) { - cmd := fmt.Sprintf("gaiacli tx sign %v --name=%s %v", f.Flags(), signer, fileName) + cmd := fmt.Sprintf("gaiacli tx sign %v --from=%s %v", f.Flags(), signer, fileName) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index a5939116f..8bdd645e9 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -141,8 +141,8 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { client.LineBreak, authcmd.GetSignCommand(cdc), authcmd.GetMultiSignCommand(cdc), - authcmd.GetBroadcastCommand(cdc), - authcmd.GetEncodeCommand(cdc), + tx.GetBroadcastCommand(cdc), + tx.GetEncodeCommand(cdc), client.LineBreak, ) @@ -158,7 +158,6 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { // NOTE: If making updates here you also need to update the test helper in client/lcd/test_helper.go func registerRoutes(rs *lcd.RestServer) { registerSwaggerUI(rs) - keys.RegisterRoutes(rs.Mux, rs.CliCtx.Indent) rpc.RegisterRoutes(rs.CliCtx, rs.Mux) tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index e7a89cea7..51927592a 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -45,7 +45,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error { // load the app logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - db, err := dbm.NewGoLevelDB("gaia", dataDir) + db, err := sdk.NewLevelDB("gaia", dataDir) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go index 59e324966..095c77a9f 100644 --- a/cmd/gaia/cmd/gaiadebug/main.go +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -112,12 +112,17 @@ func runPubKeyCmd(cmd *cobra.Command, args []string) error { pubKeyI, err4 = sdk.GetValPubKeyBech32(pubkeyString) if err4 != nil { - return fmt.Errorf(`Expected hex, base64, or bech32. Got errors: - hex: %v, - base64: %v - bech32 acc: %v - bech32 val: %v - `, err, err2, err3, err4) + var err5 error + pubKeyI, err5 = sdk.GetConsPubKeyBech32(pubkeyString) + if err5 != nil { + return fmt.Errorf(`Expected hex, base64, or bech32. Got errors: + hex: %v, + base64: %v + bech32 Acc: %v + bech32 Val: %v + bech32 Cons: %v`, + err, err2, err3, err4, err5) + } } } diff --git a/cmd/gaia/cmd/gaiakeyutil/main.go b/cmd/gaia/cmd/gaiakeyutil/main.go index 53320e823..46eefc69c 100644 --- a/cmd/gaia/cmd/gaiakeyutil/main.go +++ b/cmd/gaia/cmd/gaiakeyutil/main.go @@ -5,10 +5,18 @@ import ( "fmt" "os" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/libs/bech32" ) -var bech32Prefixes = []string{"cosmos", "cosmospub", "cosmosvaloper", "cosmosvaloperpub", "cosmosvalcons", "cosmosvalconspub"} +var bech32Prefixes = []string{ + sdk.Bech32PrefixAccAddr, + sdk.Bech32PrefixAccPub, + sdk.Bech32PrefixValAddr, + sdk.Bech32PrefixValPub, + sdk.Bech32PrefixConsAddr, + sdk.Bech32PrefixConsPub, +} func main() { if len(os.Args) < 2 { diff --git a/cmd/gaia/cmd/gaiareplay/main.go b/cmd/gaia/cmd/gaiareplay/main.go index ef6bcb139..78cacec36 100644 --- a/cmd/gaia/cmd/gaiareplay/main.go +++ b/cmd/gaia/cmd/gaiareplay/main.go @@ -15,7 +15,6 @@ import ( abci "github.com/tendermint/tendermint/abci/types" bcm "github.com/tendermint/tendermint/blockchain" cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/proxy" tmsm "github.com/tendermint/tendermint/state" tm "github.com/tendermint/tendermint/types" @@ -23,6 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -72,7 +72,7 @@ func run(rootDir string) { // App DB // appDB := dbm.NewMemDB() fmt.Println("Opening app database") - appDB, err := dbm.NewGoLevelDB("application", dataDir) + appDB, err := sdk.NewLevelDB("application", dataDir) if err != nil { panic(err) } @@ -80,14 +80,14 @@ func run(rootDir string) { // TM DB // tmDB := dbm.NewMemDB() fmt.Println("Opening tendermint state database") - tmDB, err := dbm.NewGoLevelDB("state", dataDir) + tmDB, err := sdk.NewLevelDB("state", dataDir) if err != nil { panic(err) } // Blockchain DB fmt.Println("Opening blockstore database") - bcDB, err := dbm.NewGoLevelDB("blockstore", dataDir) + bcDB, err := sdk.NewLevelDB("blockstore", dataDir) if err != nil { panic(err) } diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index e7779a343..6ce4badec 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -24,6 +24,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" + kbkeys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -129,9 +130,20 @@ following delegation and commission default parameters: return err } + info, err := txBldr.Keybase().Get(name) + if err != nil { + return err + } + + if info.GetType() == kbkeys.TypeOffline || info.GetType() == kbkeys.TypeMulti { + fmt.Println("Offline key passed in. Use `gaiacli tx sign` command to sign:") + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true) + } + // write the unsigned transaction to the buffer w := bytes.NewBuffer([]byte{}) cliCtx = cliCtx.WithOutput(w) + if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil { return err } @@ -163,6 +175,7 @@ following delegation and commission default parameters: fmt.Fprintf(os.Stderr, "Genesis transaction written to %q\n", outputDocument) return nil + }, } diff --git a/crypto/keys/codec.go b/crypto/keys/codec.go index 737836f91..d85eeb342 100644 --- a/crypto/keys/codec.go +++ b/crypto/keys/codec.go @@ -1,9 +1,10 @@ package keys import ( + amino "github.com/tendermint/go-amino" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/crypto/encoding/amino" ) var cdc = amino.NewCodec() @@ -15,4 +16,5 @@ func init() { cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil) cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil) cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil) + cdc.RegisterConcrete(multiInfo{}, "crypto/keys/multiInfo", nil) } diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index f79ed2025..e4ff5651c 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -7,7 +7,7 @@ import ( "reflect" "strings" - "errors" + "github.com/pkg/errors" "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" @@ -15,10 +15,10 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/go-bip39" + bip39 "github.com/cosmos/go-bip39" tmcrypto "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/encoding/amino" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" ) @@ -152,12 +152,18 @@ func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, return kb.writeLedgerKey(name, pub, *hdPath), nil } -// CreateOffline creates a new reference to an offline keypair -// It returns the created key info +// CreateOffline creates a new reference to an offline keypair. It returns the +// created key info. func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) { return kb.writeOfflineKey(name, pub), nil } +// CreateMulti creates a new reference to a multisig (offline) keypair. It +// returns the created key info. +func (kb dbKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) { + return kb.writeMultisigKey(name, pub), nil +} + func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) { // create master key and derive first key: masterPriv, ch := hd.ComputeMastersFromSeed(seed) @@ -222,7 +228,9 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t if err != nil { return } + var priv tmcrypto.PrivKey + switch info.(type) { case localInfo: linfo := info.(localInfo) @@ -230,39 +238,49 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t err = fmt.Errorf("private key not available") return } + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, nil, err } + case ledgerInfo: linfo := info.(ledgerInfo) priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path) if err != nil { return } - case offlineInfo: - linfo := info.(offlineInfo) - _, err := fmt.Fprintf(os.Stderr, "Bytes to sign:\n%s", msg) + + case offlineInfo, multiInfo: + _, err := fmt.Fprintf(os.Stderr, "Message to sign:\n\n%s\n", msg) if err != nil { return nil, nil, err } + buf := bufio.NewReader(os.Stdin) _, err = fmt.Fprintf(os.Stderr, "\nEnter Amino-encoded signature:\n") if err != nil { return nil, nil, err } + // Will block until user inputs the signature signed, err := buf.ReadString('\n') if err != nil { return nil, nil, err } - cdc.MustUnmarshalBinaryLengthPrefixed([]byte(signed), sig) - return sig, linfo.GetPubKey(), nil + + if err := cdc.UnmarshalBinaryLengthPrefixed([]byte(signed), sig); err != nil { + return nil, nil, errors.Wrap(err, "failed to decode signature") + } + + return sig, info.GetPubKey(), nil } + sig, err = priv.Sign(msg) if err != nil { return nil, nil, err } + pub = priv.PubKey() return sig, pub, nil } @@ -272,7 +290,9 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr if err != nil { return nil, err } + var priv tmcrypto.PrivKey + switch info.(type) { case localInfo: linfo := info.(localInfo) @@ -284,11 +304,11 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr if err != nil { return nil, err } - case ledgerInfo: - return nil, errors.New("only works on local private keys") - case offlineInfo: + + case ledgerInfo, offlineInfo, multiInfo: return nil, errors.New("only works on local private keys") } + return priv, nil } @@ -426,6 +446,12 @@ func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info { return info } +func (kb dbKeybase) writeMultisigKey(name string, pub tmcrypto.PubKey) Info { + info := NewMultiInfo(name, pub) + kb.writeInfo(name, info) + return info +} + func (kb dbKeybase) writeInfo(name string, info Info) { // write the info by key key := infoKey(name) diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 6374eaafa..09829e1dd 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -74,9 +74,9 @@ func TestCreateLedger(t *testing.T) { pk, err = sdk.Bech32ifyAccPub(pubKey) assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk) - linfo := restoredKey.(ledgerInfo) - assert.Equal(t, "44'/118'/3'/0/1", linfo.GetPath().String()) - + path, err := restoredKey.GetPath() + assert.NoError(t, err) + assert.Equal(t, "44'/118'/3'/0/1", path.String()) } // TestKeyManagement makes sure we can manipulate these keys well diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go index ce3a371c8..6f0b68523 100644 --- a/crypto/keys/lazy_keybase.go +++ b/crypto/keys/lazy_keybase.go @@ -1,11 +1,13 @@ package keys import ( + "fmt" + "github.com/tendermint/tendermint/crypto" - dbm "github.com/tendermint/tendermint/libs/db" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) var _ Keybase = lazyKeybase{} @@ -17,150 +19,180 @@ type lazyKeybase struct { // New creates a new instance of a lazy keybase. func New(name, dir string) Keybase { + if err := cmn.EnsureDir(dir, 0700); err != nil { + panic(fmt.Sprintf("failed to create Keybase directory: %s", err)) + } + return lazyKeybase{name: name, dir: dir} } func (lkb lazyKeybase) List() ([]Info, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).List() } func (lkb lazyKeybase) Get(name string) (Info, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).Get(name) } -func (lkb lazyKeybase) GetByAddress(address types.AccAddress) (Info, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) +func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) { + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).GetByAddress(address) } func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() + return newDbKeybase(db).Delete(name, passphrase, skipPass) } func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, nil, err } defer db.Close() + return newDbKeybase(db).Sign(name, passphrase, msg) } func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, "", err } defer db.Close() + return newDbKeybase(db).CreateMnemonic(name, language, passwd, algo) } func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index) } func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params) } func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, account uint32, index uint32) (info Info, err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).CreateLedger(name, algo, account, index) } func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).CreateOffline(name, pubkey) } +func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) { + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) + if err != nil { + return nil, err + } + defer db.Close() + + return newDbKeybase(db).CreateMulti(name, pubkey) +} + func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() + return newDbKeybase(db).Update(name, oldpass, getNewpass) } func (lkb lazyKeybase) Import(name string, armor string) (err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() + return newDbKeybase(db).Import(name, armor) } func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() + return newDbKeybase(db).ImportPubKey(name, armor) } func (lkb lazyKeybase) Export(name string) (armor string, err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return "", err } defer db.Close() + return newDbKeybase(db).Export(name) } func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return "", err } defer db.Close() + return newDbKeybase(db).ExportPubKey(name) } func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) { - db, err := dbm.NewGoLevelDB(lkb.name, lkb.dir) + db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() + return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase) } diff --git a/crypto/keys/output.go b/crypto/keys/output.go new file mode 100644 index 000000000..22a614ddb --- /dev/null +++ b/crypto/keys/output.go @@ -0,0 +1,111 @@ +package keys + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// KeyOutput defines a structure wrapping around an Info object used for output +// functionality. +type KeyOutput struct { + Name string `json:"name"` + Type string `json:"type"` + Address string `json:"address"` + PubKey string `json:"pubkey"` + Mnemonic string `json:"mnemonic,omitempty"` + Threshold uint `json:"threshold,omitempty"` + PubKeys []multisigPubKeyOutput `json:"pubkeys,omitempty"` +} + +type multisigPubKeyOutput struct { + Address string `json:"address"` + PubKey string `json:"pubkey"` + Weight uint `json:"weight"` +} + +// Bech32KeysOutput returns a slice of KeyOutput objects, each with the "acc" +// Bech32 prefixes, given a slice of Info objects. It returns an error if any +// call to Bech32KeyOutput fails. +func Bech32KeysOutput(infos []Info) ([]KeyOutput, error) { + kos := make([]KeyOutput, len(infos)) + for i, info := range infos { + ko, err := Bech32KeyOutput(info) + if err != nil { + return nil, err + } + kos[i] = ko + } + + return kos, nil +} + +// Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. +func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { + consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + + bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) + if err != nil { + return KeyOutput{}, err + } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: consAddr.String(), + PubKey: bechPubKey, + }, nil +} + +// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. +func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { + valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + + bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) + if err != nil { + return KeyOutput{}, err + } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: valAddr.String(), + PubKey: bechPubKey, + }, nil +} + +// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. If the +// public key is a multisig public key, then the threshold and constituent +// public keys will be added. +func Bech32KeyOutput(info Info) (KeyOutput, error) { + accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey()) + if err != nil { + return KeyOutput{}, err + } + + ko := KeyOutput{ + Name: info.GetName(), + Type: info.GetType().String(), + Address: accAddr.String(), + PubKey: bechPubKey, + } + + if mInfo, ok := info.(multiInfo); ok { + pubKeys := make([]multisigPubKeyOutput, len(mInfo.PubKeys)) + + for i, pk := range mInfo.PubKeys { + accAddr := sdk.AccAddress(pk.PubKey.Address().Bytes()) + + bechPubKey, err := sdk.Bech32ifyAccPub(pk.PubKey) + if err != nil { + return KeyOutput{}, err + } + + pubKeys[i] = multisigPubKeyOutput{accAddr.String(), bechPubKey, pk.Weight} + } + + ko.Threshold = mInfo.Threshold + ko.PubKeys = pubKeys + } + + return ko, nil +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go index 8a4ff7fc2..1459e1ae0 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -1,7 +1,10 @@ package keys import ( + "fmt" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/types" @@ -37,6 +40,9 @@ type Keybase interface { // CreateOffline creates, stores, and returns a new offline key reference CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) + // CreateMulti creates, stores, and returns a new multsig (offline) key reference + CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) + // The following operations will *only* work on locally-stored keys Update(name, oldpass string, getNewpass func() (string, error)) error Import(name string, armor string) (err error) @@ -59,12 +65,14 @@ const ( TypeLocal KeyType = 0 TypeLedger KeyType = 1 TypeOffline KeyType = 2 + TypeMulti KeyType = 3 ) var keyTypes = map[KeyType]string{ TypeLocal: "local", TypeLedger: "ledger", TypeOffline: "offline", + TypeMulti: "multi", } // String implements the stringer interface for KeyType. @@ -82,11 +90,16 @@ type Info interface { GetPubKey() crypto.PubKey // Address GetAddress() types.AccAddress + // Bip44 Path + GetPath() (*hd.BIP44Params, error) } -var _ Info = &localInfo{} -var _ Info = &ledgerInfo{} -var _ Info = &offlineInfo{} +var ( + _ Info = &localInfo{} + _ Info = &ledgerInfo{} + _ Info = &offlineInfo{} + _ Info = &multiInfo{} +) // localInfo is the public information about a locally stored key type localInfo struct { @@ -119,6 +132,10 @@ func (i localInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } +func (i localInfo) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} + // ledgerInfo is the public information about a Ledger key type ledgerInfo struct { Name string `json:"name"` @@ -150,8 +167,9 @@ func (i ledgerInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } -func (i ledgerInfo) GetPath() hd.BIP44Params { - return i.Path +func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) { + tmp := i.Path + return &tmp, nil } // offlineInfo is the public information about an offline key @@ -183,6 +201,58 @@ func (i offlineInfo) GetAddress() types.AccAddress { return i.PubKey.Address().Bytes() } +func (i offlineInfo) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} + +type multisigPubKeyInfo struct { + PubKey crypto.PubKey `json:"pubkey"` + Weight uint `json:"weight"` +} +type multiInfo struct { + Name string `json:"name"` + PubKey crypto.PubKey `json:"pubkey"` + Threshold uint `json:"threshold"` + PubKeys []multisigPubKeyInfo `json:"pubkeys"` +} + +func NewMultiInfo(name string, pub crypto.PubKey) Info { + multiPK := pub.(multisig.PubKeyMultisigThreshold) + + pubKeys := make([]multisigPubKeyInfo, len(multiPK.PubKeys)) + for i, pk := range multiPK.PubKeys { + // TODO: Recursively check pk for total weight? + pubKeys[i] = multisigPubKeyInfo{pk, 1} + } + + return &multiInfo{ + Name: name, + PubKey: pub, + Threshold: multiPK.K, + PubKeys: pubKeys, + } +} + +func (i multiInfo) GetType() KeyType { + return TypeMulti +} + +func (i multiInfo) GetName() string { + return i.Name +} + +func (i multiInfo) GetPubKey() crypto.PubKey { + return i.PubKey +} + +func (i multiInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + +func (i multiInfo) GetPath() (*hd.BIP44Params, error) { + return nil, fmt.Errorf("BIP44 Paths are not available for this type") +} + // encoding info func writeInfo(i Info) []byte { return cdc.MustMarshalBinaryLengthPrefixed(i) diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go index 621395b0c..6475eb890 100644 --- a/crypto/keys/types_test.go +++ b/crypto/keys/types_test.go @@ -4,10 +4,11 @@ import ( "encoding/hex" "testing" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" ) func Test_writeReadLedgerInfo(t *testing.T) { @@ -20,7 +21,10 @@ func Test_writeReadLedgerInfo(t *testing.T) { tmpKey, *hd.NewFundraiserParams(5, 1)} assert.Equal(t, TypeLedger, lInfo.GetType()) - assert.Equal(t, "44'/118'/5'/0/1", lInfo.GetPath().String()) + + path, err := lInfo.GetPath() + assert.NoError(t, err) + assert.Equal(t, "44'/118'/5'/0/1", path.String()) assert.Equal(t, "cosmospub1addwnpepqddddqg2glc8x4fl7vxjlnr7p5a3czm5kcdp4239sg6yqdc4rc2r5wmxv8p", types.MustBech32ifyAccPub(lInfo.GetPubKey())) @@ -36,5 +40,8 @@ func Test_writeReadLedgerInfo(t *testing.T) { assert.Equal(t, lInfo.GetType(), restoredInfo.GetType()) assert.Equal(t, lInfo.GetPubKey(), restoredInfo.GetPubKey()) - assert.Equal(t, lInfo.GetPath(), restoredInfo.(ledgerInfo).GetPath()) + restoredPath, err := restoredInfo.GetPath() + assert.NoError(t, err) + + assert.Equal(t, path, restoredPath) } diff --git a/crypto/ledger_mock.go b/crypto/ledger_mock.go index df7cf6d4e..75e6941da 100644 --- a/crypto/ledger_mock.go +++ b/crypto/ledger_mock.go @@ -3,10 +3,12 @@ package crypto import ( + "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/go-bip39" + bip39 "github.com/cosmos/go-bip39" "github.com/pkg/errors" secp256k1 "github.com/tendermint/btcd/btcec" "github.com/tendermint/tendermint/crypto" @@ -77,3 +79,9 @@ func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message [ sig2 := btcec.Signature{R: sig.R, S: sig.S} return sig2.Serialize(), nil } + +// ShowAddressSECP256K1 shows the address for the corresponding bip32 derivation path +func (mock LedgerSECP256K1Mock) ShowAddressSECP256K1(bip32Path []uint32, hrp string) error { + fmt.Printf("Request to show address for %v at %v", hrp, bip32Path) + return nil +} diff --git a/crypto/ledger_real.go b/crypto/ledger_real.go index 7aa4f8a84..93837e389 100644 --- a/crypto/ledger_real.go +++ b/crypto/ledger_real.go @@ -2,7 +2,7 @@ package crypto -import ledger "github.com/zondax/ledger-cosmos-go" +import ledger "github.com/cosmos/ledger-cosmos-go" // If ledger support (build tag) has been enabled, which implies a CGO dependency, // set the discoverLedger function which is responsible for loading the Ledger diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go index c19c82fd8..fae0c1569 100644 --- a/crypto/ledger_secp256k1.go +++ b/crypto/ledger_secp256k1.go @@ -5,12 +5,14 @@ import ( "os" "github.com/btcsuite/btcd/btcec" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + tmbtcec "github.com/tendermint/btcd/btcec" tmcrypto "github.com/tendermint/tendermint/crypto" tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" - - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" ) var ( @@ -31,6 +33,7 @@ type ( Close() error GetPublicKeySECP256K1([]uint32) ([]byte, error) SignSECP256K1([]uint32, []byte) ([]byte, error) + ShowAddressSECP256K1([]uint32, string) error } // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we @@ -61,6 +64,26 @@ func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params) (tmcrypto.PrivKey, error) { return PrivKeyLedgerSecp256k1{pubKey, path}, nil } +// LedgerShowAddress triggers a ledger device to show the corresponding address. +func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) error { + device, err := getLedgerDevice() + if err != nil { + return err + } + defer warnIfErrors(device.Close) + + pubKey, err := getPubKey(device, path) + if err != nil { + return err + } + + if pubKey != expectedPubKey { + return fmt.Errorf("pubkey does not match, Check this is the same device") + } + + return device.ShowAddressSECP256K1(path.DerivationPath(), types.Bech32PrefixAccAddr) +} + // PubKey returns the cached public key. func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey { return pkl.CachedPubKey diff --git a/docs/_attic/sdk/core/examples/app1.go b/docs/_attic/sdk/core/examples/app1.go index 8cf30f4bb..c103b5a29 100644 --- a/docs/_attic/sdk/core/examples/app1.go +++ b/docs/_attic/sdk/core/examples/app1.go @@ -151,7 +151,7 @@ func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Resul } // Deduct msg amount from sender account. - senderCoins := acc.Coins.Minus(amt) + senderCoins := acc.Coins.Sub(amt) // If any coin has negative amount, return insufficient coins error. if senderCoins.IsAnyNegative() { @@ -188,7 +188,7 @@ func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result { } // Add amount to receiver's old coins - receiverCoins := acc.Coins.Plus(amt) + receiverCoins := acc.Coins.Add(amt) // Update receiver account acc.Coins = receiverCoins diff --git a/docs/gaia/delegator-guide-cli.md b/docs/gaia/delegator-guide-cli.md index 43d7ed86c..26ae01abc 100644 --- a/docs/gaia/delegator-guide-cli.md +++ b/docs/gaia/delegator-guide-cli.md @@ -457,9 +457,9 @@ If you do not have a ledger device and want to interact with your private key on ```bash // Bond Atoms -// ex value for flags: =10000stake, =cosmosvaloper18thamkhnj9wz8pa4nhnp9rldprgant57pk2m8s, =0.001stake +// ex value for flags: =10000stake, =cosmosvaloper18thamkhnj9wz8pa4nhnp9rldprgant57pk2m8s, =0.001stake, =cosmos10snjt8dmpr5my0h76xj48ty80uzwhraqalu4eg -gaiacli tx staking delegate --from --gas auto --gas-prices --generate-only > unsignedTX.json +gaiacli tx staking delegate --from --gas auto --gas-prices --generate-only > unsignedTX.json ``` Then, copy `unsignedTx.json` and transfer it (e.g. via USB) to the offline computer. If it is not done already, [create an account on the offline computer](#using-a-computer). For additional security, you can double check the parameters of your transaction before signing it using the following command: diff --git a/docs/gaia/deploy-testnet.md b/docs/gaia/deploy-testnet.md index a91c6d09c..2aa2f692e 100644 --- a/docs/gaia/deploy-testnet.md +++ b/docs/gaia/deploy-testnet.md @@ -10,6 +10,16 @@ Supporting code can be found in the [networks directory](https://github.com/cosm > NOTE: The `remote` network bootstrapping may be out of sync with the latest releases and is not to be relied upon. +## Available Docker images + +In case you need to use or deploy gaia as a container you could skip the `build` steps and use the official images, $TAG stands for the version you are interested in: +- `docker run -it -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli tendermint:$TAG gaiad init` +- `docker run -it -p 26657:26657 -p 26656:26656 -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli tendermint:$TAG gaiad start` +- ... +- `docker run -it -v ~/.gaiad:/root/.gaiad -v ~/.gaiacli:/root/.gaiacli tendermint:$TAG gaiacli version` + +The same images can be used to build your own docker-compose stack. + ## Single-node, local, manual testnet This guide helps you create a single validator node that runs a network locally for testing and other development related uses. @@ -34,7 +44,7 @@ gaiacli keys add validator # Add that key into the genesis.app_state.accounts array in the genesis file # NOTE: this command lets you set the number of coins. Make sure this account has some coins # with the genesis.app_state.staking.params.bond_denom denom, the default is staking -gaiad add-genesis-account $(gaiacli keys show validator -a) 1000stake,1000validatortoken +gaiad add-genesis-account $(gaiacli keys show validator -a) 1000000000stake,1000000000validatortoken # Generate the transaction that creates your validator gaiad gentx --name validator diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index b43f638be..198911f67 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -683,7 +683,7 @@ gaiacli query distr rewards Multisig transactions require signatures of multiple private keys. Thus, generating and signing a transaction from a multisig account involve cooperation among the parties involved. A multisig transaction can be initiated by any of the key holders, and at least one of them would need to -import other parties' public keys into their local database and generate a multisig public key +import other parties' public keys into their Keybase and generate a multisig public key in order to finalize and broadcast the transaction. For example, given a multisig key comprising the keys `p1`, `p2`, and `p3`, each of which is held @@ -692,17 +692,17 @@ generate the multisig account public key: ``` gaiacli keys add \ - --pubkey=cosmospub1addwnpepqtd28uwa0yxtwal5223qqr5aqf5y57tc7kk7z8qd4zplrdlk5ez5kdnlrj4 \ - p2 + p2 \ + --pubkey=cosmospub1addwnpepqtd28uwa0yxtwal5223qqr5aqf5y57tc7kk7z8qd4zplrdlk5ez5kdnlrj4 gaiacli keys add \ - --pubkey=cosmospub1addwnpepqgj04jpm9wrdml5qnss9kjxkmxzywuklnkj0g3a3f8l5wx9z4ennz84ym5t \ - p3 + p3 \ + --pubkey=cosmospub1addwnpepqgj04jpm9wrdml5qnss9kjxkmxzywuklnkj0g3a3f8l5wx9z4ennz84ym5t gaiacli keys add \ - --multisig-threshold=2 + p1p2p3 \ + --multisig-threshold=2 \ --multisig=p1,p2,p3 - p1p2p3 ``` A new multisig public key `p1p2p3` has been stored, and its address will be @@ -712,6 +712,15 @@ used as signer of multisig transactions: gaiacli keys show --address p1p2p3 ``` +You may also view multisig threshold, pubkey constituents and respective weights +by viewing the JSON output of the key or passing the `--show-multisig` flag: + +```bash +gaiacli keys show p1p2p3 -o json + +gaiacli keys show p1p2p3 --show-multisig +``` + The first step to create a multisig transaction is to initiate it on behalf of the multisig address created above: @@ -726,10 +735,10 @@ The file `unsignedTx.json` contains the unsigned transaction encoded in JSON. ```bash gaiacli tx sign \ + unsignedTx.json \ --multisig= \ - --name=p1 \ + --from=p1 \ --output-document=p1signature.json \ - unsignedTx.json ``` Once the signature is generated, `p1` transmits both `unsignedTx.json` and @@ -738,10 +747,10 @@ respective signature: ```bash gaiacli tx sign \ + unsignedTx.json \ --multisig= \ - --name=p2 \ + --from=p2 \ --output-document=p2signature.json \ - unsignedTx.json ``` `p1p2p3` is a 2-of-3 multisig key, therefore one additional signature diff --git a/docs/gaia/launch/blog-2-cn.md b/docs/gaia/launch/blog-2-cn.md new file mode 100644 index 000000000..2c9dd5482 --- /dev/null +++ b/docs/gaia/launch/blog-2-cn.md @@ -0,0 +1,116 @@ +# **Cosmos主网上线三部曲** + + + + +Cosmos主网启动将分成3个阶段分布完成,下面我们将具体介绍每一阶段的预期目标。 + + + +## **🚨第一阶段:网络逐步趋于稳定🚨** + + + +在第一阶段,主网可能会不太稳定,也许会出现暂停或其他故障,可能需要Cosmos主网验证人和全节点运营者们一起来协助修复。在网络趋于稳定过程中,出现此类故障并不意外。 + + +**状态修改和主网启动:** + +区块链的核心价值之一是其不可篡改性,也就是说我们不会通过回滚来修改过去的状态记录。最终,这种不可篡改的理念在软件共识协议层面得到了支持,并最终在社区参与者之间形成了社会契约。 + + + +也就是说,Cosmos Hub的底层技术开发是能够实现低难度的分叉和回滚的, 我们已经看到社区在测试网上做过多次相应的演练。这些技术也会在主网上应用,用以最终抵御卡特尔风险的发生。 + + + +回滚网络状态通常被认为是非常严重的问题,因为这将损害网络的经济最终性。因此,回滚网络状态只能在极端状态下使用,比如以太网的DAO硬分叉,也就是说,在Cosmos Hub主网启动初期,转账交易不会开启,因此回滚的危害性很小,因为状态转换远比“经济最终性”的影响低。 如果需要,比如发现漏洞,我们可以将过去某一个高度时的网络状态导出,然后重启网络,就如同我们在测试网上演练过的那样。 + + + +一旦链上治理决定开启交易功能,全网将会遵从经济最终性。 + + + +总而言之,如果在链上交易功能开启之前,Cosmos Hub发现任何错误或漏洞,那么用户可期望回滚至任意状态,甚至创世块。 + + + +一旦链上交易功能开始后,状态回滚方式将很难被采纳。 + + + +**对开发人员的建议**:Cosmos主网启动是投资者参与主网的第一阶段。作为分布式应用的开发人员,您可能是Cosmos-SDK框架或Tendermint Core的用户。开发者基于[Cosmos-SDK](https://cosmos.network/docs/)或[Tendermint](https://tendermint.com/docs/)的应用开发进度现阶段应该不受Cosmos Hub的影响,但如果您的项目需要使用[IBC](https://blog.cosmos.network/developer-deep-dive-cosmos-ibc-5855aaf183fe)(链间通信协议),则必须要等到第三阶段或参与即将开始的IBC测试网络。 + + + +**对于用户的建议**:在此阶段,我们强烈建议您不要交易Atoms(其实法律合同也限定不能交易),因为在这个阶段仍然存在状态回滚的风险。 + + + +但是,您可以通过下面链接的[CLI指南](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/gaia/delegator-guide-cli.md)和视频教程的指导,在此阶段将Atoms通证委托给验证人。当然,如果发生状态修改,那么所有收益(包括手续费和通胀收益)都将丢失。Voyager是用于与Cosmos Hub交互的图形化客户端,目前处于alpha版,正在持续开发中。一旦Voager beta版上线,并可以安全使用,我们将另行公告。 + + +CLI指南和视频教程:https://v.qq.com/x/page/q08425izfhi.html + + + +## 第二阶段:链上交易开启 + +**摘要:** + +一旦我们认为主网足够稳定,在链上抵押了Atom的通证持有者将会通过链上治理过程投票决定是否开启链上交易。 + + + + +Cosmos浏览器是查看治理提案状态的最佳途径,可以在我们的主网启动页面上找到多款[Cosmos浏览器](https://cosmos.network/launch)。 + + + +对用户来说:如果提案被社区接受,并且链上交易开启,您就可以在链上转移您的Atom了。 + + + +## 第三阶段:启用IBC协议 + + + + + +**摘要:** + +第三阶段我们将会发布[IBC协议](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/ibc/overview.md),并且对是否将其纳入Cosmos-SDK的核心模块库进行链上治理投票。 + + + +**对开发人员的建议**:使用Cosmos-SDK或Tendermint BFT构建的应用专有链将能够连接到 Cosmos Hub,并与连接到Hubs上的其他任意区块链进行跨链交互。 + + + +**对用户的建议**:您可以将各种通证和NFT直接从采用IBC协议连接的一个链转移到另一个通过IBC协议连接的链,而无需通过一个第三方中心化平台。 + + + +## 验证人提示: 提交您的主网gentx文件 + +1. 验证人应该已经生成并且安全的保存好用于主网验证的共识密钥。 + +2. 验证人根据自己获得ATOM通证的来源,比如募资参与者或权益争夺活动的获奖者,准备好在创世文件(Genesis)中需要签署的交易。 + +3. 一旦创世通证分配计划发布,我们将开始征集gentx文件。 + + + +## 总结 + +Cosmos旨在建立所有区块链间的桥梁,最终建立一个区块链互联网。然而,路漫漫其修远兮,我们还有很长的路要走。主网上线之后,我们需要完成与整个通证经济世界的深度融合,很多的工作要做等着我们去完成。正如肯尼迪在逆境中曾经说过的那样: + + + +“我们选择去月球,不是因为很容易,而是因为很难......” + + + +走更崎岖的路,才会看见更美的风景! +**** diff --git a/docs/gaia/launch/blog-2-en.md b/docs/gaia/launch/blog-2-en.md new file mode 100644 index 000000000..2b3ed4852 --- /dev/null +++ b/docs/gaia/launch/blog-2-en.md @@ -0,0 +1,67 @@ +# The 3 Phases of the Cosmos Hub Mainnet +## Post-Mainnet Development Roadmap & Expectations for Users + +The launch of the Cosmos Hub mainnet is expected to happen in phases. Here we outline what to expect in each phase. + +# 🚨Phase I: Network Gains Stability 🚨 + +In the first phase, the network is likely to be unstable; it may experience halts or other forms of failure requiring intervention and coordination among Cosmos Hub validators and full node operators to deploy a fix. This type of failure is not unexpected while the network gains stability. + +## State Reversions and Mainnet launch + +One of the core ideologies around blockchains is immutability. This is the idea that we don't go +back and edit past state transitions. While this notion of immutability is implemented directly via consensus protocols in the software, it is ultimately upheld by social contract among participants. + +That said, the technology underlying the Cosmos Hub was intentionally developed to enable low-friction forks and rollbacks. We’ve seen the community practice these techniques numerous times on the test networks. It’s likely they will need to be used on a mainnet as well. Ultimately, they are a countervailing force to the risk of cartel takeover. + +Reverting state is often seen as highly grievous, as it compromises the network’s economic finality. Hence it should only be used in extreme conditions, as witnessed in the case of Ethereum with the DAO Hard Fork. That said, in the early days of the Cosmos Hub network, transfers will not be active, and hence the severity of state reversions will be reduced, as state transitions will be much less “economically final”. If necessary in case of bugs, the state can be exported from a past height and the network restarted, as practiced on the testnets. + +Once governance chooses to enable transfers, the importance of economic finality must be respected by the network. + +To summarize, if there are errors or vulnerabilities in the Cosmos Hub in the days before transfers are enabled, users should expect arbitrary state rollbacks even to genesis. + +Once transfers are enabled, state rollbacks will be much more difficult to justify. + +**What this means for developers:** The Cosmos mainnet launch is the first phase in which fundraiser participants will be working together to operate the software. As a decentralized application developer, you are likely a user of either the [Cosmos-SDK framework](https://cosmos.network/docs/) or [Tendermint Core](https://tendermint.com/docs/). The progress of your Cosmos-SDK or Tendermint-based application should be independent of the Cosmos Hub roadmap. However, if your project requires the use of [Inter-Blockchain Communication][blog post], you must wait until Phase III, or participate in the IBC testnets that will begin shortly. + +**What this means for users:** In this phase, we strongly recommend that you do not arrange to trade Atoms (eg. by legal contract as they will not be transferable yet) as there is the risk of state being reverted. + +You can, however, safely delegate Atoms to validators in this phase by following the CLI guideline and video tutorial linked below. Of course, in the event of a state reversion, any earned fees and inflation may be lost. Note that only `gaiacli` should be used for making transactions. Voyager, the GUI for interacting with the Cosmos Hub, is currently in alpha and undergoing development. A separate announcement will be made once Voyager is safer for use. + +CLI Guide 🔗: [github.com/cosmos/cosmos-sdk/…/delegator-guide-cli.md](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/gaia/delegator-guide-cli.md) + +**Watch CLI delegation tutorial:** [Cosmos YouTube](https://www.youtube.com/watch?v=ydZw6o6Mzy0) + +# Phase II: Transfers Enabled + +**Summary:** Once mainnet is deemed sufficiently stable, bonded Atom holders will vote to decide whether or not Atom transfers should be enabled. This procedure will happen through on-chain governance. + +The best way to check on the status of governance proposals is to view them through Cosmos explorers. A list of explorers can be found on the launch page: [cosmos.network/launch](https://cosmos.network/launch). + +**What this means for users:** If the proposal is accepted and transfers are enabled, then it becomes possible to transfer Atoms. + +# Phase III: IBC Enabled + +**Summary:** In Phase III, the [IBC protocol][ibc] is released and Atom holders vote via on-chain governance on whether or not to enable it as part of the core module library within the Cosmos-SDK. + +**What this means for developers:** Application-specific blockchains that are built using the Cosmos-SDK or Tendermint BFT will be able to connect to the Hub and interoperate/compose with all of the other blockchains that are connected to it. + +**What this means for users:** You will be able to transfer various tokens and NFTs directly from one IBC-connected chain to another IBC-connected chain without going through a centralized +third-party platform. + +# Housekeeping for Validators: Submitting a `gentx` for mainnet + +1. You should have generated and secured the validator consensus key you are going to be validating under during mainnet. +2. Be prepared to sign a transaction under an address in the genesis file either from the fundraiser or Game of Stakes depending on where you received your ATOM allocation. +3. We will begin collecting Gentxs for mainnet once the recommended genesis allocations are published. + +# In Closing +The Cosmos mission is to build bridges to connect all blockchains—to build an Internet of Blockchains. Clearly, we have a long road of development ahead of us. And after mainnet, the real work to a world of deeply integrated token economies is still ahead of us. But as John Fitzgerald Kennedy once said in the face of adversity: + +*“We choose to go to the moon...not because they are easy, but because they are hard….”* + +To the Moon 🚀 + + +[blog post]: [https://blog.cosmos.network/developer-deep-dive-cosmos-ibc-5855aaf183fe] +[ibc]: [https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/ibc/overview.md] diff --git a/docs/gaia/launch/blog-2-kr.md b/docs/gaia/launch/blog-2-kr.md new file mode 100644 index 000000000..1ba25824c --- /dev/null +++ b/docs/gaia/launch/blog-2-kr.md @@ -0,0 +1,74 @@ +# 코스모스 허브 메인넷의 세 가지 단계 +## 메인넷 후 개발 로드맵과 유저들을 위한 필수 정보 + +코스모스 허브의 런칭은 단계별로 나뉘어 진행될 계획입니다. 다음은 각 단계별로 어떤 사항들이 진행될지에 대한 요약입니다. + +# 🚨1단계: 네트워크 안정성 확보 🚨 + +메인넷의 최초 단계인 1단계에서 코스모스 네트워크는 다소 불안정할 수 있습니다. 메인넷 초기에는 네트워크 중단 같은 문제들이 발생할 확률이 존재합니다. + +혹시라도 발생할 수 있는 문제들을 해결하기 위해서는 코스모스 허브 검증인 간, 풀노드 운영자 등 참여자들 간의 긴밀한 협업이 필요합니다. + +유저는 충분한 네트워크 안정성이 확보될 때까지 문제들이 발생할 수 있다는 것을 인지해야 합니다. + +**블록체인 롤백과 메인넷 런칭** + +블록체인 기술의 핵심 이념 중 하나는 불가역성(immutability)입니다. + +불가역성이란 이미 진행된 블록체인의 기록을 되돌아가 바꾸지 않는다는 것입니다. 블록체인의 불가역성은 소프트웨어 계층에서 합의 프로토콜의 형태로 전개되지만, 궁극적으로 네트워크 참여자들의 사회적 계약(social contract) 형태로 유지됩니다. + +여기서 참고할 것은 코스모스 허브는 의도적으로 포크와 롤백 절차를 간소화할 수 있도록 설계되었다는 것입니다. 코스모스 커뮤니티는 테스트넷 단계에서 이런 기능을 실제 사용했으며, 메인넷에서 또한 사용될 수 있습니다. 포크와 롤백 기능은 특정 카르텔 집단이 네트워크를 점령하는 것에 대한 대응 도구로 사용될 수 있습니다. + +대다수의 블록체인 커뮤니티는 롤백을 부정적으로 인식하고 있으며 매우 신중하게 결정되어야 합니다. 블록체인의 상태를 되돌린다는 것은 블록체인 네트워크의 경제적 완결성(economic finality)을 깨트리는 행동입니다. 그렇기 때문에 블록체인 롤백은 이더리움의 The DAO 하드포크 같은 극한 상황 상황에서만 사용되어야 합니다. + +단, 코스모스 허브 네트워크의 초기 단계에서는 토큰 전송 기능이 비활성화된 상태로 시작됩니다. 이런 특성은 블록체인 롤백의 중대성을 줄이게 되며 블록체인 상태의 변경의 ‘경제적 완결성’을 낮추게 됩니다. 버그 등 문제 발생으로 블록체인 롤백이 필요하다는 판단이 내려지는 경우, 테스트넷과 같이 블록체인 상태 롤백이 제네시스 블록까지도 진행될 수 있습니다. + +거버넌스에 의하여 토큰 전송이 활성화되는 경우, 코스모스 네트워크는 경제적 완결성을 따르게 됩니다. + +위 내용을 요약하자면, 토큰 전송이 활성화되기 전 코스모스 허브에 에러 또는 취약점이 발견되는 경우, 블록체인의 (제네시스 상태까지) 롤백이 진행될 수 있습니다. + +하지만 토큰 전송이 활성화된 뒤라면 블록체인 롤백을 정당화하는 것은 상당히 어렵게 됩니다. + +**개발자 참고 사항**: 코스모스 메인넷 런칭은 펀드레이저 참가자들이 협동하여 코스모스 허브 소프트웨어를 운영하는 첫 단계입니다. 생태계 내 대다수의 탈중앙화 애플리케이션 개발자는 [코스모스 SDK](https://cosmos.network/docs/) 또는 [텐더민트 코어](https://tendermint.com/docs/)를 사용하고 있는 것으로 예상됩니다. 각 코스모스 SDK/텐더민트 기반 애플리케이션의 개발 진행은 코스모스 허브와 별도로 진행되어도 무관합니다. 다만, [IBC](https://blog.cosmos.network/developer-deep-dive-cosmos-ibc-5855aaf183fe)(Inter-Blockchain Communication)을 사용하기 위해서는 메인넷 3단계까지 기다리거나 IBC 테스트넷에서 시범운영을 하실 수 있습니다. + +**유저 참고 사항**: 메인넷 최초 단계에서는 코스모스 아톰을 거래하지 **않는** 것을 강력하게 권고합니다 (예, 법적 계약을 통한 거래). 1단계에서는 제네시스 블록까지 긴급 롤백이 진행될 수 있으므로 트랜잭션이 번복될 수 있는 위험이 존재합니다. + +다만, 유저는 CLI 가이드 또는 하단의 영상 투토리얼을 참고하여 안전하게 아톰 위임을 진행하실 수는 있습니다. 다만 만약 블록체인 롤백이 진행되는 경우, 스테이킹으로 받은 리워드와 인플레이션 리워드의 손실이 있습니다. + +첫 단계에서 오직 CLI를 사용해서 트랜잭션을 발생하는 것을 코스모스 허브와 소통할 때 사용될 보이저(Voyager) 지갑은 현재 알파 단계에 있기 때문에 안전하게 사용할 수 있는 프로그램은 CLI 뿐이라는 것을 강조합니다. 보이저 지갑이 안전하다는 판단이 내려지고 베타가 시작될때 별도의 공지가 있겠습니다. + +CLI 가이드 🔗: [https://github.com/cosmos/cosmos-sdk/blob/develop/docs/translations/kr/gaia/delegator-guide-cli.md](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/translations/kr/gaia/delegator-guide-cli.md) + +CLI 위임 투토리얼 (영어): [https://www.youtube.com/watch?v=ydZw6o6Mzy0](https://www.youtube.com/watch?v=ydZw6o6Mzy0) + +# 2단계: 토큰 전송 활성화 + +**요약**: 메인넷이 충분히 안정성을 확보했다는 판단이 내려진 후, 위임된 아톰을 보유한 홀더들은 아톰 토큰 전송 활성화에 대한 프로포절에 투표를 할 수 있습니다. 해당 절차는 블록체인 상의 거버넌스 절차를 통해 진행됩니다. + +거버넌스 프로포절 현황에 대한 정보는 다양한 코스모스 익스플로러를 통해 확인하실 수 있습니다. 코스모스 익스플로러 목록은 [여기](https://cosmos.network/launch)를 참고하세요. + +**유저 참고 사항**: 토큰 전송 활성화 프로포절이 통과하고 전송이 활성화된 후 아톰 이동이 가능해집니다. + +# 3단계: IBC 활성화 + +**요약**: 3단계에서는 IBC 프로토콜이 공개됩니다. 토큰 홀더는 IBC 기능을 코스모스 SDK의 코어 모듈 라이브러리로 활성화할지에 대한 거버넌스 투표를 하게됩니다. + +**개발자 참고 사항**: 이 단계에서 코스모스 SDK/텐더민트 BFT를 기반으로 만들어진 애플리케이션 특화 블록체인(application-specific blockchain)은 IBC를 통해 중앙화된 제삼자 없이 코스모스 허브와 소통할 수 있게 됩니다. IBC를 이용해 블록체인 간 다양한 토큰과 NFT(Non-fungible token)들의 이동이 가능해집니다. + +# 메인넷 검증인 준비 사항 + +1. 메인넷에서 사용할 검증인 컨센서스 키(validator consensus key)를 생성하고 안전한 형태로 보관하십시오. +2. 게임 오브 스테이크 상금 / 펀드레이저 계정에서 트랜잭션 생성 및 서명을 할 수 있도록 준비하십시오 +3. ICF의 제네시스 토큰 분배 제안이 공개된 후 메인넷 Gentx 수집 작업을 시작합니다. + +# 글을 마무리 하며 + +코스모스의 목적은 모든 블록체인을 연결하여 ‘블록체인의 인터넷’을 만드는 것입니다. + +앞으로 가야 할 길이 멀다는 것을 알고 있습니다. + +메인넷이 런칭된 후, 토큰 이코노미 기반의 세상을 설계하는 본격적인 일이 본격적으로 시작돼야 할 것입니다. + +하지만 미국 전 대통령 존 케네디의 말을 인용하자면, “우리는 달에 가기로 결정하였습니다. 그것이 쉽기 때문이 아니라 어렵기 때문에 이렇게 결정한 것입니다.” + +달에 도착하는 그 날까지… 🚀 diff --git a/docs/gaia/validators/validator-faq.md b/docs/gaia/validators/validator-faq.md index fed45537e..2c159ab40 100644 --- a/docs/gaia/validators/validator-faq.md +++ b/docs/gaia/validators/validator-faq.md @@ -8,29 +8,29 @@ This is work in progress. Mechanisms and values are susceptible to change. ### What is a validator? -The [Cosmos Hub](/introduction/cosmos-hub.md) is based on [Tendermint](/introduction/tendermint.md), which relies on a set of [validators](/validators/overview.md) to secure the network. The role of validators is to run a full-node and participate in consensus by broadcasting votes which contain cryptographic signatures signed by their private key. Validators commit new blocks in the blockchain and receive revenue in exchange for their work. They must also participate in governance by voting on proposals. Validators are weighted according to their total stake. +The [Cosmos Hub](../what-is-gaia.md) is based on [Tendermint](https://tendermint.com/docs/introduction/what-is-tendermint.html), which relies on a set of validators to secure the network. The role of validators is to run a full-node and participate in consensus by broadcasting votes which contain cryptographic signatures signed by their private key. Validators commit new blocks in the blockchain and receive revenue in exchange for their work. They must also participate in governance by voting on proposals. Validators are weighted according to their total stake. ### What is 'staking'? -The Cosmos Hub is a public Proof-Of-Stake (PoS) blockchain, meaning that validator's weight is determined by the amount of staking tokens (Atoms) bonded as collateral. These Atoms can be staked directly by the validator or delegated to them by Atom holders. +The Cosmos Hub is a public Proof-Of-Stake (PoS) blockchain, meaning that the weight of validators is determined by the amount of staking tokens (Atoms) bonded as collateral. These Atoms can be seld-delegated directly by the validator or delegated to them by other Atom holders. -Any user in the system can declare its intention to become a validator by sending a `create-validator` transaction. From there, they become validators. +Any user in the system can declare their intention to become a validator by sending a `create-validator` transaction. From there, they become validator candidates. -The weight (i.e. total stake) of a validator determines wether or not it is an active validator, and also how frequently this node will have to propose a block and how much revenue it will obtain. Initially, only the top 100 validators with the most weight will be active validators. If validators double sign, are frequently offline or do not participate in governance, their staked Atoms (including Atoms of users that delegated to them) can be destroyed, or 'slashed'. +The weight (i.e. voting power) of a validator determines wether or not they are an active validator. Initially, only the top 100 validators with the most voting power will be active validators. ### What is a full-node? A full-node is a program that fully validates transactions and blocks of a blockchain. It is distinct from a light-node that only processes block headers and a small subset of transactions. Running a full-node requires more resources than a light-node but is necessary in order to be a validator. In practice, running a full-node only implies running a non-compromised and up-to-date version of the software with low network latency and without downtime. -Of course, it is possible and encouraged for any user to run full-nodes even if they do not plan to be validators. +Of course, it is possible and encouraged for users to run full-nodes even if they do not plan to be validators. ### What is a delegator? -Delegators are Atom holders who cannot, or do not want to run validator operations themselves. Through [Cosmos Voyager](/getting-started/voyager.md), a user can delegate Atoms to a validator and obtain a part of its revenue in exchange (for more detail on how revenue is distributed, see **What is the incentive to stake?** and **What is a validator's commission?** sections below). +Delegators are Atom holders who cannot, or do not want to run a validator themselves. Atom holders can delegate Atoms to a validator and obtain a part of their revenue in exchange (for more detail on how revenue is distributed, see [**What is the incentive to stake?**](#what-is-the-incentive-to-stake?) and [**What are validators commission?**](#what-are-validators-commission?) sections below). -Because they share revenue with their validators, delegators also share responsibility. Should a validator misbehave, each of its delegators will be partially slashed in proportion to their stake. This is why delegators should perform due diligence on validators before delegating, as well as spreading their stake over multiple validators. +Because they share revenue with their validators, delegators also share risks. Should a validator misbehave, each of their delegators will be partially slashed in proportion to their delegated stake. This is why delegators should perform due diligence on validators before delegating, as well as spreading their stake over multiple validators. -Delegators play a critical role in the system, as they are responsible for choosing validators. Being a delegator is not a passive role: Delegators should actively monitor the actions of their validators and participate in governance. +Delegators play a critical role in the system, as they are responsible for choosing validators. Being a delegator is not a passive role: Delegators should actively monitor the actions of their validators and participate in governance. For more, read the [delegator's faq](https://cosmos.network/resources/delegators). ## Becoming a Validator @@ -38,20 +38,19 @@ Delegators play a critical role in the system, as they are responsible for choos Any participant in the network can signal that they want to become a validator by sending a `create-validator` transaction, where they must fill out the following parameters: -* Validator's PubKey: The private key associated with PubKey is used to sign _prevotes_ and _precommits_. This way, validators can have different accounts for validating and holding liquid funds. -* Validator's Address: Application level address. This is the address used to identify your validator publicly. The private key associated with this address is used to bond, unbond, claim rewards, and participate in governance (in MVP only). -* Validator's name (moniker) -* Validator's website (Optional) -* Validator's description (Optional) -* Initial commission rate: The commission rate on block provisions, block rewards and fees charged to delegators -* Maximum commission: The maximum commission rate which this validator can charge -* Commission change rate: The maximum daily increase of the validator commission -* Minimum self-bond amount: Minimum amount of Atoms the validator need to have bonded at all time. If the validator's self-bonded stake falls below this limit, its entire staking pool will unbond. -* Initial self-bond amount: Initial amount of Atoms the validator wants to self-bond +* **Validator's `PubKey`:** The private key associated with this Tendermint `PubKey` is used to sign _prevotes_ and _precommits_. +* **Validator's Address:** Application level address. This is the address used to identify your validator publicly. The private key associated with this address is used to delegate, unbond, claim rewards, and participate in governance. +* **Validator's name (moniker)** +* **Validator's website (Optional)** +* **Validator's description (Optional)** +* **Initial commission rate**: The commission rate on block rewards and fees charged to delegators. +* **Maximum commission:** The maximum commission rate which this validator can charge. This parameter cannot be changed after `create-validator` is processed. +* **Commission max change rate:** The maximum daily increase of the validator commission. This parameter cannot be changed after `create-validator` is processed. +* **Minimum self-delegation:** Minimum amount of Atoms the validator needs to have bonded at all time. If the validator's self-delegated stake falls below this limit, their entire staking pool will unbond. -Once a validator is created, Atom holders can delegate atoms to it, effectively adding stake to this pool. The total stake of an address is the combination of Atoms bonded by delegators and Atoms self-bonded by the entity which designated itself. +Once a validator is created, Atom holders can delegate atoms to them, effectively adding stake to their pool. The total stake of an address is the combination of Atoms bonded by delegators and Atoms self-bonded by the entity which designated themselves. -Out of all validators that signaled themselves, the 100 with the most stake are the ones who are designated as validators. They become **bonded validators** If a validator's total stake falls below the top 100 then that validator loses its validator privileges, it enters **unbonding mode** and, eventually, becomes **unbonded** . Over time, the maximum number of validators will increase, according to a predefined schedule: +Out of all validator candidates that signaled themselves, the 100 with the most total stake are the ones who are designated as validators. They become **validators** If a validator's total stake falls below the top 100 then that validator loses their validator privileges: they don't participate in consensus and generate rewards any more. Over time, the maximum number of validators will increase, according to the following schedule (*Note: this schedule can be changed by governance*): * **Year 0:** 100 * **Year 1:** 113 @@ -71,95 +70,89 @@ Out of all validators that signaled themselves, the 100 with the most stake are The Testnet is a great environment to test your validator setup before launch. -We view testnet participation as a great way to signal to the community that you are ready and able to operate a validator. You can find all relevant information about the testnet [here](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets) and [here](https://github.com/cosmos/testnets). +We view testnet participation as a great way to signal to the community that you are ready and able to operate a validator. You can find all relevant information about the testnet [here](../join-testnet.md) and [here](https://github.com/cosmos/testnets). ### What are the different types of keys? In short, there are two types of keys: -* **Tendermint Key**: This is a unique key used to sign block hashes. It is associated with a public key `cosmosvalconspub`. - * Generated when the node is created with gaiad init. - * Get this value with `gaiad tendermint show-validator` - e.g. `cosmosvalconspub1zcjduc3qcyj09qc03elte23zwshdx92jm6ce88fgc90rtqhjx8v0608qh5ssp0w94c` +- **Tendermint Key**: This is a unique key used to sign consensus votes. + + It is associated with a public key `cosmosvalconspub` (Get this value with `gaiad tendermint show-validator`) + + It is generated when the node is created with gaiad init. + +- **Application key**: This key is created from `gaiacli` and used to sign transactions. Application keys are associated with a public key prefixed by `cosmospub` and an address prefixed by `cosmos`. Both are derived from account keys generated by `gaiacli keys add`. -* **Application keys**: These keys are created from the application and used to sign transactions. As a validator, you will probably use one key to sign staking-related transactions, and another key to sign governance-related transactions. Application keys are associated with a public key `cosmospub` and an address `cosmos`. Both are derived from account keys generated by `gaiacli keys add`. - * Note: A validator's operator key is directly tied to an application key, but - uses reserved prefixes solely for this purpose: `cosmosvaloper` and `cosmosvaloperpub` +Note: A validator's operator key is directly tied to an application key, but +uses reserved prefixes solely for this purpose: `cosmosvaloper` and `cosmosvaloperpub` ### What are the different states a validator can be in? -After a validator is created with a `create-validator` transaction, it can be in three states: +After a validator is created with a `create-validator` transaction, they can be in three states: -- `bonded`: Validator is in the active set and participates in consensus. Validator is earning rewards and can be slashed for misbehaviour. -- `unbonding`: Validator is not in the active set and does not participate in consensus. Validator is not earning rewards, but can still be slashed for misbehaviour. This is a transition state from `bonded` to `unbonded`. If validator does not send a `rebond` transaction while in `unbonding` mode, it will take three weeks for the state transition to complete. +- `in validator set`: Validator is in the active set and participates in consensus. Validator is earning rewards and can be slashed for misbehaviour. +- `jailed`: Validator misbehaved and is in jail, i.e. outisde of the validator set. If the jailing is due to being offline for too long, the validator can send an `unjail` transaction in order to re-enter the validator set. If the jailing is due to double signing, the validator cannot unjail. - `unbonded`: Validator is not in the active set, and therefore not signing blocs. Validator cannot be slashed, and does not earn any reward. It is still possible to delegate Atoms to this validator. Un-delegating from an `unbonded` validator is immediate. -Delegators have the same state as their validator. -*Note that delegation are not necessarily bonded. Atoms can be delegated and bonded, delegated and unbonding, delegated and unbonded, or liquid* +### What is 'self-delegation'? How can I increase my 'self-delegatino'? +Self-delegation is delegation from a validator to themselves. This amount can be increases by sending a `delegate` transaction from your validator's `application` application key. -### What is 'self-bond'? How can I increase my 'self-bond'? +### Is there a minimum amount of Atoms that must be delegated to be an active (=bonded) validator? -### Is there a faucet? - -If you want to obtain coins for the testnet, you can do so by using [this faucet](https://gaia.faucetcosmos.network/) - -### Is there a minimum amount of Atoms that must be staked to be an active (=bonded) validator? - -There is no minimum. The top 100 validators with the highest total stake (where total stake = self-bonded stake + delegators stake) are the active validators. +The minimum is `1 atom`. ### How will delegators choose their validators? Delegators are free to choose validators according to their own subjective criteria. This said, criteria anticipated to be important include: -* **Amount of self-bonded Atoms:** Number of Atoms a validator self-bonded to its staking pool. A validator with higher amount of self-bonded Atoms has more skin in the game, making it more liable for its actions. -* **Amount of delegated Atoms:** Total number of Atoms delegated to a validator. A high stake shows that the community trusts this validator, but it also means that this validator is a bigger target for hackers. Indeed, hackers are incentivized to hack bigger validators as they receive a reward proportionate to the stake of the validator they can prove to have compromised. Validators are expected to become less and less attractive as their amount of delegated Atoms grows. -* **Commission rate:** Commission applied on revenue by validators before it is distributed to their delegators +* **Amount of self-delegated Atoms:** Number of Atoms a validator self-delegated to themselves. A validator with a higher amount of self-delegated Atoms has more skin in the game, making them more liable for their actions. +* **Amount of delegated Atoms:** Total number of Atoms delegated to a validator. A high voting power shows that the community trusts this validator, but it also means that this validator is a bigger target for hackers. Bigger validators also decrease the decentralisation of the network. +* **Commission rate:** Commission applied on revenue by validators before it is distributed to their delegators. * **Track record:** Delegators will likely look at the track record of the validators they plan to delegate to. This includes seniority, past votes on proposals, historical average uptime and how often the node was compromised. -Apart from these criteria that will be displayed in Cosmos Voyager, there will be a possibility for validators to signal a website address to complete their resume. Validators will need to build reputation one way or another to attract delegators. For example, it would be a good practice for validators to have their setup audited by third parties. Note though, that the Tendermint team will not approve or conduct any audit itself. For more on due diligence, see [this blog post](https://medium.com/@interchain_io/3d0faf10ce6f) +Apart from these criteria, there will be a possibility for validators to signal a website address to complete their resume. Validators will need to build reputation one way or another to attract delegators. For example, it would be a good practice for validators to have their setup audited by third parties. Note though, that the Tendermint team will not approve or conduct any audit themselves. For more on due diligence, see [this blog post](https://medium.com/@interchain_io/3d0faf10ce6f) ## Responsibilites ### Do validators need to be publicly identified? -No, they do not. Each delegator will value validators based on their own criteria. Validators will be able (and are advised) to register a website address when they nominate themselves so that they can advertise their operation as they see fit. Some delegators may prefer a website that clearly displays the team running the validator and their resume, while others might prefer anonymous validators with positive track records. Most likely both identified and anonymous validators will coexist in the validator set. +No, they do not. Each delegator will value validators based on their own criteria. Validators will be able to register a website address when they nominate themselves so that they can advertise their operation as they see fit. Some delegators may prefer a website that clearly displays the team operating the validator and their resume, while others might prefer anonymous validators with positive track records. ### What are the responsiblities of a validator? Validators have two main responsibilities: -* **Be able to constantly run a correct version of the software:** validators need to make sure that their servers are always online and their private keys are not compromised. -* **Actively participate in governance:** validators are required to vote on every proposal. +* **Be able to constantly run a correct version of the software:**Validators need to make sure that their servers are always online and their private keys are not compromised. +* **Actively participate in governance:** Validators are required to vote on every proposal. Additionally, validators are expected to be active members of the community. They should always be up-to-date with the current state of the ecosystem so that they can easily adapt to any change. ### What does 'participate in governance' entail? -Validators and delegators on the Cosmos Hub can vote on proposals to change operational parameters (such as the block gas limit), coordinate upgrades, as well as vote on amendments to the human-readable constitution that govern the Cosmos Hub. +Validators and delegators on the Cosmos Hub can vote on proposals to change operational parameters (such as the block gas limit), coordinate upgrades, or make a decision on any given matter. -Validators play a special role in the governance system. Being the pillars of the system, they are required to vote on every proposal. It is especially important since delegators who do not vote will inherit the vote of their validator. Each time a validator does not vote on a proposal, it will get slashed by a minimal amount. +Validators play a special role in the governance system. Being the pillars of the system, they are required to vote on every proposal. It is especially important since delegators who do not vote will inherit the vote of their validator. ### What does staking imply? -Staking Atoms can be thought of as a safety deposit on validation activities. When a validator or a delegator wants to retrieve part or all of their deposit, they send an unbonding transaction. Then, Atoms undergo a _three weeks unbonding period_ during which they are liable to being slashed for potential misbehaviors committed by the validator before the unbonding process started. +Staking Atoms can be thought of as a safety deposit on validation activities. When a validator or a delegator wants to retrieve part or all of their deposit, they send an `unbonding` transaction. Then, Atoms undergo a **3 weeks unbonding period** during which they are liable to being slashed for potential misbehaviors committed by the validator before the unbonding process started. -Validators, and by association delegators, receive block provisions, block rewards, fee rewards, and the right to participate in governance. If a validator misbehaves, a certain portion of its total stake is slashed (the severity of the penalty depends on the type of misbehavior). This means that every user that bonded Atoms to this validator gets penalized in proportion to its stake. Delegators are therefore incentivized to delegate to validators that they anticipate will function safely. +Validators, and by association delegators, receive block rewards, fees, and have the right to participate in governance. If a validator misbehaves, a certain portion of their total stake is slashed. This means that every delegator that bonded Atoms to this validator gets penalized in proportion to their bonded stake. Delegators are therefore incentivized to delegate to validators that they anticipate will function safely. -### Can a validator run away with its delegators' Atoms? +### Can a validator run away with their delegators' Atoms? -By delegating to a validator, a user delegates staking power. The more staking power a validator has, the more weight it has in the consensus and governance processes. This does not mean that the validator has custody of its delegators' Atoms. _By no means can a validator run away with its delegator's funds_. +By delegating to a validator, a user delegates voting power. The more voting power a validator have, the more weight they have in the consensus and governance processes. This does not mean that the validator has custody of their delegators' Atoms. **By no means can a validator run away with its delegator's funds**. -Even though delegated funds cannot be stolen by their validators, delegators are still liable if their validators misbehave. In such case, each delegators' stake will be partially slashed in proportion to their relative stake. +Even though delegated funds cannot be stolen by their validators, delegators are still liable if their validators misbehave. -### How often will a validator be chosen to propose the next block? Does it go up with the quantity of Atoms staked? +### How often will a validator be chosen to propose the next block? Does it go up with the quantity of bonded Atoms? -The validator that is selected to propose the next block is called proposer. Each proposer is selected deterministically, and the frequency of being chosen is equal to the relative total stake (where total stake = self-bonded stake + delegators stake) of the validator. For example, if the total bonded stake across all validators is 100 Atoms and a validator's total stake is 10 Atoms, then this validator will be chosen 10% of the time as the next proposer. +The validator that is selected to propose the next block is called proposer. Each proposer is selected deterministically, and the frequency of being chosen is proportional to the voting power (i.e. amount of bonded Atoms) of the validator. For example, if the total bonded stake across all validators is 100 Atoms and a validator's total stake is 10 Atoms, then this validator will proposer ~10% of the blocks. ### Will validators of the Cosmos Hub ever be required to validate other zones in the Cosmos ecosystem? -Yes, they will. Initially, validators of the Cosmos hub will also validate the first public Ethermint zone. If governance decides so, validators of the Cosmos hub may be required to validate additional zones in the Cosmos ecosystem. As the case with the Ethermint Zone, for each additional zone compensation is to be provided in the form of block rewards and transaction fees. +Yes, they will. If governance decides so, validators of the Cosmos hub may be required to validate additional zones in the Cosmos ecosystem. ## Incentives @@ -167,45 +160,44 @@ Yes, they will. Initially, validators of the Cosmos hub will also validate the f Each member of a validator's staking pool earns different types of revenue: -* **Block provisions:** Native tokens of applications run by validators (e.g. Atoms on the Cosmos Hub) are inflated to produce block provisions. These provisions exist to incentivize Atom holders to bond their stake, as non-bonded Atom will be diluted over time. -* **Block rewards:** For the Ethermint zone, block rewards are paid in Photons. Initial distribution of Photons will be hard spooned from Ethereum. This means Photons will be emitted 1:1 to Ether. -* **Transaction fees:** The Cosmos Hub maintains a whitelist of token that are accepted as fee payment. +* **Block rewards:** Native tokens of applications run by validators (e.g. Atoms on the Cosmos Hub) are inflated to produce block provisions. These provisions exist to incentivize Atom holders to bond their stake, as non-bonded Atom will be diluted over time. +* **Transaction fees:** The Cosmos Hub maintains a whitelist of token that are accepted as fee payment. The initial fee token is tha `atom`. -This total revenue is divided among validators' staking pools according to each validator's weight. Then, within each validator's staking pool the revenue is divided among delegators in proportion to each delegator's stake. Note that a commission on delegators' revenue is applied by the validator before it is distributed. +This total revenue is divided among validators' staking pools according to each validator's weight. Then, within each validator's staking pool the revenue is divided among delegators in proportion to each delegator's stake. A commission on delegators' revenue is applied by the validator before it is distributed. ### What is the incentive to run a validator ? Validators earn proportionally more revenue than their delegators because of commissions. -Validators also play a major role in governance. If a delegator does not vote, it inherits the vote from its validator. This gives validators a major responsibility in the ecosystem. +Validators also play a major role in governance. If a delegator does not vote, they inherit the vote from their validator. This gives validators a major responsibility in the ecosystem. -### What is a validator's commission? +### What are validators commission? -Revenue received by a validator's pool is split between the validator and its delegators. The validator can apply a commission on the part of the revenue that goes to its delegators. This commission is set as a percentage. Each validator is free to set its initial commission, maximum daily commission change rate and maximum commission. The Cosmos Hub enforces the parameter that each validator sets. These parameters can only be defined when initially declaring candidacy, and may only be constrained further after being declared. +Revenue received by a validator's pool is split between the validator and their delegators. The validator can apply a commission on the part of the revenue that goes to their delegators. This commission is set as a percentage. Each validator is free to set their initial commission, maximum daily commission change rate and maximum commission. The Cosmos Hub enforces the parameter that each validator sets. Only the commission rate can change after the validator is created. -### How are block provisions distributed? +### How are block rewards distributed? -Block provisions are distributed proportionally to all validators relative to their total stake. This means that even though each validator gains atoms with each provision, all validators will still maintain equal weight. +Block rewards are distributed proportionally to all validators relative to their voting power. This means that even though each validator gains atoms with each reward, all validators will maintain equal weight over time. -Let us take an example where we have 10 validators with equal staking power and a commission rate of 1%. Let us also assume that the provision for a block is 1000 Atoms and that each validator has 20% of self-bonded Atoms. These tokens do not go directly to the proposer. Instead, they are evenly spread among validators. So now each validator's pool has 100 Atoms. These 100 Atoms will be distributed according to each participant's stake: +Let us take an example where we have 10 validators with equal voting power and a commission rate of 1%. Let us also assume that the reward for a block is 1000 Atoms and that each validator has 20% of self-bonded Atoms. These tokens do not go directly to the proposer. Instead, they are evenly spread among validators. So now each validator's pool has 100 Atoms. These 100 Atoms will be distributed according to each participant's stake: * Commission: `100*80%*1% = 0.8 Atoms` * Validator gets: `100\*20% + Commission = 20.8 Atoms` * All delegators get: `100\*80% - Commission = 79.2 Atoms` -Then, each delegator can claim its part of the 79.2 Atoms in proportion to their stake in the validator's staking pool. Note that the validator's commission is not applied on block provisions. Note that block rewards (paid in Photons) are distributed according to the same mechanism. +Then, each delegator can claim their part of the 79.2 Atoms in proportion to their stake in the validator's staking pool. ### How are fees distributed? -Fees are similarly distributed with the exception that the block proposer can get a bonus on the fees of the block it proposes if it includes more than the strict minimum of required precommits. +Fees are similarly distributed with the exception that the block proposer can get a bonus on the fees of the block they propose if they include more than the strict minimum of required precommits. -When a validator is selected to propose the next block, it must include at least 2/3 precommits for the previous block in the form of validator signatures. However, there is an incentive to include more than 2/3 precommits in the form of a bonus. The bonus is linear: it ranges from 1% if the proposer includes 2/3rd precommits (minimum for the block to be valid) to 5% if the proposer includes 100% precommits. Of course the proposer should not wait too long or other validators may timeout and move on to the next proposer. As such, validators have to find a balance between wait-time to get the most signatures and risk of losing out on proposing the next block. This mechanism aims to incentivize non-empty block proposals, better networking between validators as well as to mitigate censorship. +When a validator is selected to propose the next block, they must include at least 2/3 precommits of the previous block. However, there is an incentive to include more than 2/3 precommits in the form of a bonus. The bonus is linear: it ranges from 1% if the proposer includes 2/3rd precommits (minimum for the block to be valid) to 5% if the proposer includes 100% precommits. Of course the proposer should not wait too long or other validators may timeout and move on to the next proposer. As such, validators have to find a balance between wait-time to get the most signatures and risk of losing out on proposing the next block. This mechanism aims to incentivize non-empty block proposals, better networking between validators as well as to mitigate censorship. -Let's take a concrete example to illustrate the aforementioned concept. In this example, there are 10 validators with equal stake. Each of them applies a 1% commission and has 20% of self-bonded Atoms. Now comes a successful block that collects a total of 1025.51020408 Atoms in fees. +Let's take a concrete example to illustrate the aforementioned concept. In this example, there are 10 validators with equal stake. Each of them applies a 1% commission rate and has 20% of self-delegated Atoms. Now comes a successful block that collects a total of 1025.51020408 Atoms in fees. First, a 2% tax is applied. The corresponding Atoms go to the reserve pool. Reserve pool's funds can be allocated through governance to fund bounties and upgrades. -* `2% \* 1025.51020408 = 20.51020408` Atoms go to the reserve pool. +* `2% * 1025.51020408 = 20.51020408` Atoms go to the reserve pool. 1005 Atoms now remain. Let's assume that the proposer included 100% of the signatures in its block. It thus obtains the full bonus of 5%. @@ -222,32 +214,25 @@ We have to solve this simple equation to find the reward R for each validator: * The pool obtains R: 100 Atoms * Commission: `100 * 80% * 1%` = 0.8 Atoms * Validator's reward: `100 * 20% + Commission` = 20.8 Atoms - * Delegators' rewards: `100 * 80% - Commission` = 79.2 Atoms (each delegator will be able to claim its portion of these rewards in proportion to their stake) + * Delegators' rewards: `100 * 80% - Commission` = 79.2 Atoms (each delegator will be able to claim their portion of these rewards in proportion to their stake) ### What are the slashing conditions? -If a validator misbehaves, its bonded stake along with its delegators' stake and will be slashed. The severity of the punishment depends on the type of fault. There are 3 main faults that can result in slashing of funds for a validator and its delegators: +If a validator misbehaves, their delegated stake will be partially slashed. There is currently one main fault that can result in slashing of funds for a validator and their delegators: * **Double signing:** If someone reports on chain A that a validator signed two blocks at the same height on chain A and chain B, and if chain A and chain B share a common ancestor, then this validator will get slashed on chain A -* **Unavailability:** If a validator's signature has not been included in the last X blocks, the validator will get slashed by a marginal amount proportional to X. If X is above a certain limit Y, then the validator will get unbonded -* **Non-voting:** If a validator did not vote on a proposal, its stake will receive a minor slash. -Note that even if a validator does not intentionally misbehave, it can still be slashed if its node crashes, looses connectivity, gets DDOSed, or if its private key is compromised. +### Do validators need to self-delegate Atoms? -### Do validators need to self-bond Atoms? +Yes, they do need to seld-delegate at least `1 atom`. Even though there is no obligation for validators to self-delegate more than `1 atom`, delegators should want their validator to have more self-delegated Atoms in their staking pool. In other words, validators should have skin in the game. -No, they do not. A validators total stake is equal to the sum of its own self-bonded stake and of its delegated stake. This means that a validator can compensate its low amount of self-bonded stake by attracting more delegators. This is why reputation is very important for validators. - -Even though there is no obligation for validators to self-bond Atoms, delegators should want their validator to have self-bonded Atoms in their staking pool. In other words, validators should have skin in the game. - -In order for delegators to have some guarantee about how much skin-in-the-game their validator has, the latter can signal a minimum amount of self-bonded Atoms. If a validator's self-bond goes below the limit that it predefined, this validator and all of its delegators will unbond. +In order for delegators to have some guarantee about how much skin-in-the-game their validator has, the latter can signal a minimum amount of self-delegated Atoms. If a validator's self-delegation goes below the limit that it predefined, this validator and all of its delegators will unbond. ### How to prevent concentration of stake in the hands of a few top validators? -For now the community is expected to behave in a smart and self-preserving way. When a mining pool in Bitcoin gets too much mining power the community usually stops contributing to that pool. The Cosmos Hub will rely on the same effect initially. In the future, other mechanisms will be deployed to smoothen this process as much as possible: +For now the community is expected to behave in a smart and self-preserving way. When a mining pool in Bitcoin gets too much mining power the community usually stops contributing to that pool. The Cosmos Hub will rely on the same effect initially. Other mechanisms are in place to smoothen this process as much as possible: * **Penalty-free re-delegation:** This is to allow delegators to easily switch from one validator to another, in order to reduce validator stickiness. -* **Hack bounty:** This is an incentive for the community to hack validators. There will be bounties proportionate to the size of the validator, so that a validator becomes a bigger target as its stake grows. * **UI warning:** Users will be warned by Cosmos Voyager if they want to delegate to a validator that already has a significant amount of staking power. ## Technical Requirements diff --git a/docs/gaia/validators/validator-setup.md b/docs/gaia/validators/validator-setup.md index cac1508d0..7c5e7d03b 100644 --- a/docs/gaia/validators/validator-setup.md +++ b/docs/gaia/validators/validator-setup.md @@ -53,20 +53,19 @@ __Note__: If unspecified, `consensus_pubkey` will default to the output of `gaia ## Participate in genesis as a validator -__Note__: This section only concerns validators that want to be in the genesis file. If the chain you want to validate is already live, skip this section. +__Note__: This section only concerns validators that want to be in the genesis +file. If the chain you want to validate is already live, skip this section. -__Note__: `Gaia-9002` and `Game of stakes` will not use this process. They will be bootsrapped using Tendermint seed validators. You will just need to use the [create-validator](#create-your-validator) command in order to join as a validator for these networks. +__Note__: `Gaia-9002` and `Game of stakes` will not use this process. They will +be bootstrapped using validators operated by Tendermint. You will just need to use the +[create-validator](#create-your-validator) command in order to join as a validator +for these networks. -If you want to participate in genesis as a validator, you need to justify that you (or a delegator) have some stake at genesis, create one (or multiple) transaction to bond this stake to your validator address, and include this transaction in the genesis file. +If you want to participate in genesis as a validator, you need to justify that +you have some stake at genesis, create one (or multiple) transactions to bond this +stake to your validator address, and include this transaction in the genesis file. -We thus need to distinguish two cases: - -- Case 1: You want to bond the initial stake from your validator's address. -- Case 2: You want to bond the initial stake from a delegator's address. - -### Case 1: The initial stake comes from your validator's address - -In this case, you will create a `gentx`: +You will need create a `gentx`: ```bash gaiad gentx \ @@ -78,46 +77,17 @@ gaiad gentx \ --name ``` -__Note__: This command automatically store your `gentx` in `~/.gaiad/config/gentx` for it to be processed at genesis. +__Note__: This command automatically store your `gentx` in `~/.gaiad/config/gentx` +for it to be processed at genesis. ::: tip Consult `gaiad gentx --help` for more information on the flags defaults. ::: -A `gentx` is a JSON file carrying a self-delegation. All genesis transactions are collected by a `genesis coordinator` and validated against an initial `genesis.json`. Such initial `genesis.json` contains only a list of accounts and their coins. Once the transactions are processed, they are merged in the `genesis.json`'s `gentxs` field. - -### Case 2: The initial stake comes from a delegator's address - -In this case, you need both the signature of the validator and the delegator. Start by creating an unsigned `create-validator` transaction, and save it in a file called `unsignedValTx`: - -```bash -gaiacli tx staking create-validator \ - --amount=5STAKE \ - --pubkey=$(gaiad tendermint show-validator) \ - --moniker="choose a moniker" \ - --chain-id= \ - --from= \ - --commission-rate="0.10" \ - --commission-max-rate="0.20" \ - --commission-max-change-rate="0.01" \ - --address-delegator="address of the delegator" \ - --generate-only \ - > unsignedValTx.json -``` - -Then, sign this `unsignedValTx` with your validator's private key, and save the output in a new file `signedValTx.json`: - -```bash -gaiacli tx sign unsignedValTx.json --from= > signedValTx.json -``` - -Then, pass this file to the delegator, who needs to run the following command: - -```bash -gaiacli tx sign signedValTx.json --from= > gentx.json -``` - -This `gentx.json` needs to be included in the `~/.gaiad/config/gentx` folder on the validator's machine to be processed at genesis, just like in case 1 (except here it needs to be copied manually into the folder). +A `gentx` is a JSON file carrying a self-delegation. All genesis transactions are +collected by a `genesis coordinator` and validated against an initial `genesis.json`. +Such initial `genesis.json` contains only a list of accounts and their coins. +Once the transactions are processed, they are merged in the `genesis.json`'s `gentxs` field. ### Copy the Initial Genesis File and Process Genesis Transactions diff --git a/docs/spec/SPEC-SPEC.md b/docs/spec/SPEC-SPEC.md index 5b65833c7..7cb3ec016 100644 --- a/docs/spec/SPEC-SPEC.md +++ b/docs/spec/SPEC-SPEC.md @@ -7,20 +7,35 @@ this directory. For consistency, specs should be written in passive present tense. +## Pseudo-Code + +Generally, pseudo-code should be minimized throughout the spec. Often, simple +bulleted-lists which describe a function's operations are sufficient and should +be considered preferable. In certain instances, due to the complex nature of +the functionality being described pseudo-code may the most suitable form of +specification. In these cases use of pseudo-code is permissible, but should be +presented in a concise manner, ideally restricted to only the complex +element as a part of a larger description. + ## Common Layout -The following generalized structure should be used to breakdown specifications -for modules. Note that not all files may be required depending on the modules -function +The following generalized file structure should be used to breakdown +specifications for modules. With the exception of README.md, `XX` at the +beginning of the file name should be replaced with a number to indicate +document flow (ex. read `01_state.md` before `02_state_transitions.md`). The +following list is nonbinding and all files are optional. - - `overview.md` - describe module - - `state.md` - specify and describe structures expected to marshalled into the store, and their keys - - `state_transitions.md` - standard state transition operations triggered by hooks, messages, etc. - - `end_block.md` - specify any end-block operations - - `begin_block.md` - specify any begin-block operations - - `messages.md` - specify message structure and expected state machine behaviour - - `hooks.md` - describe available hooks to be called by/from this module - - `tags.md` - list and describe event tags used + - `README.md` - overview of the module + - `XX_concepts.md` - describe specialized concepts and definitions used throughout the spec + - `XX_state.md` - specify and describe structures expected to marshalled into the store, and their keys + - `XX_state_transitions.md` - standard state transition operations triggered by hooks, messages, etc. + - `XX_messages.md` - specify message structure(s) and expected state machine behaviour(s) + - `XX_begin_block.md` - specify any begin-block operations + - `XX_end_block.md` - specify any end-block operations + - `XX_hooks.md` - describe available hooks to be called by/from this module + - `XX_tags.md` - list and describe event tags used + - `XX_future_improvements.md` - describe future improvements of this module + - `XX_appendix.md` - supplementary details referenced elsewhere within the spec ### Notation for key-value mapping diff --git a/docs/spec/auth/gas_fees.md b/docs/spec/auth/01_concepts.md similarity index 98% rename from docs/spec/auth/gas_fees.md rename to docs/spec/auth/01_concepts.md index 309f590ce..388c5a8ac 100644 --- a/docs/spec/auth/gas_fees.md +++ b/docs/spec/auth/01_concepts.md @@ -1,4 +1,6 @@ -# Gas & Fees +# Concepts + +## Gas & Fees Fees serve two purposes for an operator of the network. diff --git a/docs/spec/auth/state.md b/docs/spec/auth/02_state.md similarity index 95% rename from docs/spec/auth/state.md rename to docs/spec/auth/02_state.md index 29b5a93ab..ad061d9a9 100644 --- a/docs/spec/auth/state.md +++ b/docs/spec/auth/02_state.md @@ -1,6 +1,6 @@ -## State +# State -### Accounts +## Accounts Accounts contain authentication information for a uniquely identified external user of an SDK blockchain, including public key, address, and account number / sequence number for replay protection. For efficiency, @@ -13,7 +13,7 @@ account types may do so. - `0x01 | Address -> amino(account)` -#### Account Interface +### Account Interface The account interface exposes methods to read and write standard account information. Note that all of these methods operate on an account struct confirming to the interface @@ -53,6 +53,6 @@ type BaseAccount struct { } ``` -#### Vesting Account +### Vesting Account See [Vesting](vesting.md). diff --git a/docs/spec/auth/handlers.md b/docs/spec/auth/03_messages.md similarity index 95% rename from docs/spec/auth/handlers.md rename to docs/spec/auth/03_messages.md index 2f8909c63..9180f2da4 100644 --- a/docs/spec/auth/handlers.md +++ b/docs/spec/auth/03_messages.md @@ -1,3 +1,7 @@ +# Messages + +TODO make this file conform to typical messages spec + ## Handlers The auth module presently has no transaction handlers of its own, but does expose diff --git a/docs/spec/auth/types.md b/docs/spec/auth/03_types.md similarity index 96% rename from docs/spec/auth/types.md rename to docs/spec/auth/03_types.md index 30c5ac8b1..4d37c55c8 100644 --- a/docs/spec/auth/types.md +++ b/docs/spec/auth/03_types.md @@ -1,4 +1,4 @@ -## Types +# Types Besides accounts (specified in [State](state.md)), the types exposed by the auth module are `StdFee`, the combination of an amount and gas limit, `StdSignature`, the combination @@ -6,7 +6,7 @@ of an optional public key and a cryptographic signature as a byte array, `StdTx` a struct which implements the `sdk.Tx` interface using `StdFee` and `StdSignature`, and `StdSignDoc`, a replay-prevention structure for `StdTx` which transaction senders must sign over. -### StdFee +## StdFee A `StdFee` is simply the combination of a fee amount, in any number of denominations, and a gas limit (where dividing the amount by the gas limit gives a "gas price"). @@ -18,7 +18,7 @@ type StdFee struct { } ``` -### StdSignature +## StdSignature A `StdSignature` is the combination of an optional public key and a cryptographic signature as a byte array. The SDK is agnostic to particular key or signature formats and supports any @@ -31,7 +31,7 @@ type StdSignature struct { } ``` -### StdTx +## StdTx A `StdTx` is a struct which implements the `sdk.Tx` interface, and is likely to be generic enough to serve the purposes of many Cosmos SDK blockchains. @@ -45,7 +45,7 @@ type StdTx struct { } ``` -### StdSignDoc +## StdSignDoc A `StdSignDoc` is a replay-prevention structure to be signed over, which ensures that any submitted transaction (which is simply a signature over a particular bytestring) diff --git a/docs/spec/auth/keepers.md b/docs/spec/auth/04_keepers.md similarity index 97% rename from docs/spec/auth/keepers.md rename to docs/spec/auth/04_keepers.md index 27611decd..61af1efa1 100644 --- a/docs/spec/auth/keepers.md +++ b/docs/spec/auth/04_keepers.md @@ -1,8 +1,8 @@ -## Keepers +# Keepers The auth module only exposes one keeper, the account keeper, which can be used to read and write accounts. -### Account Keeper +## Account Keeper Presently only one fully-permissioned account keeper is exposed, which has the ability to both read and write all fields of all accounts, and to iterate over all stored accounts. diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/05_vesting.md similarity index 96% rename from docs/spec/auth/vesting.md rename to docs/spec/auth/05_vesting.md index a0c64fdd5..c6a16f5f3 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/05_vesting.md @@ -251,10 +251,12 @@ func DelegateCoins(t Time, from Account, amount Coins) { ### Undelegating For a vesting account attempting to undelegate `D` coins, the following is performed: +NOTE: `DV < D` and `(DV + DF) < D` may be possible due to quirks in the rounding of +delegation/undelegation logic. -1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check) +1. Verify `D > 0` 2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins) -3. Compute `Y := D - X` (portion of `D` that should remain vesting) +3. Compute `Y := min(DV, D - X)` (portion of `D` that should remain vesting) 4. Set `DF -= X` 5. Set `DV -= Y` 6. Set `BC += D` @@ -274,6 +276,11 @@ func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) { with an excess `DV` amount, even after all its coins have vested. This is because undelegating free coins are prioritized. +**Note**: The undelegation (bond refund) amount may exceed the delegated +vesting (bond) amount due to the way undelegation truncates the bond refund, +which can increase the validator's exchange rate (tokens/shares) slightly if the +undelegated tokens are non-integral. + #### Keepers/Handlers ```go diff --git a/docs/spec/auth/README.md b/docs/spec/auth/README.md index be6647a80..92ff7f4de 100644 --- a/docs/spec/auth/README.md +++ b/docs/spec/auth/README.md @@ -13,18 +13,24 @@ This module will be used in the Cosmos Hub. ## Contents -1. **[State](state.md)** - 1. [Accounts](state.md#accounts) - 1. [Account Interface](state.md#account-interface) - 2. [Base Account](state.md#baseaccount) - 3. [Vesting Account](state.md#vestingaccount) -1. **[Types](types.md)** - 1. [StdFee](types.md#stdfee) - 2. [StdSignature](types.md#stdsignature) - 3. [StdTx](types.md#stdtx) - 4. [StdSignDoc](types.md#stdsigndoc) -1. **[Keepers](keepers.md)** - 1. [AccountKeeper](keepers.md#account-keeper) -1. **[Handlers](handlers.md)** - 1. [Ante Handler](handlers.md#ante-handler) -1. **[Gas & Fees](gas_fees.md)** +1. **[Concepts](01_concepts.md)** + - [Gas & Fees](01_concepts.md#gas-&-fees) +2. **[State](02_state.md)** + - [Accounts](02_state.md#accounts) +3. **[Messages](03_messages.md)** + - [Handlers](03_messages.md#handlers) +4. **[Types](03_types.md)** + - [StdFee](03_types.md#stdfee) + - [StdSignature](03_types.md#stdsignature) + - [StdTx](03_types.md#stdtx) + - [StdSignDoc](03_types.md#stdsigndoc) +5. **[Keepers](04_keepers.md)** + - [Account Keeper](04_keepers.md#account-keeper) +6. **[Vesting](05_vesting.md)** + - [Intro and Requirements](05_vesting.md#intro-and-requirements) + - [Vesting Account Types](05_vesting.md#vesting-account-types) + - [Vesting Account Specification](05_vesting.md#vesting-account-specification) + - [Keepers & Handlers](05_vesting.md#keepers-&-handlers) + - [Genesis Initialization](05_vesting.md#genesis-initialization) + - [Examples](05_vesting.md#examples) + - [Glossary](05_vesting.md#glossary) diff --git a/docs/spec/bank/state.md b/docs/spec/bank/01_state.md similarity index 97% rename from docs/spec/bank/state.md rename to docs/spec/bank/01_state.md index aa6585bbf..b3421b607 100644 --- a/docs/spec/bank/state.md +++ b/docs/spec/bank/01_state.md @@ -1,4 +1,4 @@ -## State +# State Presently, the bank module has no inherent state — it simply reads and writes accounts using the `AccountKeeper` from the `auth` module. diff --git a/docs/spec/bank/keepers.md b/docs/spec/bank/02_keepers.md similarity index 97% rename from docs/spec/bank/keepers.md rename to docs/spec/bank/02_keepers.md index 3c6eab772..ed277bd23 100644 --- a/docs/spec/bank/keepers.md +++ b/docs/spec/bank/02_keepers.md @@ -1,12 +1,12 @@ -## Keepers +# Keepers The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require. Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect. -### Common Types +## Common Types -#### Input +### Input An input of a multiparty transfer @@ -17,7 +17,7 @@ type Input struct { } ``` -#### Output +### Output An output of a multiparty transfer. @@ -28,7 +28,7 @@ type Output struct { } ``` -### BaseKeeper +## BaseKeeper The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins. @@ -82,7 +82,7 @@ inputOutputCoins(inputs []Input, outputs []Output) addCoins(output.Address, output.Coins) ``` -### SendKeeper +## SendKeeper The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins). @@ -100,7 +100,7 @@ sendCoins(from AccAddress, to AccAddress, amt Coins) addCoins(to, amt) ``` -### ViewKeeper +## ViewKeeper The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`. diff --git a/docs/spec/bank/messages.md b/docs/spec/bank/03_messages.md similarity index 94% rename from docs/spec/bank/messages.md rename to docs/spec/bank/03_messages.md index fd9331763..f249d2cbd 100644 --- a/docs/spec/bank/messages.md +++ b/docs/spec/bank/03_messages.md @@ -1,6 +1,6 @@ -## Messages +# Messages -### MsgSend +## MsgSend ```golang type MsgSend struct { diff --git a/docs/spec/bank/tags.md b/docs/spec/bank/04_tags.md similarity index 100% rename from docs/spec/bank/tags.md rename to docs/spec/bank/04_tags.md diff --git a/docs/spec/bank/README.md b/docs/spec/bank/README.md index bee48c029..ba4beb856 100644 --- a/docs/spec/bank/README.md +++ b/docs/spec/bank/README.md @@ -14,13 +14,13 @@ This module will be used in the Cosmos Hub. ## Contents -1. **[State](state.md)** -1. **[Keepers](keepers.md)** - 1. [Common Types](keepers.md#common-types) - 1. [Input](keepers.md#input) - 1. [Output](keepers.md#output) - 1. [BaseKeeper](keepers.md#basekeeper) - 1. [SendKeeper](keepers.md#sendkeeper) - 1. [ViewKeeper](keepers.md#viewkeeper) -1. **[Transactions](transactions.md)** - 1. [MsgSend](transactions.md#msgsend) +1. **[State](01_state.md)** +2. **[Keepers](02_keepers.md)** + - [Common Types](02_keepers.md#common-types) + - [BaseKeeper](02_keepers.md#basekeeper) + - [SendKeeper](02_keepers.md#sendkeeper) + - [ViewKeeper](02_keepers.md#viewkeeper) +3. **[Messages](03_messages.md)** + - [MsgSend](03_messages.md#msgsend) +4. **[Tags](04_tags.md)** + - [Handlers](04_tags.md#handlers) diff --git a/docs/spec/distribution/f1_reference_counting.md b/docs/spec/distribution/01_concepts.md similarity index 99% rename from docs/spec/distribution/f1_reference_counting.md rename to docs/spec/distribution/01_concepts.md index 2563be872..38485dcda 100644 --- a/docs/spec/distribution/f1_reference_counting.md +++ b/docs/spec/distribution/01_concepts.md @@ -1,3 +1,5 @@ +# Concepts + ## Reference Counting in F1 Fee Distribution In F1 fee distribution, in order to calculate the rewards a delegator ought to receive when they diff --git a/docs/spec/distribution/state.md b/docs/spec/distribution/02_state.md similarity index 100% rename from docs/spec/distribution/state.md rename to docs/spec/distribution/02_state.md diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/03_end_block.md similarity index 100% rename from docs/spec/distribution/end_block.md rename to docs/spec/distribution/03_end_block.md diff --git a/docs/spec/distribution/messages.md b/docs/spec/distribution/04_messages.md similarity index 100% rename from docs/spec/distribution/messages.md rename to docs/spec/distribution/04_messages.md diff --git a/docs/spec/distribution/hooks.md b/docs/spec/distribution/05_hooks.md similarity index 100% rename from docs/spec/distribution/hooks.md rename to docs/spec/distribution/05_hooks.md diff --git a/docs/spec/distribution/tags.md b/docs/spec/distribution/06_tags.md similarity index 100% rename from docs/spec/distribution/tags.md rename to docs/spec/distribution/06_tags.md diff --git a/docs/spec/distribution/README.md b/docs/spec/distribution/README.md index 8cbe9355a..8221974ea 100644 --- a/docs/spec/distribution/README.md +++ b/docs/spec/distribution/README.md @@ -74,3 +74,22 @@ In conclusion, we can only have Atom commission and unbonded atoms provisions or bonded atom provisions with no Atom commission, and we elect to implement the former. Stakeholders wishing to rebond their provisions may elect to set up a script to periodically withdraw and rebond rewards. + +## Contents + +1. **[Concepts](01_concepts.md)** + - [Reference Counting in F1 Fee Distribution](01_concepts.md#reference-counting-in-f1-fee-distribution) +2. **[02_state.md](02_state.md)** + - [State](02_state.md#state) +3. **[End Block](03_end_block.md)** +4. **[Messages](04_messages.md)** + - [MsgWithdrawDelegationRewardsAll](04_messages.md#msgwithdrawdelegationrewardsall) + - [MsgWithdrawDelegationReward](04_messages.md#msgwithdrawdelegationreward) + - [MsgWithdrawValidatorRewardsAll](04_messages.md#msgwithdrawvalidatorrewardsall) + - [Common calculations ](04_messages.md#common-calculations-) +5. **[Hooks](05_hooks.md)** + - [Create or modify delegation distribution](05_hooks.md#create-or-modify-delegation-distribution) + - [Commission rate change](05_hooks.md#commission-rate-change) + - [Change in Validator State](05_hooks.md#change-in-validator-state) +6. **[Tags](06_tags.md)** + - [Handlers](06_tags.md#handlers) diff --git a/docs/spec/governance/overview.md b/docs/spec/governance/01_concepts.md similarity index 89% rename from docs/spec/governance/overview.md rename to docs/spec/governance/01_concepts.md index e2efe5544..9c287d54f 100644 --- a/docs/spec/governance/overview.md +++ b/docs/spec/governance/01_concepts.md @@ -1,4 +1,4 @@ -# Design Overview +# Concepts *Disclaimer: This is work in progress. Mechanisms are susceptible to change.* @@ -139,20 +139,7 @@ If a delegator does not vote, it will inherit its validator vote. ### Validator’s punishment for non-voting -Validators are required to vote on all proposals to ensure that results have -legitimacy. Voting is part of validators' directives and failure to do it will -result in a penalty. - -If a validator’s address is not in the list of addresses that voted on a -proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting -period` is over), then the validator will automatically be partially slashed by -`GovernancePenalty`. - -*Note: Need to define values for `GovernancePenalty`* - -**Exception:** If a proposal is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. -That is because the proposal will close as soon as the ratio exceeds 2:3, -making it mechanically impossible for some validators to vote on it. +At present, validators are not punished for failing to vote. ### Governance address @@ -185,4 +172,4 @@ Once a block contains more than 2/3rd *precommits* where a common nodes, non-validating full nodes and light-nodes) are expected to switch to the new version of the software. -*Note: Not clear how the flip is handled programatically* \ No newline at end of file +*Note: Not clear how the flip is handled programatically* diff --git a/docs/spec/governance/state.md b/docs/spec/governance/02_state.md similarity index 97% rename from docs/spec/governance/state.md rename to docs/spec/governance/02_state.md index f28a309a4..4a2fd08d1 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/02_state.md @@ -1,8 +1,6 @@ -# Implementation (1/2) +# State -## State - -### Parameters and base types +## Parameters and base types `Parameters` define the rules according to which votes are run. There can only be one active parameter set at any given time. If governance wants to change a @@ -27,7 +25,6 @@ type TallyParams struct { Quorum sdk.Dec // Minimum percentage of stake that needs to vote for a proposal to be considered valid Threshold sdk.Dec // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5 Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 - GovernancePenalty sdk.Dec // Penalty if validator does not vote } ``` @@ -65,7 +62,7 @@ const ( ) ``` -### Deposit +## Deposit ```go type Deposit struct { @@ -74,7 +71,7 @@ const ( } ``` -### ValidatorGovInfo +## ValidatorGovInfo This type is used in a temp map when tallying @@ -85,7 +82,7 @@ This type is used in a temp map when tallying } ``` -### Proposals +## Proposals `Proposals` are an item to be voted on. @@ -117,7 +114,7 @@ We also mention a method to update the tally for a given proposal: func (proposal Proposal) updateTally(vote byte, amount sdk.Dec) ``` -### Stores +## Stores *Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*` @@ -132,7 +129,7 @@ For pseudocode purposes, here are the two function we will use to read or write * `load(StoreKey, Key)`: Retrieve item stored at key `Key` in store found at key `StoreKey` in the multistore * `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore -### Proposal Processing Queue +## Proposal Processing Queue **Store:** * `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the diff --git a/docs/spec/governance/messages.md b/docs/spec/governance/03_messages.md similarity index 98% rename from docs/spec/governance/messages.md rename to docs/spec/governance/03_messages.md index 17004e4b8..aaca50330 100644 --- a/docs/spec/governance/messages.md +++ b/docs/spec/governance/03_messages.md @@ -1,8 +1,6 @@ -# Implementation (2/2) +# Messages -## Messages - -### Proposal Submission +## Proposal Submission Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. @@ -69,7 +67,7 @@ upon receiving txGovSubmitProposal from sender do return proposalID ``` -### Deposit +## Deposit Once a proposal is submitted, if `Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send @@ -138,7 +136,7 @@ upon receiving txGovDeposit from sender do store(Proposals, , proposal) ``` -### Vote +## Vote Once `ActiveParam.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their diff --git a/docs/spec/governance/tags.md b/docs/spec/governance/04_tags.md similarity index 100% rename from docs/spec/governance/tags.md rename to docs/spec/governance/04_tags.md diff --git a/docs/spec/governance/future_improvements.md b/docs/spec/governance/05_future_improvements.md similarity index 95% rename from docs/spec/governance/future_improvements.md rename to docs/spec/governance/05_future_improvements.md index 9e0b0f4df..1cd6c9466 100644 --- a/docs/spec/governance/future_improvements.md +++ b/docs/spec/governance/05_future_improvements.md @@ -1,4 +1,4 @@ -# Future improvements (not in scope for MVP) +# Future Improvements The current documentation only describes the minimum viable product for the governance module. Future improvements may include: @@ -27,4 +27,4 @@ governance module. Future improvements may include: process. * **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to - reward third party auditors. \ No newline at end of file + reward third party auditors. diff --git a/docs/spec/governance/README.md b/docs/spec/governance/README.md index 6cc1edb50..d4d77a16c 100644 --- a/docs/spec/governance/README.md +++ b/docs/spec/governance/README.md @@ -21,15 +21,22 @@ This module will be used in the Cosmos Hub, the first Hub in the Cosmos network. The following specification uses *Atom* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the native staking token of the chain. -1. **[Design overview](overview.md)** -2. **Implementation** - 1. **[State](state.md)** - 1. Parameters - 2. Proposals - 3. Proposal Processing Queue - 2. **[Transactions](transactions.md)** - 1. Proposal Submission - 2. Deposit - 3. Claim Deposit - 4. Vote -3. **[Future improvements](future_improvements.md)** +1. **[Concepts](01_concepts.md)** + - [Proposal submission](01_concepts.md#proposal-submission) + - [Vote](01_concepts.md#vote) + - [Software Upgrade](01_concepts.md#software-upgrade) +2. **[State](02_state.md)** + - [Parameters and base types](02_state.md#parameters-and-base-types) + - [Deposit](02_state.md#deposit) + - [ValidatorGovInfo](02_state.md#validatorgovinfo) + - [Proposals](02_state.md#proposals) + - [Stores](02_state.md#stores) + - [Proposal Processing Queue](02_state.md#proposal-processing-queue) +3. **[Messages](03_messages.md)** + - [Proposal Submission](03_messages.md#proposal-submission) + - [Deposit](03_messages.md#deposit) + - [Vote](03_messages.md#vote) +4. **[Tags](04_tags.md)** + - [EndBlocker](04_tags.md#endblocker) + - [Handlers](04_tags.md#handlers) +5. **[Future Improvements](05_future_improvements.md)** diff --git a/docs/spec/ibc/mvp/mvp1.md b/docs/spec/ibc/mvp/mvp1.md index 3cc7d4336..cf8827769 100644 --- a/docs/spec/ibc/mvp/mvp1.md +++ b/docs/spec/ibc/mvp/mvp1.md @@ -26,12 +26,12 @@ type IBCPacket struct { } // Implements sdk.Msg -type IBCTransferMsg struct { +type MsgIBCTransfer struct { IBCPacket } // Implements sdk.Msg -type IBCReceiveMsg struct { +type MsgIBCReceive struct { IBCPacket Relayer sdk.Address Sequence int64 diff --git a/docs/spec/ibc/mvp/mvp2.md b/docs/spec/ibc/mvp/mvp2.md index 61590007d..ed26bc18e 100644 --- a/docs/spec/ibc/mvp/mvp2.md +++ b/docs/spec/ibc/mvp/mvp2.md @@ -29,12 +29,12 @@ type TransferPayload struct { } // Implements sdk.Msg -type IBCTransferMsg struct { +type MsgIBCTransfer struct { Packet } // Implements sdk.Msg -type IBCReceiveMsg struct { +type MsgIBCReceive struct { Packet Relayer sdk.Address Sequence int64 diff --git a/docs/spec/ibc/mvp/mvp3.md b/docs/spec/ibc/mvp/mvp3.md index bcbf39a92..4bc13659d 100644 --- a/docs/spec/ibc/mvp/mvp3.md +++ b/docs/spec/ibc/mvp/mvp3.md @@ -8,12 +8,12 @@ // Implements sdk.Msg -type IBCTransferMsg struct { +type MsgIBCTransfer struct { Packet } // Implements sdk.Msg -type IBCReceiveMsg struct { +type MsgIBCReceive struct { Packet } @@ -42,12 +42,12 @@ type TransferPayload struct { } // Implements sdk.Msg -type IBCTransferMsg struct { +type MsgIBCTransfer struct { Packet } // Implements sdk.Msg -type IBCReceiveMsg struct { +type MsgIBCReceive struct { Packet Proof iavl.Proof FromChainID string diff --git a/docs/spec/mint/state.md b/docs/spec/mint/01_state.md similarity index 96% rename from docs/spec/mint/state.md rename to docs/spec/mint/01_state.md index 42a8df518..bb528dcf7 100644 --- a/docs/spec/mint/state.md +++ b/docs/spec/mint/01_state.md @@ -1,6 +1,6 @@ -## State +# State -### Minter +## Minter The minter is a space for holding current inflation information. @@ -13,7 +13,7 @@ type Minter struct { } ``` -### Params +## Params Minting params are held in the global params store. diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/02_begin_block.md similarity index 100% rename from docs/spec/mint/begin_block.md rename to docs/spec/mint/02_begin_block.md diff --git a/docs/spec/mint/README.md b/docs/spec/mint/README.md index 085986b37..096a44a99 100644 --- a/docs/spec/mint/README.md +++ b/docs/spec/mint/README.md @@ -1,4 +1,11 @@ # Mint Specification -- [State](./state.md) -- [Begin Block](./begin_block.md) \ No newline at end of file +## Contents + +1. **[State](01_state.md)** + - [Minter](01_state.md#minter) + - [Params](01_state.md#params) +2. **[Begin-Block](02_begin_block.md)** + - [NextInflationRate](02_begin_block.md#nextinflationrate) + - [NextAnnualProvisions](02_begin_block.md#nextannualprovisions) + - [BlockProvision](02_begin_block.md#blockprovision) diff --git a/docs/spec/params/keeper.md b/docs/spec/params/01_keeper.md similarity index 100% rename from docs/spec/params/keeper.md rename to docs/spec/params/01_keeper.md diff --git a/docs/spec/params/subspace.md b/docs/spec/params/02_subspace.md similarity index 100% rename from docs/spec/params/subspace.md rename to docs/spec/params/02_subspace.md diff --git a/docs/spec/params/README.md b/docs/spec/params/README.md index d9c8b9221..a7728e868 100644 --- a/docs/spec/params/README.md +++ b/docs/spec/params/README.md @@ -16,5 +16,8 @@ The following contents explains how to use params module for master and user mod ## Contents -1. [Keeper](keeper.md) -1. [Subspace](subspace.md) +1. **[Keeper](01_keeper.md)** +2. **[Subspace](02_subspace.md)** + - [Key](02_subspace.md#key) + - [KeyTable](02_subspace.md#keytable) + - [ParamSet](02_subspace.md#paramset) diff --git a/docs/spec/slashing/overview.md b/docs/spec/slashing/01_concepts.md similarity index 95% rename from docs/spec/slashing/overview.md rename to docs/spec/slashing/01_concepts.md index 63f0e494f..006c4ed9b 100644 --- a/docs/spec/slashing/overview.md +++ b/docs/spec/slashing/01_concepts.md @@ -1,12 +1,12 @@ -## Conceptual overview +# Concepts -### States +## States At any given time, there are any number of validators registered in the state machine. Each block, the top `n = MaximumBondedValidators` validators who are not jailed become *bonded*, meaning that they may propose and vote on blocks. Validators who are *bonded* are *at stake*, meaning that part or all of their stake and their delegators' stake is at risk if they commit a protocol fault. -### Tombstone Caps +## Tombstone Caps In order to mitigate the impact of initially likely categories of non-malicious protocol faults, the Cosmos Hub implements for each validator a *tombstone* cap, which only allows a validator to be slashed once for a double sign fault. For example, if you misconfigure your HSM and double-sign @@ -15,7 +15,7 @@ to avoid, but tombstone caps somewhat blunt the economic impact of unintentional Liveness faults do not have caps, as they can't stack upon each other. Liveness bugs are "detected" as soon as the infraction occurs, and the validators are immediately put in jail, so it is not possible for them to commit multiple liveness faults without unjailing in between. -#### ASCII timelines +## ASCII timelines *Code* @@ -39,4 +39,4 @@ A single infraction is committed then later discovered, at which point the valid [----------C1--C2---C3---D1,D2,D3Vu-----] Multiple infractions are committed and then later discovered, at which point the validator is jailed and slashed for only one infraction. -Because the validator is also tombstoned, they can not rejoin the validator set. \ No newline at end of file +Because the validator is also tombstoned, they can not rejoin the validator set. diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/02_state.md similarity index 100% rename from docs/spec/slashing/state.md rename to docs/spec/slashing/02_state.md diff --git a/docs/spec/slashing/messages.md b/docs/spec/slashing/03_messages.md similarity index 97% rename from docs/spec/slashing/messages.md rename to docs/spec/slashing/03_messages.md index 342f3468b..ce9c1af94 100644 --- a/docs/spec/slashing/messages.md +++ b/docs/spec/slashing/03_messages.md @@ -1,8 +1,8 @@ -## Messages +# Messages In this section we describe the processing of messages for the `slashing` module. -### Unjail +## Unjail If a validator was automatically unbonded due to downtime and wishes to come back online & possibly rejoin the bonded set, it must send `TxUnjail`: diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/04_begin_block.md similarity index 100% rename from docs/spec/slashing/begin-block.md rename to docs/spec/slashing/04_begin_block.md diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/05_hooks.md similarity index 100% rename from docs/spec/slashing/hooks.md rename to docs/spec/slashing/05_hooks.md diff --git a/docs/spec/slashing/tags.md b/docs/spec/slashing/06_tags.md similarity index 100% rename from docs/spec/slashing/tags.md rename to docs/spec/slashing/06_tags.md diff --git a/docs/spec/slashing/tombstone.md b/docs/spec/slashing/07_tombstone.md similarity index 100% rename from docs/spec/slashing/tombstone.md rename to docs/spec/slashing/07_tombstone.md diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index 2240930f8..a63be0c1d 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -16,15 +16,20 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste ## Contents -1. **[Overview](overview.md)** -1. **[State](state.md)** - 1. [SigningInfo](state.md#signing-info) -2. **[Transactions](transactions.md)** - 1. [Unjail](transactions.md#unjail) -3. **[Hooks](hooks.md)** - 1. [Validator Bonded](hooks.md#validator-bonded) -4. **[Begin Block](begin-block.md)** - 1. [Evidence handling](begin-block.md#evidence-handling) - 2. [Uptime tracking](begin-block.md#uptime-tracking) -5. **[Future Improvements](future-improvements.md)** - 1. [State cleanup](future-improvements.md#state-cleanup) +1. **[Concepts](01_concepts.md)** + - [States](01_concepts.md#states) + - [Tombstone Caps](01_concepts.md#tombstone-caps) + - [ASCII timelines](01_concepts.md#ascii-timelines) +2. **[State](02_state.md)** + - [Signing Info](02_state.md#signing-info) +3. **[Messages](03_messages.md)** + - [Unjail](03_messages.md#unjail) +4. **[Begin-Block](04_begin_block.md)** + - [Evidence handling](04_begin_block.md#evidence-handling) + - [Uptime tracking](04_begin_block.md#uptime-tracking) +5. **[05_hooks.md](05_hooks.md)** + - [Hooks](05_hooks.md#hooks) +6. **[Tags](06_tags.md)** + - [Handlers](06_tags.md#handlers) +7. **[Staking Tombstone](07_tombstone.md)** + - [Abstract](07_tombstone.md#abstract) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/01_state.md similarity index 99% rename from docs/spec/staking/state.md rename to docs/spec/staking/01_state.md index 0adcb5819..b730de8a5 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/01_state.md @@ -117,7 +117,7 @@ with the `ValidatorAddr` Delegators are indexed in the store as follows: - Delegation: ` 0x31 | DelegatorAddr | ValidatorAddr -> amino(delegation)` -Atom holders may delegate coins to validators; under this circumstance their +Stake holders may delegate coins to validators; under this circumstance their funds are held in a `Delegation` data structure. It is owned by one delegator, and is associated with the shares for one validator. The sender of the transaction is the owner of the bond. diff --git a/docs/spec/staking/state_transitions.md b/docs/spec/staking/02_state_transitions.md similarity index 100% rename from docs/spec/staking/state_transitions.md rename to docs/spec/staking/02_state_transitions.md diff --git a/docs/spec/staking/messages.md b/docs/spec/staking/03_messages.md similarity index 100% rename from docs/spec/staking/messages.md rename to docs/spec/staking/03_messages.md diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/04_end_block.md similarity index 100% rename from docs/spec/staking/end_block.md rename to docs/spec/staking/04_end_block.md diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/05_hooks.md similarity index 100% rename from docs/spec/staking/hooks.md rename to docs/spec/staking/05_hooks.md diff --git a/docs/spec/staking/tags.md b/docs/spec/staking/06_tags.md similarity index 100% rename from docs/spec/staking/tags.md rename to docs/spec/staking/06_tags.md diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md index 09f457589..a44d8ed3c 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/README.md @@ -16,34 +16,29 @@ network. ## Contents -The following specification uses *Atom* as the native staking token. The module -can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the -native staking token of the chain. - - 1. **[State](state.md)** - - Pool - - Params - - Validator - - Delegation - - UnbondingDelegation - - Redelegation - - Queues - 2. **[State Transistions](state_transitions.md)** - - Validator - - Delegation - - Slashing - 3. **[Messages](messages.md)** - - MsgCreateValidator - - MsgEditValidator - - MsgDelegate - - MsgBeginUnbonding - - MsgBeginRedelegate - 4. **[End-Block](end_block.md)** - - Validator Set Changes - - Queues - - Unbonding Validators - - Unbonding Delegations - - Redelegations - 5. **[Hooks](hooks.md)** - 6. **[Tags](tags.md)** - +1. **[State](01_state.md)** + - [Pool](01_state.md#pool) + - [LastTotalPower](01_state.md#lasttotalpower) + - [Params](01_state.md#params) + - [Validator](01_state.md#validator) + - [Delegation](01_state.md#delegation) + - [UnbondingDelegation](01_state.md#unbondingdelegation) + - [Redelegation](01_state.md#redelegation) + - [Queues](01_state.md#queues) +2. **[State Transitions](02_state_transitions.md)** + - [Validators](02_state_transitions.md#validators) + - [Delegations](02_state_transitions.md#delegations) + - [Slashing](02_state_transitions.md#slashing) +3. **[Messages](03_messages.md)** + - [MsgCreateValidator](03_messages.md#msgcreatevalidator) + - [MsgEditValidator](03_messages.md#msgeditvalidator) + - [MsgDelegate](03_messages.md#msgdelegate) + - [MsgBeginUnbonding](03_messages.md#msgbeginunbonding) + - [MsgBeginRedelegate](03_messages.md#msgbeginredelegate) +4. **[End-Block ](04_end_block.md)** + - [Validator Set Changes](04_end_block.md#validator-set-changes) + - [Queues ](04_end_block.md#queues-) +5. **[Hooks](05_hooks.md)** +6. **[Tags](06_tags.md)** + - [EndBlocker](06_tags.md#endblocker) + - [Handlers](06_tags.md#handlers) diff --git a/docs/translations/kr/gaia/gaiacli.md b/docs/translations/kr/gaia/gaiacli.md index 2feb51d37..3150d8399 100755 --- a/docs/translations/kr/gaia/gaiacli.md +++ b/docs/translations/kr/gaia/gaiacli.md @@ -31,7 +31,7 @@ gaiacli config chain-id gaia-9004 키의 형태는 총 3개가 있습니다: - `cosmos` - - `gaiacli keyts add`로 생성되는 계정 키 + - `gaiacli keys add`로 생성되는 계정 키 - 자금을 받는데 사용 - 예시) `cosmos15h6vd5f0wqps26zjlwrc6chah08ryu4hzzdwhc` @@ -55,10 +55,10 @@ gaiacli config chain-id gaia-9004 새로운 _secp256k1_ 키를 생성하기 위해서는: ```bash -gaiacli keys add +gaiacli keys add ``` -새로운 키를 생성하는 과정에서 나오는 _시트키(seed phrase)_ 는 안전하게 저장하시길 바랍니다. 시드키는 다음과 같은 명령을 이용해 잊어버린 퍼블릭/프라이빗 키를 복구하는데 이용됩니다: +새로운 키를 생성하는 과정에서 나오는 _시트키(seed phrase)_ 는 안전하게 저장하시길 바랍니다. 시드키는 다음과 같은 명령을 실행하여 잊어버린 퍼블릭/프라이빗 키를 복구하는데 이용됩니다: ```bash gaiacli keys add --recover @@ -67,7 +67,7 @@ gaiacli keys add --recover 이제 프라이빗 키를 확인하고 ``을 찾으면 됩니다: ```bash -gaiacli keys show +gaiacli keys show ``` 검증인 운영자 주소는 다음과 같이 확인하시고: @@ -82,16 +82,16 @@ gaiacli keys show --bech=val gaiacli keys list ``` -본인 노드의 검증인 pubkey는 다음과 같이 확인할 수 있습니다: +본인이 연결된 노드의 검증인 pubkey는 다음과 같이 확인할 수 있습니다: ```bash gaiad tendermint show-validator ``` -위 키는 텐더민트 사이닝 키이며, 위임 트랜잭션에서 이용되는 오퍼레이터 키가 아니라는 점을 참고하세요. +위 키는 텐더민트 사이닝 키이며, 위임 트랜잭션에서 이용되는 '오퍼레이터 키'가 아니라는 점을 참고하세요. ::: danger 경고 -다수의 키에 동일한 passphrase를 이용하는 것을 추천하지 않습니다. 텐더민트 팀과 인터체인 재단은 자산 손실에 대한 책임을 지지 않습니다. +다수의 키에 동일한 passphrase를 사용하는 것을 추천하지 않습니다. 텐더민트 팀과 인터체인 재단은 자산 손실에 대한 책임을 지지 않습니다. ::: #### 멀티시그 퍼블릭 키 생성하기 @@ -104,7 +104,7 @@ gaiacli keys add --multisig=name1,name2,name3[...] --multisig-threshold=K new_ke 여기서 `K`는 트랜잭션이 승인되기 위해서 필요한 최소의 키 개수입니다. -`--multisig` 플래그는 로컬 데이터베이스에 `new_key_name`으로 저장될 멀티시그 퍼블릭 키를 생성할때 사용되는 다수의 퍼블릭 키들의 값을 뜻합니다. `--multisig` 값에 포함될 키들은 로컬 데이터베이스에 존재하는 상태여야 합니다. `--nosort` 플래그가 정의된지 않은 경우, 멀티시그 조합에 필요한 키들이 입력되는 순서는 무관합니다. 예를 들어 다음 두 명령어는 두개의 동일한 멀티시그 퍼블릭 키를 생성합니다: +`--multisig` 플래그는 로컬 데이터베이스에 `new_key_name`으로 저장될 멀티시그 퍼블릭 키를 생성할때 사용되는 다수의 퍼블릭 키들의 값을 뜻합니다. `--multisig` 값에 포함될 각 키는 로컬 데이터베이스에 존재해야 합니다. `--nosort` 플래그가 정의된지 않은 경우, 멀티시그 조합에 필요한 키들이 입력되는 순서는 무관합니다. 예를 들어 다음 두 명령어는 두개의 동일한 멀티시그 퍼블릭 키를 생성합니다: ```bash gaiacli keys add --multisig=foo,bar,baz --multisig-threshold=2 multisig_address @@ -117,11 +117,11 @@ gaiacli keys add --multisig=baz,foo,bar --multisig-threshold=2 multisig_address gaiacli keys show --multisig-threshold K name1 name2 name3 [...] ``` -멀티시그 트랜잭션를 생성, 사인, 전파하는 방법은 [멀티시그 트랜잭션](#멀티시그-트랜잭션) 항목을 참고하세요. +멀티시그 트랜잭션를 생성, 서명, 전파하는 방법은 [멀티시그 트랜잭션](#멀티시그-트랜잭션) 항목을 참고하세요. ### 수수료와 가스 -각 트랜잭션은 수수료(fee)를 지정하거나 가스 가격(gas price)을 지정할 수 있지만, 두 값을 함께 지정하는 것은 불가능합니다. 대다수의 유저는 지정된 비용만을 수수료로 사용하기 위해 수수료(fee)를 지정하는 방식으로 트랜잭션을 발생할 것으로 예상됩니다. +각 트랜잭션은 수수료(fee)를 지정하거나 가스 가격(gas price)을 지정할 수 있지만, 두 값을 함께 지정하는 것은 불가능합니다. 대다수의 유저는 본인이 지불하고 싶은 수수료 금액을 지정할 것으로 예상되며, 이 경우에는 수수료(fee)를 지정하는 방식으로 트랜잭션을 생성하면 됩니다. 각 검증인은 최소 가스 가격(minimum gas price)를 (다수 토큰 사용 가능) 설정할 수 있으며 이 값을 기준으로 `CheckTx` 단계에서 특정 트랜잭션을 블록에 포함시킬지 확인합니다. `gasPrices >= minGasPrices`일때 검증인은 트랜잭션을 처리합니다. 참고로 트랜잭션 전파시 검증인이 요구하는 토큰 중 하나를 수수료 지불 토큰으로 사용하셔야 합니다. @@ -150,7 +150,7 @@ gaiacli tx send ... --gas-prices=0.000001stake 주소에 토큰을 받으신 후 잔고를 확인하시려면 다음 명령어를 입력하시면 됩니다: ```bash -gaiacli query account +gaiacli query account ``` ::: warning 참고 @@ -162,9 +162,9 @@ gaiacli query account 한 계정에서 다른 계정으로 토큰/코인을 전송하기 위해서는 다음 명령어를 이용하시면 됩니다: ```bash -gaiacli tx send 10faucetToken \ - --chain-id= \ - --from= \ +gaiacli tx send 10faucetToken \ + --chain-id= \ + --from= \ ``` ::: warning 참고 @@ -172,37 +172,37 @@ gaiacli tx send 10faucetToken \ ::: ::: tip 참고 -해당 트랜잭션이 사용하는 가스 값의 최대치를 설정하기 원하시면 `--gas` 플래그를 이용하세요. 만약 `--gas=auto`를 이용하시는 경우, 트랜잭션이 실행되기 전에 가스 서플라이가 자동으로 예측됩니다. 예측된 가스 값과 실제 트랜잭션이 일어나는 사이에 블록체인 상태가 변경될 수 있으며, 기존 예측 수량에서 값이 변경이 될 수 있다는 점을 유의하세요. 변경 값은 `--gas-adjustment` 플래그를 이용해 설정하실 수 있으며 기본 값은 1.0입니다. +해당 트랜잭션이 사용할 가스에 한도를 설정하기 원하시면 `--gas` 플래그를 이용하세요. 만약 `--gas=auto`를 이용하시는 경우, 트랜잭션이 실행되기 전에 가스 서플라이가 자동으로 예측됩니다. 예측된 가스 값과 실제 트랜잭션이 일어나는 사이에 블록체인 상태가 변경될 수 있으며, 기존 예측 수량에서 값이 변경이 될 수 있다는 점을 유의하십시오. 변경 값은 `--gas-adjustment` 플래그를 이용해 설정하실 수 있으며 기본 값은 1.0입니다. ::: 이제 토큰을 전송한 계정과 토큰을 받은 계정의 잔고를 확인합니다: ```bash -gaiacli query account -gaiacli query account +gaiacli query account +gaiacli query account ``` -특정 블록에서의 잔고를 확인하고 싶으시다면 `--block` 플래그를 사용하실 수 있습니다: +특정 블록 높의에서의 잔고를 확인하고 싶으시다면 `--block` 플래그를 사용하실 수 있습니다: ```bash -gaiacli query account --block= +gaiacli query account --block= ``` 트랜잭션을 실제 전파하지 않고 시뮬레이션을 하시려면 명령어 뒤에 `--dry-run` 플래그를 추가하세요: ```bash -gaiacli tx send 10faucetToken \ - --chain-id= \ - --from= \ +gaiacli tx send 10faucetToken \ + --chain-id= \ + --from= \ --dry-run ``` 또한 트랜잭션을 빌드한 후 해당 트랜잭션을 JSON 포맷으로 STDOUT에 프린트 하시기를 원하면 `--generate-only`를 명령어에 추가하시면 됩니다: ```bash -gaiacli tx send 10faucetToken \ - --chain-id= \ - --from= \ +gaiacli tx send 10faucetToken \ + --chain-id= \ + --from= \ --generate-only > unsignedSendTx.json ``` @@ -214,8 +214,8 @@ gaiacli tx send 10faucetToken \ ```bash gaiacli tx sign \ - --chain-id= \ - --from= + --chain-id= \ + --from= unsignedSendTx.json > signedSendTx.json ``` @@ -237,12 +237,12 @@ gaiacli tx broadcast --node= signedSendTx.json 트랜잭션 검색 명령을 이용하여 모든 트랜잭션에 추가되는 특정 `tags` 세트를 검색할 수 있습니다. -각 태그의 키-값 페어는 `:` 형태로 이루어집니다. 더 상세한 검색을 원하실 경우 `&` 를 사용하여 태그를 추가할 수 있습니다. +각 태그의 키-값 페어는 `:` 형태로 이루어집니다. 더 상세한 검색을 원하실 경우 `&` 를 사용하여 태그를 추가할 수 있습니다. `tag`를 이용한 트랜잭션 조회는 다음과 같이 합니다: ```bash -gaiacli query txs --tags=':' +gaiacli query txs --tags=':' ``` 다수의 `tags`를 이용하실 경우: @@ -254,7 +254,7 @@ gaiacli query txs --tags=':&:' 페이지네이션은 `page`와 `limit` 값으로 지원됩니다. ```bash -gaiacli query txs --tags=':' --page=1 --limit=20 +gaiacli query txs --tags=':' --page=1 --limit=20 ``` ::: tip 참고 @@ -286,7 +286,7 @@ gaiacli query tx [hash] 제일링 된 검증인을 언제일 하기 위해서는: ```bash -gaiacli tx slashing unjail --from +gaiacli tx slashing unjail --from ``` #### 서명 정보 @@ -294,7 +294,7 @@ gaiacli tx slashing unjail --from 특정 검증인의 서명 정보를 확인하기 위해서는: ```bash -gaiacli query slashing signing-info +gaiacli query slashing signing-info ``` #### 슬래싱 파라미터 조회 @@ -325,7 +325,7 @@ gaiacli query staking validators 특정 검증인에 대한 정보를 원하실 경우 다음 명령을 실행하세요: ```bash -gaiacli query staking validator +gaiacli query staking validator ``` #### 토큰 본딩하기 @@ -336,9 +336,9 @@ gaiacli query staking validator ```bash gaiacli tx staking delegate \ --amount=10steak \ - --validator= \ - --from= \ - --chain-id= + --validator= \ + --from= \ + --chain-id= ``` `` 는 검증 대상 검증인의 운영자 주소입니다. 로컬 테스트넷을 운영하시는 경우, 다음 명령어로 관련 주소를 확인하실 수 있습니다: @@ -361,14 +361,14 @@ gaiacli keys show [name] --bech val 위임 요청을 검증인에게 전송한 경우, 관련 정보를 다음 명령을 통해 조회하실 수 있습니다: ```bash -gaiacli query staking delegation +gaiacli query staking delegation ``` 만약 특정 검증인에 대한 모든 위임 상태를 확인하고 싶으실 경우 다음 명령을 이용하세요: ```bash -gaiacli query staking delegation +gaiacli query staking delegation ``` 과거 위임 기록에 대해서는 `--height` 플래그를 추가 하셔서 해당 블록 높이에 대한 기록을 조회하실 수 있습니다. @@ -380,10 +380,10 @@ gaiacli query staking delegation ```bash gaiacli tx staking unbond \ - --validator= \ + --validator= \ --shares-fraction=0.5 \ - --from= \ - --chain-id= + --from= \ + --chain-id= ``` 언본딩은 언본딩 기간이 끝나는 대로 완료됩니다. @@ -393,19 +393,19 @@ gaiacli tx staking unbond \ 언본딩 절차를 시작하신 후 관련 정보를 조회하는 방법은 다음과 같습니다: ```bash -gaiacli query staking unbonding-delegation +gaiacli query staking unbonding-delegation ``` -또는 모든 언본딩 정보를 확인하고 싶으신 경우: +또는 특정 위임자의 모든 언본딩 정보를 확인하고 싶으신 경우: ```bash -gaiacli query staking unbonding-delegations +gaiacli query staking unbonding-delegations ``` 추가적으로 특정 검증인으로 부터 언본딩하는 정보를 확인하고 싶으신 경우: ```bash -gaiacli query staking unbonding-delegations-from +gaiacli query staking unbonding-delegations-from ``` 과거 언본딩 정보는 `--height` 플래그를 통해서 특정 블록 높이에 대한 언본딩 정보를 조회할 수 있습니다. @@ -416,11 +416,11 @@ gaiacli query staking unbonding-delegations-from ```bash gaiacli tx staking redelegate \ - --addr-validator-source= \ - --addr-validator-dest= \ + --addr-validator-source= \ + --addr-validator-dest= \ --shares-fraction=50 \ - --from= \ - --chain-id= + --from= \ + --chain-id= ``` 위 예시와 같이 재위임될 토큰의 수량은 특정 수량(`shares-amount`) 또는 일정 비율(`shares-fraction`)로 표현될 수 있습니다. @@ -432,19 +432,19 @@ gaiacli tx staking redelegate \ 재위임을 시작하신 후, 다음 명령을 통해서 관련 정보를 조회하실 수 있습니다: ```bash -gaiacli query staking redelegation +gaiacli query staking redelegation ``` -모든 검증인에 대한 재위임을 확인하고 싶으신 경우: +특정 위임자의 모든 검증인에 대한 재위임을 확인하고 싶으신 경우: ```bash -gaiacli query staking redelegations +gaiacli query staking redelegations ``` 특정 검증인에 대한 재위임을 확인하고 싶으신 경우: ```bash - gaiacli query staking redelegations-from + gaiacli query staking redelegations-from ``` 과거 재위임에 대한 정보는 다른 트랜잭션과 동일하게 `--height` 플래그를 이용하여 특정 블록 높이에 대한 재위임 정보를 확인하실 수 있습니다. @@ -484,7 +484,7 @@ gaiacli query staking pool 특정 검증인에 대한 모든 위임은 다음 명령으로 조회가 가능합니다: ```bash - gaiacli query delegations-to + gaiacli query delegations-to ``` ### 거버넌스 @@ -512,12 +512,12 @@ gaiacli query staking pool ```bash gaiacli tx gov submit-proposal \ - --title= \ - --description=<description> \ - --type=<Text/ParameterChange/SoftwareUpgrade> \ - --deposit=<40steak> \ - --from=<name> \ - --chain-id=<chain_id> + --title=<title(프로포절 제목)> \ + --description=<description(프로포절 설명)> \ + --type=<Text/ParameterChange/SoftwareUpgrade(프로포절 타입)> \ + --deposit=<40steak(예치금 수량)> \ + --from=<name(트랜잭션을 발생시킬 키/계정 이름)> \ + --chain-id=<chain_id(체인 아이디)> ``` ##### 프로포절 조회 @@ -539,7 +539,7 @@ gaiacli query gov proposals 특정 거버넌스 프로포절의 제안자를 확인하기 위해서는: ```bash -gaiacli query gov proposer <proposal_id> +gaiacli query gov proposer <proposal_id(프로포절 ID)> ``` #### 보증금 추가하기 @@ -547,9 +547,9 @@ gaiacli query gov proposer <proposal_id> 프로포절이 네트워크에 전파되기 위해서는 해당 프로포절의 보증금이 `minDeposit` 값 이상이어야 합니다 (현재 기본 값은 `10 steak`입니다). 만약 사전에 생성한 프로포절이 해당 기준을 충족하지 못하였다면 추후에 보증금을 추가 예치하여 활성화할 수 있습니다. 프로포절의 보증금이 최소 값을 도달하면 해당 프로포절의 투표는 활성화 됩니다: ```bash -gaiacli tx gov deposit <proposal_id> <200steak> \ - --from=<name> \ - --chain-id=<chain_id> +gaiacli tx gov deposit <proposal_id(프로포절 ID)> <200steak(금액)> \ + --from=<name(트랜잭션을 발생시킬 키/계정 이름)> \ + --chain-id=<chain_id(체인 아이디)> ``` > _참고_: 기본 보증금 기준을 충족하지 못한 프로포절은 `MaxDepositPeriod`이 지나면 자동으로 삭제됩니다. @@ -559,13 +559,13 @@ gaiacli tx gov deposit <proposal_id> <200steak> \ 새로운 프로포절이 생성된 후, 해당 프로포절에 대한 보증금은 다음과 같이 조회할 수 있습니다: ```bash -gaiacli query gov deposits <proposal_id> +gaiacli query gov deposits <proposal_id(프로포절 ID)> ``` 특정 주소에 대한 보증금은 다음과 같이 확인하실 수 있습니다: ```bash -gaiacli query gov deposit <proposal_id> <depositor_address> +gaiacli query gov deposit <proposal_id(프로포절 ID)> <depositor_address(예치자 주소)> ``` #### 프로포절 투표하기 @@ -574,9 +574,9 @@ gaiacli query gov deposit <proposal_id> <depositor_address> ```bash -gaiacli tx gov vote <proposal_id> <Yes/No/NoWithVeto/Abstain> \ - --from=<name> \ - --chain-id=<chain_id> +gaiacli tx gov vote <proposal_id> <Yes/No/NoWithVeto/Abstain(표 선택)> \ + --from=<name(트랜잭션을 발생시킬 키/계정 이름)> \ + --chain-id=<chain_id(체인 아이디)> ``` ##### 표 조회하기 @@ -584,12 +584,12 @@ gaiacli tx gov vote <proposal_id> <Yes/No/NoWithVeto/Abstain> \ 특정 표와 관련한 정보를 조회하기 위해서는: ```bash -gaiacli query gov vote <proposal_id> <voter_address> +gaiacli query gov vote <proposal_id(프로포절 ID)> <voter_address(투표자 주소)> ``` 과거 프로포절에 대한 표 정보를 확인하기 위해서는: ```bash -gaiacli query gov votes <proposal_id> +gaiacli query gov votes <proposal_id(프로포절 ID)> ``` #### 프로포절 결과 조회하기 @@ -597,7 +597,7 @@ gaiacli query gov votes <proposal_id> 특정 프로포절에 대한 결과를 확인하기 위해서는: ```bash -gaiacli query gov tally <proposal_id> +gaiacli query gov tally <proposal_id(프로포절 ID)> ``` #### 거버넌스 파라미터 조회하기 @@ -633,7 +633,7 @@ gaiacli query distr outstanding-rewards 특정 검증인의 커미션을 조회하기 위해서는: ```bash -gaiacli query distr commission <validator_address> +gaiacli query distr commission <validator_address(검증인 주소)> ``` #### 검증인 슬래싱 조회 @@ -641,7 +641,7 @@ gaiacli query distr commission <validator_address> 특정 검증인의 슬래싱 기록을 조회하기 위해서는: ```bash -gaiacli query distr slashes <validator_address> <start_height> <end_height> +gaiacli query distr slashes <validator_address(검증인 주소)> <start_height(시작 블록 높이)> <end_height(마지막 블록 높이)> ``` #### 특정 검증인에서 수령되지 않은 리워드 조회 @@ -649,7 +649,7 @@ gaiacli query distr slashes <validator_address> <start_height> <end_height> 위임자의 특정 검증인에서 발생된 미수령 리워드를 조회하기 위해서는: ```bash -gaiacli query distr rewards <delegator_address> <validator_address> +gaiacli query distr rewards <delegator_address(위임자 주소)> <validator_address(검증인 주소)> ``` #### 위임자의 수령 대기중인 모든 리워드 조회 @@ -657,7 +657,7 @@ gaiacli query distr rewards <delegator_address> <validator_address> 위임자의 모든 수령 대기 리워드를 조회하기 위해서는: ```bash -gaiacli query distr rewards <delegator_address> +gaiacli query distr rewards <delegator_address(위임자 주소)> ``` ### 멀티시그 트랜잭션 @@ -689,7 +689,7 @@ gaiacli keys show --address p1p2p3 ```bash gaiacli tx send cosmos1570v2fq3twt0f0x02vhxpuzc9jc4yl30q2qned 10stake \ - --from=<multisig_address> \ + --from=<multisig_address(멀티시그 주소)> \ --generate-only > unsignedTx.json ``` @@ -697,7 +697,7 @@ gaiacli tx send cosmos1570v2fq3twt0f0x02vhxpuzc9jc4yl30q2qned 10stake \ ```bash gaiacli tx sign \ - --multisig=<multisig_address> \ + --multisig=<multisig_address(멀티시그 주소)> \ --name=p1 \ --output-document=p1signature.json \ unsignedTx.json @@ -707,7 +707,7 @@ gaiacli tx sign \ ```bash gaiacli tx sign \ - --multisig=<multisig_address> \ + --multisig=<multisig_address(멀티시그 주소)> \ --name=p2 \ --output-document=p2signature.json \ unsignedTx.json diff --git a/docs/translations/kr/gaia/validators/validator-setup.md b/docs/translations/kr/gaia/validators/validator-setup.md index aeaff5ff3..aa105bfc5 100755 --- a/docs/translations/kr/gaia/validators/validator-setup.md +++ b/docs/translations/kr/gaia/validators/validator-setup.md @@ -57,13 +57,6 @@ __참고__: 이 문항은 제네시스 파일에 참가하려는 밸리데이터 밸리데이터로써 제네시스에 참가하고 싶으시다면 우선 본인(또는 위임자)가 stake를 보유하고 있다는 것을 증명해야 합니다. 스테이크를 검증인에게 본딩하는 하나 이상의 트랜잭션을 발생하신 후, 해당 트랜잭션을 제네시스 파일에 추가하시기 바랍니다. -우선 두가지의 케이스가 존재합니다: - -- 경우 1: 본인 밸리데이터의 stake를 본딩(위임)한다. -- 경우 2: 타인(위임자)의 stake를 본딩한다. - -### Case 1: 최초 위임이 밸리데이터 본인 주소에서 발생하는 경우 - 이런 경우에는 `gentx`를 생성하셔야 합니다: ```bash @@ -84,40 +77,6 @@ __참고__: 이 명령어는 제네시스에서의 처리를 위해 `gentx`를 ` `gentx`는 자체위임 정보가 포함된 JSON 파일입니다. 모든 제네시스 트랜잭셕은 `genesis coordinator`에 의하여 모아진 후 최초 `genesis.json`파일과 대치하여 검증합니다. 최초 `genesis.json`에는 계정 리스트와 각 계정이 보유하고 있는 코인 정보가 포함되어있습니다. 트랜잭션이 처리되었다면 해당 정보는 `genesis.json`의 `gentx` 항목에 머지(merge)됩니다. -### Case 2: 최초 위임이 위임자(delegator) 주소에서 발생하는 경우 - -이런 경우에는 위임자와 검증인의 서명이 둘다 필요합니다. 우선 서명이 되지 않은 `create-validator` 트랜잭션을 생성하신 후 `unsignedValTx`라는 파일에 저장하십시오: - -```bash -gaiacli tx staking create-validator \ - --amount=5STAKE \ - --pubkey=$(gaiad tendermint show-validator) \ - --moniker="choose a moniker" \ - --chain-id=<chain_id> \ - --from=<key_name> \ - --commission-rate="0.10" \ - --commission-max-rate="0.20" \ - --commission-max-change-rate="0.01" \ - --address-delegator="address of the delegator" \ - --generate-only \ - > unsignedValTx.json -``` - -이제 해당 `unsignedValTx`를 밸리데이터의 프라이빗 키를 이용해 서명합니다. 서명이된 아웃풋을 `signedValTx.json`이라는 파일에 저장합니다: - -```bash -gaiacli tx sign unsignedValTx.json --from=<validator_key_name> > signedValTx.json -``` - -이제 이 파일을 위임자에게 전달하세요. 위임인은 다음 명령어를 실행하면 됩니다: - -```bash -gaiacli tx sign signedValTx.json --from=<delegator_key_name> > gentx.json -``` - -이 파일은 제네시스 절차에서 필요하기 때문에 Case 1과 동일하게 `gentx.json`은 밸리데이터 머신의 `~/.gaiad/config/gentx` 폴더에 포함되어야 합니다 (Case 2 에서는 직접 해당 파을을 이동해야 합니다). - - ### 제네시스 파일 복사, 제네시스 트랜잭션 처리하기 우선 `genesis.json`파일을 `gaiad`의 config 디렉토리로 가져옵니다. diff --git a/server/constructors.go b/server/constructors.go index 5a51937a9..ac087d657 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -24,7 +25,7 @@ type ( func openDB(rootDir string) (dbm.DB, error) { dataDir := filepath.Join(rootDir, "data") - db, err := dbm.NewGoLevelDB("application", dataDir) + db, err := sdk.NewLevelDB("application", dataDir) return db, err } diff --git a/server/export.go b/server/export.go index 5256aa76c..1df998f5f 100644 --- a/server/export.go +++ b/server/export.go @@ -10,9 +10,9 @@ import ( "github.com/spf13/viper" "io/ioutil" - "path" "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" @@ -34,32 +34,35 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C config.SetRoot(viper.GetString(cli.HomeFlag)) traceWriterFile := viper.GetString(flagTraceStore) - emptyState, err := isEmptyState(config.RootDir) - if err != nil { - return err - } - - if emptyState || appExporter == nil { - fmt.Fprintln(os.Stderr, "WARNING: State is not initialized. Returning genesis file.") - genesis, err := ioutil.ReadFile(config.GenesisFile()) - if err != nil { - return err - } - fmt.Println(string(genesis)) - return nil - } db, err := openDB(config.RootDir) if err != nil { return err } + + if isEmptyState(db) || appExporter == nil { + if _, err := fmt.Fprintln(os.Stderr, "WARNING: State is not initialized. Returning genesis file."); err != nil { + return err + } + + genesis, err := ioutil.ReadFile(config.GenesisFile()) + if err != nil { + return err + } + + fmt.Println(string(genesis)) + return nil + } + traceWriter, err := openTraceWriter(traceWriterFile) if err != nil { return err } + height := viper.GetInt64(flagHeight) forZeroHeight := viper.GetBool(flagForZeroHeight) jailWhiteList := viper.GetStringSlice(flagJailWhitelist) + appState, validators, err := appExporter(ctx.Logger, db, traceWriter, height, forZeroHeight, jailWhiteList) if err != nil { return fmt.Errorf("error exporting state: %v", err) @@ -82,17 +85,16 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C return nil }, } + cmd.Flags().Int64(flagHeight, -1, "Export state from a particular height (-1 means latest height)") cmd.Flags().Bool(flagForZeroHeight, false, "Export state to start at height zero (perform preproccessing)") cmd.Flags().StringSlice(flagJailWhitelist, []string{}, "List of validators to not jail state export") return cmd } -func isEmptyState(home string) (bool, error) { - files, err := ioutil.ReadDir(path.Join(home, "data")) - if err != nil { - return false, err +func isEmptyState(db dbm.DB) bool { + if db.Stats()["leveldb.sstables"] != "" { + return false } - - return len(files) == 0, nil + return true } diff --git a/server/mock/app.go b/server/mock/app.go index e2f94c1f1..9063bb7c5 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -8,7 +8,6 @@ import ( "github.com/tendermint/tendermint/types" abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" bam "github.com/cosmos/cosmos-sdk/baseapp" @@ -20,7 +19,7 @@ import ( // similar to a real app. Make sure rootDir is empty before running the test, // in order to guarantee consistent results func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { - db, err := dbm.NewGoLevelDB("mock", filepath.Join(rootDir, "data")) + db, err := sdk.NewLevelDB("mock", filepath.Join(rootDir, "data")) if err != nil { return nil, err } diff --git a/server/start.go b/server/start.go index 2caf5541e..4b976c822 100644 --- a/server/start.go +++ b/server/start.go @@ -88,7 +88,7 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error { } // wait forever - cmn.TrapSignal(func() { + cmn.TrapSignal(ctx.Logger, func() { // cleanup err = svr.Stop() if err != nil { diff --git a/snapcraft.yaml b/snapcraft.yaml index 0c24dafe0..beb770c7d 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -4,7 +4,7 @@ summary: Gaia Daemon # 79 char long summary description: | This snap provides the Gaia daemon gaiad and the command line tool gaiacli. -grade: devel # must be 'stable' to release into candidate/stable channels +grade: stable confinement: strict apps: @@ -13,7 +13,7 @@ apps: plugs: [home,network,network-bind] gaiacli: command: bin/gaiacli - plugs: [home,network,network-bind] + plugs: [home,network,network-bind,raw-usb] parts: gaia: diff --git a/store/prefix/store_test.go b/store/prefix/store_test.go index 72eeb400d..3576d421d 100644 --- a/store/prefix/store_test.go +++ b/store/prefix/store_test.go @@ -8,7 +8,10 @@ import ( "github.com/cosmos/cosmos-sdk/store/gaskv" "github.com/cosmos/cosmos-sdk/store/iavl" "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + tiavl "github.com/tendermint/iavl" dbm "github.com/tendermint/tendermint/libs/db" ) @@ -243,7 +246,7 @@ func mockStoreWithStuff() types.KVStore { store.Set(bz("key3"), bz("value3")) store.Set(bz("something"), bz("else")) store.Set(bz(""), bz("")) - store.Set(bz("k"), bz("val")) + store.Set(bz("k"), bz(sdk.PrefixValidator)) store.Set(bz("ke"), bz("valu")) store.Set(bz("kee"), bz("valuu")) return store diff --git a/store/types/utils.go b/store/types/utils.go index a86efdb8c..20e7d2603 100644 --- a/store/types/utils.go +++ b/store/types/utils.go @@ -58,7 +58,7 @@ func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair // range query for all []byte with a certain prefix // Deals with last byte of prefix being FF without overflowing func PrefixEndBytes(prefix []byte) []byte { - if prefix == nil { + if len(prefix) == 0 { return nil } diff --git a/types/address.go b/types/address.go index 62c535d6e..05a18ecab 100644 --- a/types/address.go +++ b/types/address.go @@ -17,19 +17,35 @@ import ( const ( // AddrLen defines a valid address length AddrLen = 20 + // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address + Bech32MainPrefix = "cosmos" + + // PrefixAccount is the prefix for account keys + PrefixAccount = "acc" + // PrefixValidator is the prefix for validator keys + PrefixValidator = "val" + // PrefixConsensus is the prefix for consensus keys + PrefixConsensus = "cons" + // PrefixPublic is the prefix for public keys + PrefixPublic = "pub" + // PrefixOperator is the prefix for operator keys + PrefixOperator = "oper" + + // PrefixAddress is the prefix for addresses + PrefixAddress = "addr" // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address - Bech32PrefixAccAddr = "cosmos" + Bech32PrefixAccAddr = Bech32MainPrefix // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key - Bech32PrefixAccPub = "cosmospub" + Bech32PrefixAccPub = Bech32MainPrefix + PrefixPublic // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address - Bech32PrefixValAddr = "cosmosvaloper" + Bech32PrefixValAddr = Bech32MainPrefix + PrefixValidator + PrefixOperator // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key - Bech32PrefixValPub = "cosmosvaloperpub" + Bech32PrefixValPub = Bech32MainPrefix + PrefixValidator + PrefixOperator + PrefixPublic // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address - Bech32PrefixConsAddr = "cosmosvalcons" + Bech32PrefixConsAddr = Bech32MainPrefix + PrefixValidator + PrefixConsensus // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key - Bech32PrefixConsPub = "cosmosvalconspub" + Bech32PrefixConsPub = Bech32MainPrefix + PrefixValidator + PrefixConsensus + PrefixPublic ) // Address is a common interface for different types of addresses used by the SDK @@ -263,7 +279,7 @@ func (va *ValAddress) UnmarshalJSON(data []byte) error { err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } va2, err := ValAddressFromBech32(s) @@ -399,7 +415,7 @@ func (ca *ConsAddress) UnmarshalJSON(data []byte) error { err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } ca2, err := ConsAddressFromBech32(s) diff --git a/types/address_test.go b/types/address_test.go index 6aed5d565..129f002a1 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -213,25 +213,50 @@ func TestConfiguredPrefix(t *testing.T) { rand.Read(pub[:]) // Test if randomly generated prefix of a given length works prefix := RandString(length) + // Assuming that GetConfig is not sealed. config := types.GetConfig() - config.SetBech32PrefixForAccount(prefix+"acc", prefix+"pub") + config.SetBech32PrefixForAccount( + prefix+types.PrefixAccount, + prefix+types.PrefixPublic) + acc := types.AccAddress(pub.Address()) - require.True(t, strings.HasPrefix(acc.String(), prefix+"acc"), acc.String()) + require.True(t, strings.HasPrefix( + acc.String(), + prefix+types.PrefixAccount), acc.String()) + bech32Pub := types.MustBech32ifyAccPub(pub) - require.True(t, strings.HasPrefix(bech32Pub, prefix+"pub")) + require.True(t, strings.HasPrefix( + bech32Pub, + prefix+types.PrefixPublic)) + + config.SetBech32PrefixForValidator( + prefix+types.PrefixValidator+types.PrefixAddress, + prefix+types.PrefixValidator+types.PrefixPublic) - config.SetBech32PrefixForValidator(prefix+"valaddr", prefix+"valpub") val := types.ValAddress(pub.Address()) - require.True(t, strings.HasPrefix(val.String(), prefix+"valaddr")) - bech32ValPub := types.MustBech32ifyValPub(pub) - require.True(t, strings.HasPrefix(bech32ValPub, prefix+"valpub")) + require.True(t, strings.HasPrefix( + val.String(), + prefix+types.PrefixValidator+types.PrefixAddress)) + + bech32ValPub := types.MustBech32ifyValPub(pub) + require.True(t, strings.HasPrefix( + bech32ValPub, + prefix+types.PrefixValidator+types.PrefixPublic)) + + config.SetBech32PrefixForConsensusNode( + prefix+types.PrefixConsensus+types.PrefixAddress, + prefix+types.PrefixConsensus+types.PrefixPublic) - config.SetBech32PrefixForConsensusNode(prefix+"consaddr", prefix+"conspub") cons := types.ConsAddress(pub.Address()) - require.True(t, strings.HasPrefix(cons.String(), prefix+"consaddr")) + require.True(t, strings.HasPrefix( + cons.String(), + prefix+types.PrefixConsensus+types.PrefixAddress)) + bech32ConsPub := types.MustBech32ifyConsPub(pub) - require.True(t, strings.HasPrefix(bech32ConsPub, prefix+"conspub")) + require.True(t, strings.HasPrefix( + bech32ConsPub, + prefix+types.PrefixConsensus+types.PrefixPublic)) } } diff --git a/types/coin.go b/types/coin.go index f969d2c43..365e0f2ef 100644 --- a/types/coin.go +++ b/types/coin.go @@ -1,6 +1,7 @@ package types import ( + "errors" "fmt" "regexp" "sort" @@ -27,10 +28,10 @@ type Coin struct { // NewCoin returns a new coin with a denomination and amount. It will panic if // the amount is negative. func NewCoin(denom string, amount Int) Coin { - validateDenom(denom) + mustValidateDenom(denom) if amount.LT(ZeroInt()) { - panic(fmt.Sprintf("negative coin amount: %v\n", amount)) + panic(fmt.Errorf("negative coin amount: %v", amount)) } return Coin{ @@ -86,7 +87,7 @@ func (coin Coin) IsEqual(other Coin) bool { // Adds amounts of two coins with same denom. If the coins differ in denom then // it panics. -func (coin Coin) Plus(coinB Coin) Coin { +func (coin Coin) Add(coinB Coin) Coin { if coin.Denom != coinB.Denom { panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) } @@ -96,7 +97,7 @@ func (coin Coin) Plus(coinB Coin) Coin { // Subtracts amounts of two coins with same denom. If the coins differ in denom // then it panics. -func (coin Coin) Minus(coinB Coin) Coin { +func (coin Coin) Sub(coinB Coin) Coin { if coin.Denom != coinB.Denom { panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) } @@ -148,7 +149,7 @@ func (coins Coins) IsValid() bool { case 0: return true case 1: - if strings.ToLower(coins[0].Denom) != coins[0].Denom { + if err := validateDenom(coins[0].Denom); err != nil { return false } return coins[0].IsPositive() @@ -178,27 +179,27 @@ func (coins Coins) IsValid() bool { } } -// Plus adds two sets of coins. +// Add adds two sets of coins. // // e.g. // {2A} + {A, 2B} = {3A, 2B} // {2A} + {0B} = {2A} // -// NOTE: Plus operates under the invariant that coins are sorted by +// NOTE: Add operates under the invariant that coins are sorted by // denominations. // -// CONTRACT: Plus will never return Coins where one Coin has a non-positive +// CONTRACT: Add will never return Coins where one Coin has a non-positive // amount. In otherwords, IsValid will always return true. -func (coins Coins) Plus(coinsB Coins) Coins { - return coins.safePlus(coinsB) +func (coins Coins) Add(coinsB Coins) Coins { + return coins.safeAdd(coinsB) } -// safePlus will perform addition of two coins sets. If both coin sets are +// safeAdd will perform addition of two coins sets. If both coin sets are // empty, then an empty set is returned. If only a single set is empty, the // other set is returned. Otherwise, the coins are compared in order of their // denomination and addition only occurs when the denominations match, otherwise // the coin is simply added to the sum assuming it's not zero. -func (coins Coins) safePlus(coinsB Coins) Coins { +func (coins Coins) safeAdd(coinsB Coins) Coins { sum := ([]Coin)(nil) indexA, indexB := 0, 0 lenA, lenB := len(coins), len(coinsB) @@ -228,7 +229,7 @@ func (coins Coins) safePlus(coinsB Coins) Coins { indexA++ case 0: // coin A denom == coin B denom - res := coinA.Plus(coinB) + res := coinA.Add(coinB) if !res.IsZero() { sum = append(sum, res) } @@ -246,17 +247,17 @@ func (coins Coins) safePlus(coinsB Coins) Coins { } } -// Minus subtracts a set of coins from another. +// Sub subtracts a set of coins from another. // // e.g. // {2A, 3B} - {A} = {A, 3B} // {2A} - {0B} = {2A} // {A, B} - {A} = {B} // -// CONTRACT: Minus will never return Coins where one Coin has a non-positive +// CONTRACT: Sub will never return Coins where one Coin has a non-positive // amount. In otherwords, IsValid will always return true. -func (coins Coins) Minus(coinsB Coins) Coins { - diff, hasNeg := coins.SafeMinus(coinsB) +func (coins Coins) Sub(coinsB Coins) Coins { + diff, hasNeg := coins.SafeSub(coinsB) if hasNeg { panic("negative coin amount") } @@ -264,17 +265,17 @@ func (coins Coins) Minus(coinsB Coins) Coins { return diff } -// SafeMinus performs the same arithmetic as Minus but returns a boolean if any +// SafeSub performs the same arithmetic as Sub but returns a boolean if any // negative coin amount was returned. -func (coins Coins) SafeMinus(coinsB Coins) (Coins, bool) { - diff := coins.safePlus(coinsB.negative()) +func (coins Coins) SafeSub(coinsB Coins) (Coins, bool) { + diff := coins.safeAdd(coinsB.negative()) return diff, diff.IsAnyNegative() } // IsAllGT returns true if for every denom in coins, the denom is present at a // greater amount in coinsB. func (coins Coins) IsAllGT(coinsB Coins) bool { - diff, _ := coins.SafeMinus(coinsB) + diff, _ := coins.SafeSub(coinsB) if len(diff) == 0 { return false } @@ -285,7 +286,7 @@ func (coins Coins) IsAllGT(coinsB Coins) bool { // IsAllGTE returns true iff for every denom in coins, the denom is present at // an equal or greater amount in coinsB. func (coins Coins) IsAllGTE(coinsB Coins) bool { - diff, _ := coins.SafeMinus(coinsB) + diff, _ := coins.SafeSub(coinsB) if len(diff) == 0 { return true } @@ -360,7 +361,7 @@ func (coins Coins) Empty() bool { // Returns the amount of a denom from coins func (coins Coins) AmountOf(denom string) Int { - validateDenom(denom) + mustValidateDenom(denom) switch len(coins) { case 0: @@ -389,8 +390,6 @@ func (coins Coins) AmountOf(denom string) Int { // IsAllPositive returns true if there is at least one coin and all currencies // have a positive value. -// -// TODO: Remove once unsigned integers are used. func (coins Coins) IsAllPositive() bool { if len(coins) == 0 { return false @@ -479,20 +478,25 @@ func (coins Coins) Sort() Coins { var ( // Denominations can be 3 ~ 16 characters long. - reDnm = `[[:alpha:]][[:alnum:]]{2,15}` - reAmt = `[[:digit:]]+` - reDecAmt = `[[:digit:]]*\.[[:digit:]]+` - reSpc = `[[:space:]]*` - reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm)) - reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnm)) + reDnmString = `[a-z][a-z0-9]{2,15}` + reAmt = `[[:digit:]]+` + reDecAmt = `[[:digit:]]*\.[[:digit:]]+` + reSpc = `[[:space:]]*` + reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, reDnmString)) + reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) + reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnmString)) ) -func validateDenom(denom string) { - if len(denom) < 3 || len(denom) > 16 { - panic(fmt.Sprintf("invalid denom length: %s", denom)) +func validateDenom(denom string) error { + if !reDnm.MatchString(denom) { + return errors.New("illegal characters") } - if strings.ToLower(denom) != denom { - panic(fmt.Sprintf("denom cannot contain upper case characters: %s", denom)) + return nil +} + +func mustValidateDenom(denom string) { + if err := validateDenom(denom); err != nil { + panic(err) } } @@ -513,8 +517,8 @@ func ParseCoin(coinStr string) (coin Coin, err error) { return Coin{}, fmt.Errorf("failed to parse coin amount: %s", amountStr) } - if denomStr != strings.ToLower(denomStr) { - return Coin{}, fmt.Errorf("denom cannot contain upper case characters: %s", denomStr) + if err := validateDenom(denomStr); err != nil { + return Coin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %s", err) } return NewCoin(denomStr, amount), nil diff --git a/types/coin_benchmark_test.go b/types/coin_benchmark_test.go index 9c13bf772..93a3fc891 100644 --- a/types/coin_benchmark_test.go +++ b/types/coin_benchmark_test.go @@ -21,7 +21,7 @@ func BenchmarkCoinsAdditionIntersect(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - coinsA.Plus(coinsB) + coinsA.Add(coinsB) } } } @@ -50,7 +50,7 @@ func BenchmarkCoinsAdditionNoIntersect(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - coinsA.Plus(coinsB) + coinsA.Add(coinsB) } } } diff --git a/types/coin_test.go b/types/coin_test.go index 3bbc497f6..50dd8bb52 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -47,7 +47,7 @@ func TestIsEqualCoin(t *testing.T) { } } -func TestPlusCoin(t *testing.T) { +func TestAddCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin @@ -61,15 +61,15 @@ func TestPlusCoin(t *testing.T) { for tcIndex, tc := range cases { if tc.shouldPanic { - require.Panics(t, func() { tc.inputOne.Plus(tc.inputTwo) }) + require.Panics(t, func() { tc.inputOne.Add(tc.inputTwo) }) } else { - res := tc.inputOne.Plus(tc.inputTwo) + res := tc.inputOne.Add(tc.inputTwo) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } } -func TestMinusCoin(t *testing.T) { +func TestSubCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin @@ -85,9 +85,9 @@ func TestMinusCoin(t *testing.T) { for tcIndex, tc := range cases { if tc.shouldPanic { - require.Panics(t, func() { tc.inputOne.Minus(tc.inputTwo) }) + require.Panics(t, func() { tc.inputOne.Sub(tc.inputTwo) }) } else { - res := tc.inputOne.Minus(tc.inputTwo) + res := tc.inputOne.Sub(tc.inputTwo) require.Equal(t, tc.expected, res, "difference of coins is incorrect, tc #%d", tcIndex) } } @@ -97,7 +97,7 @@ func TestMinusCoin(t *testing.T) { inputTwo Coin expected int64 }{NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 1), 0} - res := tc.inputOne.Minus(tc.inputTwo) + res := tc.inputOne.Sub(tc.inputTwo) require.Equal(t, tc.expected, res.Amount.Int64()) } @@ -205,7 +205,7 @@ func TestEqualCoins(t *testing.T) { } } -func TestPlusCoins(t *testing.T) { +func TestAddCoins(t *testing.T) { zero := NewInt(0) one := NewInt(1) two := NewInt(2) @@ -223,13 +223,13 @@ func TestPlusCoins(t *testing.T) { } for tcIndex, tc := range cases { - res := tc.inputOne.Plus(tc.inputTwo) + res := tc.inputOne.Add(tc.inputTwo) assert.True(t, res.IsValid()) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } -func TestMinusCoins(t *testing.T) { +func TestSubCoins(t *testing.T) { zero := NewInt(0) one := NewInt(1) two := NewInt(2) @@ -249,9 +249,9 @@ func TestMinusCoins(t *testing.T) { for i, tc := range testCases { if tc.shouldPanic { - require.Panics(t, func() { tc.inputOne.Minus(tc.inputTwo) }) + require.Panics(t, func() { tc.inputOne.Sub(tc.inputTwo) }) } else { - res := tc.inputOne.Minus(tc.inputTwo) + res := tc.inputOne.Sub(tc.inputTwo) assert.True(t, res.IsValid()) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", i) } diff --git a/types/dec_coin.go b/types/dec_coin.go index 9634b8536..19d5db2bd 100644 --- a/types/dec_coin.go +++ b/types/dec_coin.go @@ -19,7 +19,7 @@ type DecCoin struct { } func NewDecCoin(denom string, amount Int) DecCoin { - validateDenom(denom) + mustValidateDenom(denom) if amount.LT(ZeroInt()) { panic(fmt.Sprintf("negative coin amount: %v\n", amount)) @@ -27,12 +27,12 @@ func NewDecCoin(denom string, amount Int) DecCoin { return DecCoin{ Denom: denom, - Amount: NewDecFromInt(amount), + Amount: amount.ToDec(), } } func NewDecCoinFromDec(denom string, amount Dec) DecCoin { - validateDenom(denom) + mustValidateDenom(denom) if amount.LT(ZeroDec()) { panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount)) @@ -54,7 +54,7 @@ func NewDecCoinFromCoin(coin Coin) DecCoin { return DecCoin{ Denom: coin.Denom, - Amount: NewDecFromInt(coin.Amount), + Amount: coin.Amount.ToDec(), } } @@ -99,7 +99,7 @@ func (coin DecCoin) IsEqual(other DecCoin) bool { } // Adds amounts of two coins with same denom -func (coin DecCoin) Plus(coinB DecCoin) DecCoin { +func (coin DecCoin) Add(coinB DecCoin) DecCoin { if coin.Denom != coinB.Denom { panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) } @@ -107,7 +107,7 @@ func (coin DecCoin) Plus(coinB DecCoin) DecCoin { } // Subtracts amounts of two coins with same denom -func (coin DecCoin) Minus(coinB DecCoin) DecCoin { +func (coin DecCoin) Sub(coinB DecCoin) DecCoin { if coin.Denom != coinB.Denom { panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) } @@ -117,7 +117,7 @@ func (coin DecCoin) Minus(coinB DecCoin) DecCoin { // return the decimal coins with trunctated decimals, and return the change func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) { truncated := coin.Amount.TruncateInt() - change := coin.Amount.Sub(NewDecFromInt(truncated)) + change := coin.Amount.Sub(truncated.ToDec()) return NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change} } @@ -179,29 +179,29 @@ func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) { for i, coin := range coins { truncated, change := coin.TruncateDecimal() out[i] = truncated - changeSum = changeSum.Plus(DecCoins{change}) + changeSum = changeSum.Add(DecCoins{change}) } return out, changeSum } -// Plus adds two sets of DecCoins. +// Add adds two sets of DecCoins. // -// NOTE: Plus operates under the invariant that coins are sorted by +// NOTE: Add operates under the invariant that coins are sorted by // denominations. // -// CONTRACT: Plus will never return Coins where one Coin has a non-positive +// CONTRACT: Add will never return Coins where one Coin has a non-positive // amount. In otherwords, IsValid will always return true. -func (coins DecCoins) Plus(coinsB DecCoins) DecCoins { - return coins.safePlus(coinsB) +func (coins DecCoins) Add(coinsB DecCoins) DecCoins { + return coins.safeAdd(coinsB) } -// safePlus will perform addition of two DecCoins sets. If both coin sets are +// safeAdd will perform addition of two DecCoins sets. If both coin sets are // empty, then an empty set is returned. If only a single set is empty, the // other set is returned. Otherwise, the coins are compared in order of their // denomination and addition only occurs when the denominations match, otherwise // the coin is simply added to the sum assuming it's not zero. -func (coins DecCoins) safePlus(coinsB DecCoins) DecCoins { +func (coins DecCoins) safeAdd(coinsB DecCoins) DecCoins { sum := ([]DecCoin)(nil) indexA, indexB := 0, 0 lenA, lenB := len(coins), len(coinsB) @@ -231,7 +231,7 @@ func (coins DecCoins) safePlus(coinsB DecCoins) DecCoins { indexA++ case 0: // coin A denom == coin B denom - res := coinA.Plus(coinB) + res := coinA.Add(coinB) if !res.IsZero() { sum = append(sum, res) } @@ -261,9 +261,9 @@ func (coins DecCoins) negative() DecCoins { return res } -// Minus subtracts a set of DecCoins from another (adds the inverse). -func (coins DecCoins) Minus(coinsB DecCoins) DecCoins { - diff, hasNeg := coins.SafeMinus(coinsB) +// Sub subtracts a set of DecCoins from another (adds the inverse). +func (coins DecCoins) Sub(coinsB DecCoins) DecCoins { + diff, hasNeg := coins.SafeSub(coinsB) if hasNeg { panic("negative coin amount") } @@ -271,13 +271,30 @@ func (coins DecCoins) Minus(coinsB DecCoins) DecCoins { return diff } -// SafeMinus performs the same arithmetic as Minus but returns a boolean if any +// SafeSub performs the same arithmetic as Sub but returns a boolean if any // negative coin amount was returned. -func (coins DecCoins) SafeMinus(coinsB DecCoins) (DecCoins, bool) { - diff := coins.safePlus(coinsB.negative()) +func (coins DecCoins) SafeSub(coinsB DecCoins) (DecCoins, bool) { + diff := coins.safeAdd(coinsB.negative()) return diff, diff.IsAnyNegative() } +// Intersect will return a new set of coins which contains the minimum DecCoin +// for common denoms found in both `coins` and `coinsB`. For denoms not common +// to both `coins` and `coinsB` the minimum is considered to be 0, thus they +// are not added to the final set.In other words, trim any denom amount from +// coin which exceeds that of coinB, such that (coin.Intersect(coinB)).IsLTE(coinB). +func (coins DecCoins) Intersect(coinsB DecCoins) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + minCoin := DecCoin{ + Denom: coin.Denom, + Amount: MinDec(coin.Amount, coinsB.AmountOf(coin.Denom)), + } + res[i] = minCoin + } + return removeZeroDecCoins(res) +} + // IsAnyNegative returns true if there is at least one coin whose amount // is negative; returns false otherwise. It returns false if the DecCoins set // is empty too. @@ -352,7 +369,7 @@ func (coins DecCoins) Empty() bool { // returns the amount of a denom from deccoins func (coins DecCoins) AmountOf(denom string) Dec { - validateDenom(denom) + mustValidateDenom(denom) switch len(coins) { case 0: @@ -415,7 +432,7 @@ func (coins DecCoins) IsValid() bool { return true case 1: - if strings.ToLower(coins[0].Denom) != coins[0].Denom { + if err := validateDenom(coins[0].Denom); err != nil { return false } return coins[0].IsPositive() @@ -515,8 +532,8 @@ func ParseDecCoin(coinStr string) (coin DecCoin, err error) { return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr)) } - if denomStr != strings.ToLower(denomStr) { - return DecCoin{}, fmt.Errorf("denom cannot contain upper case characters: %s", denomStr) + if err := validateDenom(denomStr); err != nil { + return DecCoin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %s", err) } return NewDecCoinFromDec(denomStr, amount), nil diff --git a/types/dec_coin_test.go b/types/dec_coin_test.go index ba7f49add..b7b6f4d0c 100644 --- a/types/dec_coin_test.go +++ b/types/dec_coin_test.go @@ -60,22 +60,22 @@ func TestDecCoinIsPositive(t *testing.T) { require.False(t, dc.IsPositive()) } -func TestPlusDecCoin(t *testing.T) { +func TestAddDecCoin(t *testing.T) { decCoinA1 := NewDecCoinFromDec(testDenom1, NewDecWithPrec(11, 1)) decCoinA2 := NewDecCoinFromDec(testDenom1, NewDecWithPrec(22, 1)) decCoinB1 := NewDecCoinFromDec(testDenom2, NewDecWithPrec(11, 1)) // regular add - res := decCoinA1.Plus(decCoinA1) + res := decCoinA1.Add(decCoinA1) require.Equal(t, decCoinA2, res, "sum of coins is incorrect") // bad denom add require.Panics(t, func() { - decCoinA1.Plus(decCoinB1) + decCoinA1.Add(decCoinB1) }, "expected panic on sum of different denoms") } -func TestPlusDecCoins(t *testing.T) { +func TestAddDecCoins(t *testing.T) { one := NewDec(1) zero := NewDec(0) two := NewDec(2) @@ -91,7 +91,7 @@ func TestPlusDecCoins(t *testing.T) { } for tcIndex, tc := range cases { - res := tc.inputOne.Plus(tc.inputTwo) + res := tc.inputOne.Add(tc.inputTwo) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } @@ -224,3 +224,35 @@ func TestDecCoinsString(t *testing.T) { require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input) } } + +func TestDecCoinsIntersect(t *testing.T) { + testCases := []struct { + input1 string + input2 string + expectedResult string + }{ + {"", "", ""}, + {"1.0stake", "", ""}, + {"1.0stake", "1.0stake", "1.0stake"}, + {"", "1.0stake", ""}, + {"1.0stake", "", ""}, + {"2.0stake,1.0trope", "1.9stake", "1.9stake"}, + {"2.0stake,1.0trope", "2.1stake", "2.0stake"}, + {"2.0stake,1.0trope", "0.9trope", "0.9trope"}, + {"2.0stake,1.0trope", "1.9stake,0.9trope", "1.9stake,0.9trope"}, + {"2.0stake,1.0trope", "1.9stake,0.9trope,20.0other", "1.9stake,0.9trope"}, + {"2.0stake,1.0trope", "1.0other", ""}, + } + + for i, tc := range testCases { + in1, err := ParseDecCoins(tc.input1) + require.NoError(t, err, "unexpected parse error in %v", i) + in2, err := ParseDecCoins(tc.input2) + require.NoError(t, err, "unexpected parse error in %v", i) + exr, err := ParseDecCoins(tc.expectedResult) + require.NoError(t, err, "unexpected parse error in %v", i) + + require.True(t, in1.Intersect(in2).IsEqual(exr), "in1.cap(in2) != exr in %v", i) + // require.Equal(t, tc.expectedResult, in1.Intersect(in2).String(), "in1.cap(in2) != exr in %v", i) + } +} diff --git a/types/decimal.go b/types/decimal.go index 5f59a0f54..bec27468c 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -291,6 +291,21 @@ func (d Dec) QuoTruncate(d2 Dec) Dec { return Dec{chopped} } +// quotient, round up +func (d Dec) QuoRoundUp(d2 Dec) Dec { + // multiply precision twice + mul := new(big.Int).Mul(d.Int, precisionReuse) + mul.Mul(mul, precisionReuse) + + quo := new(big.Int).Quo(mul, d2.Int) + chopped := chopPrecisionAndRoundUp(quo) + + if chopped.BitLen() > 255+DecimalPrecisionBits { + panic("Int overflow") + } + return Dec{chopped} +} + // quotient func (d Dec) QuoInt(i Int) Dec { mul := new(big.Int).Quo(d.Int, i.i) @@ -412,6 +427,29 @@ func chopPrecisionAndRound(d *big.Int) *big.Int { } } +func chopPrecisionAndRoundUp(d *big.Int) *big.Int { + + // remove the negative and add it back when returning + if d.Sign() == -1 { + // make d positive, compute chopped value, and then un-mutate d + d = d.Neg(d) + // truncate since d is negative... + d = chopPrecisionAndTruncate(d) + d = d.Neg(d) + return d + } + + // get the truncated quotient and remainder + quo, rem := d, big.NewInt(0) + quo, rem = quo.QuoRem(d, precisionReuse, rem) + + if rem.Sign() == 0 { // remainder is zero + return quo + } + + return quo.Add(quo, oneInt) +} + func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { tmp := new(big.Int).Set(d) return chopPrecisionAndRound(tmp) diff --git a/types/decimal_test.go b/types/decimal_test.go index 2193d841f..55aa31db9 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -156,44 +156,61 @@ func TestDecsEqual(t *testing.T) { func TestArithmetic(t *testing.T) { tests := []struct { - d1, d2 Dec - expMul, expDiv, expAdd, expSub Dec + d1, d2 Dec + expMul, expMulTruncate Dec + expQuo, expQuoRoundUp, expQuoTruncate Dec + expAdd, expSub Dec }{ - // d1 d2 MUL DIV ADD SUB - {NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)}, - {NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)}, - {NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)}, - {NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)}, - {NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)}, + // d1 d2 MUL MulTruncate QUO QUORoundUp QUOTrunctate ADD SUB + {NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)}, + {NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)}, + {NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)}, + {NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)}, + {NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)}, - {NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)}, - {NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)}, - {NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)}, - {NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)}, + {NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)}, + {NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)}, + {NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)}, + {NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)}, - {NewDec(3), NewDec(7), NewDec(21), NewDecWithPrec(428571428571428571, 18), NewDec(10), NewDec(-4)}, - {NewDec(2), NewDec(4), NewDec(8), NewDecWithPrec(5, 1), NewDec(6), NewDec(-2)}, - {NewDec(100), NewDec(100), NewDec(10000), NewDec(1), NewDec(200), NewDec(0)}, + {NewDec(3), NewDec(7), NewDec(21), NewDec(21), + NewDecWithPrec(428571428571428571, 18), NewDecWithPrec(428571428571428572, 18), NewDecWithPrec(428571428571428571, 18), + NewDec(10), NewDec(-4)}, + {NewDec(2), NewDec(4), NewDec(8), NewDec(8), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1), + NewDec(6), NewDec(-2)}, - {NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), - NewDec(1), NewDec(3), NewDec(0)}, - {NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), - MustNewDecFromStr("10.009009009009009009"), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, + {NewDec(100), NewDec(100), NewDec(10000), NewDec(10000), NewDec(1), NewDec(1), NewDec(1), NewDec(200), NewDec(0)}, + + {NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), NewDecWithPrec(225, 2), + NewDec(1), NewDec(1), NewDec(1), NewDec(3), NewDec(0)}, + {NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), NewDecWithPrec(1109889, 8), + MustNewDecFromStr("10.009009009009009009"), MustNewDecFromStr("10.009009009009009010"), MustNewDecFromStr("10.009009009009009009"), + NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, } for tcIndex, tc := range tests { resAdd := tc.d1.Add(tc.d2) resSub := tc.d1.Sub(tc.d2) resMul := tc.d1.Mul(tc.d2) + resMulTruncate := tc.d1.MulTruncate(tc.d2) require.True(t, tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) require.True(t, tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex) require.True(t, tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex) + require.True(t, tc.expMulTruncate.Equal(resMulTruncate), "exp %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex) if tc.d2.IsZero() { // panic for divide by zero require.Panics(t, func() { tc.d1.Quo(tc.d2) }) } else { - resDiv := tc.d1.Quo(tc.d2) - require.True(t, tc.expDiv.Equal(resDiv), "exp %v, res %v, tc %d", tc.expDiv.String(), resDiv.String(), tcIndex) + resQuo := tc.d1.Quo(tc.d2) + require.True(t, tc.expQuo.Equal(resQuo), "exp %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex) + + resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2) + require.True(t, tc.expQuoRoundUp.Equal(resQuoRoundUp), "exp %v, res %v, tc %d", + tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex) + + resQuoTruncate := tc.d1.QuoTruncate(tc.d2) + require.True(t, tc.expQuoTruncate.Equal(resQuoTruncate), "exp %v, res %v, tc %d", + tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex) } } } diff --git a/types/int.go b/types/int.go index e19809548..35b754daf 100644 --- a/types/int.go +++ b/types/int.go @@ -31,7 +31,7 @@ func sub(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Sub(i, i2) } func mul(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mul(i, i2) } -func div(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Div(i, i2) } +func div(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Quo(i, i2) } func mod(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mod(i, i2) } @@ -162,6 +162,11 @@ func ZeroInt() Int { return Int{big.NewInt(0)} } // OneInt returns Int value with one func OneInt() Int { return Int{big.NewInt(1)} } +// ToDec converts Int to Dec +func (i Int) ToDec() Dec { + return NewDecFromInt(i) +} + // Int64 converts Int to int64 // Panics if the value is out of range func (i Int) Int64() int64 { @@ -271,8 +276,8 @@ func (i Int) MulRaw(i2 int64) Int { return i.Mul(NewInt(i2)) } -// Div divides Int with Int -func (i Int) Div(i2 Int) (res Int) { +// Quo divides Int with Int +func (i Int) Quo(i2 Int) (res Int) { // Check division-by-zero if i2.i.Sign() == 0 { panic("Division by zero") @@ -280,9 +285,9 @@ func (i Int) Div(i2 Int) (res Int) { return Int{div(i.i, i2.i)} } -// DivRaw divides Int with int64 -func (i Int) DivRaw(i2 int64) Int { - return i.Div(NewInt(i2)) +// QuoRaw divides Int with int64 +func (i Int) QuoRaw(i2 int64) Int { + return i.Quo(NewInt(i2)) } // Mod returns remainder after dividing with Int @@ -318,11 +323,6 @@ func (i Int) String() string { return i.i.String() } -// Testing purpose random Int generator -func randomInt(i Int) Int { - return NewIntFromBigInt(random(i.BigInt())) -} - // MarshalAmino defines custom encoding scheme func (i Int) MarshalAmino() (string, error) { if i.i == nil { // Necessary since default Uint initialization has i.i as nil @@ -355,256 +355,6 @@ func (i *Int) UnmarshalJSON(bz []byte) error { 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 -} - -// BigInt converts Uint to big.Unt -func (i Uint) BigInt() *big.Int { - 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} -} - -// NewUintFromBigUint constructs Uint from big.Uint -func NewUintFromBigInt(i *big.Int) Uint { - res := Uint{i} - if UintOverflow(res) { - panic("Uint overflow") - } - return res -} - -// NewUintFromString constructs Uint from string -func NewUintFromString(s string) (res Uint, ok bool) { - i, ok := newIntegerFromString(s) - if !ok { - return - } - // Check overflow - if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 { - ok = false - return - } - return Uint{i}, true -} - -// NewUintWithDecimal constructs Uint with decimal -// Result value is n*10^dec -func NewUintWithDecimal(n uint64, dec int) Uint { - if dec < 0 { - panic("NewUintWithDecimal() decimal is negative") - } - exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(dec)), nil) - i := new(big.Int) - i.Mul(new(big.Int).SetUint64(n), exp) - - res := Uint{i} - if UintOverflow(res) { - panic("NewUintWithDecimal() out of bound") - } - - return res -} - -// ZeroUint returns Uint value with zero -func ZeroUint() Uint { return Uint{big.NewInt(0)} } - -// OneUint returns Uint value with one -func OneUint() Uint { return Uint{big.NewInt(1)} } - -// Uint64 converts Uint to uint64 -// Panics if the value is out of range -func (i Uint) Uint64() uint64 { - if !i.i.IsUint64() { - panic("Uint64() out of bound") - } - return i.i.Uint64() -} - -// IsUint64 returns true if Uint64() not panics -func (i Uint) IsUint64() bool { - return i.i.IsUint64() -} - -// IsZero returns true if Uint is zero -func (i Uint) IsZero() bool { - return i.i.Sign() == 0 -} - -// Sign returns sign of Uint -func (i Uint) Sign() int { - return i.i.Sign() -} - -// Equal compares two Uints -func (i Uint) Equal(i2 Uint) bool { - 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) -} - -// LT returns true if first Uint is lesser than second -func (i Uint) LT(i2 Uint) bool { - 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)} - if UintOverflow(res) { - panic("Uint overflow") - } - return -} - -// AddRaw adds uint64 to Uint -func (i Uint) AddRaw(i2 uint64) Uint { - return i.Add(NewUint(i2)) -} - -// Sub subtracts Uint from another -func (i Uint) Sub(i2 Uint) (res Uint) { - res = Uint{sub(i.i, i2.i)} - if UintOverflow(res) { - panic("Uint overflow") - } - return -} - -// SafeSub attempts to subtract one Uint from another. A boolean is also returned -// indicating if the result contains integer overflow. -func (i Uint) SafeSub(i2 Uint) (Uint, bool) { - res := Uint{sub(i.i, i2.i)} - if UintOverflow(res) { - return res, true - } - - return res, false -} - -// SubRaw subtracts uint64 from Uint -func (i Uint) SubRaw(i2 uint64) Uint { - return i.Sub(NewUint(i2)) -} - -// Mul multiples two Uints -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)} - if UintOverflow(res) { - panic("Uint overflow") - } - - return -} - -// MulRaw multipies Uint and uint64 -func (i Uint) MulRaw(i2 uint64) Uint { - return i.Mul(NewUint(i2)) -} - -// Div divides Uint with Uint -func (i Uint) Div(i2 Uint) (res Uint) { - // Check division-by-zero - if i2.Sign() == 0 { - panic("division-by-zero") - } - return Uint{div(i.i, i2.i)} -} - -// Div divides Uint with uint64 -func (i Uint) DivRaw(i2 uint64) Uint { - return i.Div(NewUint(i2)) -} - -// Mod returns remainder after dividing with Uint -func (i Uint) Mod(i2 Uint) Uint { - if i2.Sign() == 0 { - panic("division-by-zero") - } - return Uint{mod(i.i, i2.i)} -} - -// ModRaw returns remainder after dividing with uint64 -func (i Uint) ModRaw(i2 uint64) Uint { - return i.Mod(NewUint(i2)) -} - -// Return the minimum of the Uints -func MinUint(i1, i2 Uint) Uint { - return Uint{min(i1.BigInt(), i2.BigInt())} -} - -// MaxUint returns the maximum between two unsigned integers. -func MaxUint(i, i2 Uint) Uint { - return Uint{max(i.BigInt(), i2.BigInt())} -} - -// Human readable string -func (i Uint) String() string { - return i.i.String() -} - -// Testing purpose random Uint generator -func randomUint(i Uint) Uint { - return NewUintFromBigInt(random(i.BigInt())) -} - -// MarshalAmino defines custom encoding scheme -func (i Uint) MarshalAmino() (string, error) { - 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 { - 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) { - 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 { - if i.i == nil { // Necessary since default Uint initialization has i.i as nil - i.i = new(big.Int) - } - return unmarshalJSON(i.i, bz) -} - -//__________________________________________________________________________ - -// UintOverflow returns true if a given unsigned integer overflows and false -// otherwise. -func UintOverflow(x Uint) bool { - return x.i.Sign() == -1 || x.i.Sign() == 1 && x.i.BitLen() > 256 -} - // intended to be used with require/assert: require.True(IntEq(...)) func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) { return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() diff --git a/types/int_test.go b/types/int_test.go index 20a2f3f61..5251d4665 100644 --- a/types/int_test.go +++ b/types/int_test.go @@ -1,7 +1,6 @@ package types import ( - "math" "math/big" "math/rand" "strconv" @@ -70,46 +69,7 @@ func TestIntPanic(t *testing.T) { require.Panics(t, func() { intmin.Sub(OneInt()) }) // Division-by-zero check - require.Panics(t, func() { i1.Div(NewInt(0)) }) -} - -func TestUintPanic(t *testing.T) { - // Max Uint = 1.15e+77 - // Min Uint = 0 - require.NotPanics(t, func() { NewUintWithDecimal(5, 76) }) - i1 := NewUintWithDecimal(5, 76) - require.NotPanics(t, func() { NewUintWithDecimal(10, 76) }) - i2 := NewUintWithDecimal(10, 76) - require.NotPanics(t, func() { NewUintWithDecimal(11, 76) }) - i3 := NewUintWithDecimal(11, 76) - - require.Panics(t, func() { NewUintWithDecimal(12, 76) }) - require.Panics(t, func() { NewUintWithDecimal(1, 80) }) - - // Overflow check - require.NotPanics(t, func() { i1.Add(i1) }) - require.Panics(t, func() { i2.Add(i2) }) - require.Panics(t, func() { i3.Add(i3) }) - - require.Panics(t, func() { i1.Mul(i1) }) - require.Panics(t, func() { i2.Mul(i2) }) - require.Panics(t, func() { i3.Mul(i3) }) - - // Underflow check - require.NotPanics(t, func() { i2.Sub(i1) }) - require.NotPanics(t, func() { i2.Sub(i2) }) - require.Panics(t, func() { i2.Sub(i3) }) - - // Bound check - uintmax := NewUintFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))) - uintmin := NewUint(0) - require.NotPanics(t, func() { uintmax.Add(ZeroUint()) }) - require.NotPanics(t, func() { uintmin.Sub(ZeroUint()) }) - require.Panics(t, func() { uintmax.Add(OneUint()) }) - require.Panics(t, func() { uintmin.Sub(OneUint()) }) - - // Division-by-zero check - require.Panics(t, func() { i1.Div(uintmin) }) + require.Panics(t, func() { i1.Quo(NewInt(0)) }) } // Tests below uses randomness @@ -166,11 +126,11 @@ func TestArithInt(t *testing.T) { {i1.Add(i2), n1 + n2}, {i1.Sub(i2), n1 - n2}, {i1.Mul(i2), n1 * n2}, - {i1.Div(i2), n1 / n2}, + {i1.Quo(i2), n1 / n2}, {i1.AddRaw(n2), n1 + n2}, {i1.SubRaw(n2), n1 - n2}, {i1.MulRaw(n2), n1 * n2}, - {i1.DivRaw(n2), n1 / n2}, + {i1.QuoRaw(n2), n1 / n2}, {MinInt(i1, i2), minint(n1, n2)}, {MaxInt(i1, i2), maxint(n1, n2)}, {i1.Neg(), -n1}, @@ -205,28 +165,6 @@ func TestCompInt(t *testing.T) { } } -func TestIdentUint(t *testing.T) { - for d := 0; d < 1000; d++ { - n := rand.Uint64() - i := NewUint(n) - - ifromstr, ok := NewUintFromString(strconv.FormatUint(n, 10)) - require.True(t, ok) - - cases := []uint64{ - i.Uint64(), - i.BigInt().Uint64(), - ifromstr.Uint64(), - NewUintFromBigInt(new(big.Int).SetUint64(n)).Uint64(), - NewUintWithDecimal(n, 0).Uint64(), - } - - for tcnum, tc := range cases { - require.Equal(t, n, tc, "Uint is modified during conversion. tc #%d", tcnum) - } - } -} - func minuint(i1, i2 uint64) uint64 { if i1 < i2 { return i1 @@ -241,71 +179,6 @@ func maxuint(i1, i2 uint64) uint64 { return i2 } -func TestArithUint(t *testing.T) { - for d := 0; d < 1000; d++ { - n1 := uint64(rand.Uint32()) - i1 := NewUint(n1) - n2 := uint64(rand.Uint32()) - i2 := NewUint(n2) - - cases := []struct { - ires Uint - nres uint64 - }{ - {i1.Add(i2), n1 + n2}, - {i1.Mul(i2), n1 * n2}, - {i1.Div(i2), n1 / n2}, - {i1.AddRaw(n2), n1 + n2}, - {i1.MulRaw(n2), n1 * n2}, - {i1.DivRaw(n2), n1 / n2}, - {MinUint(i1, i2), minuint(n1, n2)}, - {MaxUint(i1, i2), maxuint(n1, n2)}, - } - - for tcnum, tc := range cases { - require.Equal(t, tc.nres, tc.ires.Uint64(), "Uint arithmetic operation does not match with uint64 operation. tc #%d", tcnum) - } - - if n2 > n1 { - continue - } - - subs := []struct { - ires Uint - nres uint64 - }{ - {i1.Sub(i2), n1 - n2}, - {i1.SubRaw(n2), n1 - n2}, - } - - for tcnum, tc := range subs { - require.Equal(t, tc.nres, tc.ires.Uint64(), "Uint subtraction does not match with uint64 operation. tc #%d", tcnum) - } - } -} - -func TestCompUint(t *testing.T) { - for d := 0; d < 1000; d++ { - n1 := rand.Uint64() - i1 := NewUint(n1) - n2 := rand.Uint64() - i2 := NewUint(n2) - - cases := []struct { - ires bool - nres bool - }{ - {i1.Equal(i2), n1 == n2}, - {i1.GT(i2), n1 > n2}, - {i1.LT(i2), n1 < n2}, - } - - for tcnum, tc := range cases { - require.Equal(t, tc.nres, tc.ires, "Uint comparison operation does not match with uint64 operation. tc #%d", tcnum) - } - } -} - func randint() Int { return NewInt(rand.Int63()) } @@ -315,11 +188,11 @@ func TestImmutabilityAllInt(t *testing.T) { func(i *Int) { _ = i.Add(randint()) }, func(i *Int) { _ = i.Sub(randint()) }, func(i *Int) { _ = i.Mul(randint()) }, - func(i *Int) { _ = i.Div(randint()) }, + func(i *Int) { _ = i.Quo(randint()) }, func(i *Int) { _ = i.AddRaw(rand.Int63()) }, func(i *Int) { _ = i.SubRaw(rand.Int63()) }, func(i *Int) { _ = i.MulRaw(rand.Int63()) }, - func(i *Int) { _ = i.DivRaw(rand.Int63()) }, + func(i *Int) { _ = i.QuoRaw(rand.Int63()) }, func(i *Int) { _ = i.Neg() }, func(i *Int) { _ = i.IsZero() }, func(i *Int) { _ = i.Sign() }, @@ -367,11 +240,11 @@ func TestImmutabilityArithInt(t *testing.T) { intarith(Int.Add, (*big.Int).Add), intarith(Int.Sub, (*big.Int).Sub), intarith(Int.Mul, (*big.Int).Mul), - intarith(Int.Div, (*big.Int).Div), + intarith(Int.Quo, (*big.Int).Quo), intarithraw(Int.AddRaw, (*big.Int).Add), intarithraw(Int.SubRaw, (*big.Int).Sub), intarithraw(Int.MulRaw, (*big.Int).Mul), - intarithraw(Int.DivRaw, (*big.Int).Div), + intarithraw(Int.QuoRaw, (*big.Int).Quo), } for i := 0; i < 100; i++ { @@ -394,108 +267,6 @@ func TestImmutabilityArithInt(t *testing.T) { } } } -func TestImmutabilityAllUint(t *testing.T) { - ops := []func(*Uint){ - func(i *Uint) { _ = i.Add(NewUint(rand.Uint64())) }, - func(i *Uint) { _ = i.Sub(NewUint(rand.Uint64() % i.Uint64())) }, - func(i *Uint) { _ = i.Mul(randuint()) }, - func(i *Uint) { _ = i.Div(randuint()) }, - func(i *Uint) { _ = i.AddRaw(rand.Uint64()) }, - func(i *Uint) { _ = i.SubRaw(rand.Uint64() % i.Uint64()) }, - func(i *Uint) { _ = i.MulRaw(rand.Uint64()) }, - func(i *Uint) { _ = i.DivRaw(rand.Uint64()) }, - func(i *Uint) { _ = i.IsZero() }, - func(i *Uint) { _ = i.Sign() }, - func(i *Uint) { _ = i.Equal(randuint()) }, - func(i *Uint) { _ = i.GT(randuint()) }, - func(i *Uint) { _ = i.LT(randuint()) }, - func(i *Uint) { _ = i.String() }, - } - - for i := 0; i < 1000; i++ { - n := rand.Uint64() - ni := NewUint(n) - - for opnum, op := range ops { - op(&ni) - - require.Equal(t, n, ni.Uint64(), "Uint is modified by operation. #%d", opnum) - require.Equal(t, NewUint(n), ni, "Uint is modified by operation. #%d", opnum) - } - } -} - -type uintop func(Uint, *big.Int) (Uint, *big.Int) - -func uintarith(uifn func(Uint, Uint) Uint, bifn func(*big.Int, *big.Int, *big.Int) *big.Int, sub bool) uintop { - return func(ui Uint, bi *big.Int) (Uint, *big.Int) { - r := rand.Uint64() - if sub && ui.IsUint64() { - if ui.IsZero() { - return ui, bi - } - r = r % ui.Uint64() - } - ur := NewUint(r) - br := new(big.Int).SetUint64(r) - return uifn(ui, ur), bifn(new(big.Int), bi, br) - } -} - -func uintarithraw(uifn func(Uint, uint64) Uint, bifn func(*big.Int, *big.Int, *big.Int) *big.Int, sub bool) uintop { - return func(ui Uint, bi *big.Int) (Uint, *big.Int) { - r := rand.Uint64() - if sub && ui.IsUint64() { - if ui.IsZero() { - return ui, bi - } - r = r % ui.Uint64() - } - br := new(big.Int).SetUint64(r) - mui := ui.ModRaw(math.MaxUint64) - mbi := new(big.Int).Mod(bi, new(big.Int).SetUint64(math.MaxUint64)) - return uifn(mui, r), bifn(new(big.Int), mbi, br) - } -} - -func TestImmutabilityArithUint(t *testing.T) { - size := 500 - - ops := []uintop{ - uintarith(Uint.Add, (*big.Int).Add, false), - uintarith(Uint.Sub, (*big.Int).Sub, true), - uintarith(Uint.Mul, (*big.Int).Mul, false), - uintarith(Uint.Div, (*big.Int).Div, false), - uintarithraw(Uint.AddRaw, (*big.Int).Add, false), - uintarithraw(Uint.SubRaw, (*big.Int).Sub, true), - uintarithraw(Uint.MulRaw, (*big.Int).Mul, false), - uintarithraw(Uint.DivRaw, (*big.Int).Div, false), - } - - for i := 0; i < 100; i++ { - uis := make([]Uint, size) - bis := make([]*big.Int, size) - - n := rand.Uint64() - ui := NewUint(n) - bi := new(big.Int).SetUint64(n) - - for j := 0; j < size; j++ { - op := ops[rand.Intn(len(ops))] - uis[j], bis[j] = op(ui, bi) - } - - for j := 0; j < size; j++ { - require.Equal(t, 0, bis[j].Cmp(uis[j].BigInt()), "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) - require.Equal(t, NewUintFromBigInt(bis[j]), uis[j], "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) - require.True(t, uis[j].i != bis[j], "Pointer addresses are equal. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) - } - } -} - -func randuint() Uint { - return NewUint(rand.Uint64()) -} func TestEncodingRandom(t *testing.T) { for i := 0; i < 1000; i++ { @@ -607,31 +378,6 @@ func TestEncodingTableUint(t *testing.T) { } } -func TestSafeSub(t *testing.T) { - testCases := []struct { - x, y Uint - expected uint64 - overflow bool - }{ - {NewUint(0), NewUint(0), 0, false}, - {NewUint(10), NewUint(5), 5, false}, - {NewUint(5), NewUint(10), 5, true}, - {NewUint(math.MaxUint64), NewUint(0), math.MaxUint64, false}, - } - - for i, tc := range testCases { - res, overflow := tc.x.SafeSub(tc.y) - require.Equal( - t, tc.overflow, overflow, - "invalid overflow result; x: %s, y: %s, tc: #%d", tc.x, tc.y, i, - ) - require.Equal( - t, tc.expected, res.BigInt().Uint64(), - "invalid subtraction result; x: %s, y: %s, tc: #%d", tc.x, tc.y, i, - ) - } -} - func TestSerializationOverflow(t *testing.T) { bx, _ := new(big.Int).SetString("91888242871839275229946405745257275988696311157297823662689937894645226298583", 10) x := Int{bx} diff --git a/types/rest/rest.go b/types/rest/rest.go index f40fff747..d24e8e3d7 100644 --- a/types/rest/rest.go +++ b/types/rest/rest.go @@ -22,7 +22,6 @@ type GasEstimateResponse struct { // that all share common "base" fields. type BaseReq struct { From string `json:"from"` - Password string `json:"password"` Memo string `json:"memo"` ChainID string `json:"chain_id"` AccountNumber uint64 `json:"account_number"` @@ -31,19 +30,17 @@ type BaseReq struct { GasPrices sdk.DecCoins `json:"gas_prices"` Gas string `json:"gas"` GasAdjustment string `json:"gas_adjustment"` - GenerateOnly bool `json:"generate_only"` Simulate bool `json:"simulate"` } // NewBaseReq creates a new basic request instance and sanitizes its values func NewBaseReq( - from, password, memo, chainID string, gas, gasAdjustment string, - accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool, + from, memo, chainID string, gas, gasAdjustment string, accNumber, seq uint64, + fees sdk.Coins, gasPrices sdk.DecCoins, simulate bool, ) BaseReq { return BaseReq{ From: strings.TrimSpace(from), - Password: password, Memo: strings.TrimSpace(memo), ChainID: strings.TrimSpace(chainID), Fees: fees, @@ -52,7 +49,6 @@ func NewBaseReq( GasAdjustment: strings.TrimSpace(gasAdjustment), AccountNumber: accNumber, Sequence: seq, - GenerateOnly: genOnly, Simulate: simulate, } } @@ -60,8 +56,8 @@ func NewBaseReq( // Sanitize performs basic sanitization on a BaseReq object. func (br BaseReq) Sanitize() BaseReq { return NewBaseReq( - br.From, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment, - br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate, + br.From, br.Memo, br.ChainID, br.Gas, br.GasAdjustment, + br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.Simulate, ) } @@ -69,12 +65,8 @@ func (br BaseReq) Sanitize() BaseReq { // logic is needed, the implementing request handler should perform those // checks manually. func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { - if !br.GenerateOnly && !br.Simulate { + if !br.Simulate { switch { - case len(br.Password) == 0: - WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") - return false - case len(br.ChainID) == 0: WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified") return false @@ -91,8 +83,8 @@ func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { } } - if len(br.From) == 0 { - WriteErrorResponse(w, http.StatusUnauthorized, "name or address required but not specified") + if _, err := sdk.AccAddressFromBech32(br.From); err != nil || len(br.From) == 0 { + WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("invalid from address: %s", br.From)) return false } @@ -119,13 +111,13 @@ func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req i // ErrorResponse defines the attributes of a JSON error response. type ErrorResponse struct { - Code int `json:"code,omitempty"` - Message string `json:"message"` + Code int `json:"code,omitempty"` + Error string `json:"error"` } // NewErrorResponse creates a new ErrorResponse instance. -func NewErrorResponse(code int, msg string) ErrorResponse { - return ErrorResponse{Code: code, Message: msg} +func NewErrorResponse(code int, err string) ErrorResponse { + return ErrorResponse{Code: code, Error: err} } // WriteErrorResponse prepares and writes a HTTP error @@ -200,6 +192,9 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter var output []byte switch response.(type) { + case []byte: + output = response.([]byte) + default: var err error if indent { @@ -211,8 +206,6 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - case []byte: - output = response.([]byte) } w.Header().Set("Content-Type", "application/json") diff --git a/types/rest/rest_test.go b/types/rest/rest_test.go index 56c2eac82..876cffe68 100644 --- a/types/rest/rest_test.go +++ b/types/rest/rest_test.go @@ -14,35 +14,27 @@ import ( type mockResponseWriter struct{} -func TestBaseReq_ValidateBasic(t *testing.T) { +func TestBaseReqValidateBasic(t *testing.T) { + fromAddr := "cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0" tenstakes, err := types.ParseCoins("10stake") require.NoError(t, err) onestake, err := types.ParseDecCoins("1.0stake") require.NoError(t, err) req1 := NewBaseReq( - "nonempty", "nonempty", "", "nonempty", "", "", - 0, 0, tenstakes, nil, false, false, + fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, nil, false, ) req2 := NewBaseReq( - "", "nonempty", "", "nonempty", "", "", - 0, 0, tenstakes, nil, false, false, + "", "", "nonempty", "", "", 0, 0, tenstakes, nil, false, ) req3 := NewBaseReq( - "nonempty", "", "", "nonempty", "", "", - 0, 0, tenstakes, nil, false, false, + fromAddr, "", "", "", "", 0, 0, tenstakes, nil, false, ) req4 := NewBaseReq( - "nonempty", "nonempty", "", "", "", "", - 0, 0, tenstakes, nil, false, false, + fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, onestake, false, ) req5 := NewBaseReq( - "nonempty", "nonempty", "", "nonempty", "", "", - 0, 0, tenstakes, onestake, false, false, - ) - req6 := NewBaseReq( - "nonempty", "nonempty", "", "nonempty", "", "", - 0, 0, types.Coins{}, types.DecCoins{}, false, false, + fromAddr, "", "nonempty", "", "", 0, 0, types.Coins{}, types.DecCoins{}, false, ) tests := []struct { @@ -52,11 +44,10 @@ func TestBaseReq_ValidateBasic(t *testing.T) { want bool }{ {"ok", req1, httptest.NewRecorder(), true}, - {"neither fees nor gasprices provided", req6, httptest.NewRecorder(), true}, + {"neither fees nor gasprices provided", req5, httptest.NewRecorder(), true}, {"empty from", req2, httptest.NewRecorder(), false}, - {"empty password", req3, httptest.NewRecorder(), false}, - {"empty chain-id", req4, httptest.NewRecorder(), false}, - {"fees and gasprices provided", req5, httptest.NewRecorder(), false}, + {"empty chain-id", req3, httptest.NewRecorder(), false}, + {"fees and gasprices provided", req4, httptest.NewRecorder(), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/types/result.go b/types/result.go index e4be53ec5..8ff873bfc 100644 --- a/types/result.go +++ b/types/result.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "strings" @@ -9,7 +10,6 @@ import ( // Result is the union of ResponseFormat and ResponseCheckTx. type Result struct { - // Code is the response code, is stored back on the chain. Code CodeType @@ -21,7 +21,7 @@ type Result struct { // results from multiple msgs executions Data []byte - // Log is just debug information. NOTE: nondeterministic. + // Log contains the txs log information. NOTE: nondeterministic. Log string // GasWanted is the maximum units of work we allow this tx to perform. @@ -39,19 +39,42 @@ func (res Result) IsOK() bool { return res.Code.IsOK() } -// Is a version of TxResponse where the tags are StringTags rather than []byte tags +// ABCIMessageLogs represents a slice of ABCIMessageLog. +type ABCIMessageLogs []ABCIMessageLog + +// ABCIMessageLog defines a structure containing an indexed tx ABCI message log. +type ABCIMessageLog struct { + MsgIndex int `json:"msg_index"` + Success bool `json:"success"` + Log string `json:"log"` +} + +// String implements the fmt.Stringer interface for the ABCIMessageLogs type. +func (logs ABCIMessageLogs) String() (str string) { + if logs != nil { + raw, err := json.Marshal(logs) + if err == nil { + str = string(raw) + } + } + + return str +} + +// TxResponse defines a structure containing relevant tx data and metadata. The +// tags are stringified and the log is JSON decoded. type TxResponse struct { - Height int64 `json:"height"` - TxHash string `json:"txhash"` - Code uint32 `json:"code,omitempty"` - Data []byte `json:"data,omitempty"` - Log string `json:"log,omitempty"` - Info string `json:"info,omitempty"` - GasWanted int64 `json:"gas_wanted,omitempty"` - GasUsed int64 `json:"gas_used,omitempty"` - Tags StringTags `json:"tags,omitempty"` - Codespace string `json:"codespace,omitempty"` - Tx Tx `json:"tx,omitempty"` + Height int64 `json:"height"` + TxHash string `json:"txhash"` + Code uint32 `json:"code,omitempty"` + Data []byte `json:"data,omitempty"` + Logs ABCIMessageLogs `json:"logs,omitempty"` + Info string `json:"info,omitempty"` + GasWanted int64 `json:"gas_wanted,omitempty"` + GasUsed int64 `json:"gas_used,omitempty"` + Tags StringTags `json:"tags,omitempty"` + Codespace string `json:"codespace,omitempty"` + Tx Tx `json:"tx,omitempty"` } // NewResponseResultTx returns a TxResponse given a ResultTx from tendermint @@ -60,12 +83,14 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse { return TxResponse{} } + parsedLogs, _ := ParseABCILogs(res.TxResult.Log) + return TxResponse{ TxHash: res.Hash.String(), Height: res.Height, Code: res.TxResult.Code, Data: res.TxResult.Data, - Log: res.TxResult.Log, + Logs: parsedLogs, Info: res.TxResult.Info, GasWanted: res.TxResult.GasWanted, GasUsed: res.TxResult.GasUsed, @@ -85,12 +110,14 @@ func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxR txHash = res.Hash.String() } + parsedLogs, _ := ParseABCILogs(res.DeliverTx.Log) + return TxResponse{ Height: res.Height, TxHash: txHash, Code: res.DeliverTx.Code, Data: res.DeliverTx.Data, - Log: res.DeliverTx.Log, + Logs: parsedLogs, Info: res.DeliverTx.Info, GasWanted: res.DeliverTx.GasWanted, GasUsed: res.DeliverTx.GasUsed, @@ -106,10 +133,12 @@ func NewResponseFormatBroadcastTx(res *ctypes.ResultBroadcastTx) TxResponse { return TxResponse{} } + parsedLogs, _ := ParseABCILogs(res.Log) + return TxResponse{ Code: res.Code, Data: res.Data.Bytes(), - Log: res.Log, + Logs: parsedLogs, TxHash: res.Hash.String(), } } @@ -134,8 +163,8 @@ func (r TxResponse) String() string { sb.WriteString(fmt.Sprintf(" Data: %s\n", string(r.Data))) } - if r.Log != "" { - sb.WriteString(fmt.Sprintf(" Log: %s\n", r.Log)) + if r.Logs != nil { + sb.WriteString(fmt.Sprintf(" Logs: %s\n", r.Logs)) } if r.Info != "" { @@ -163,5 +192,12 @@ func (r TxResponse) String() string { // Empty returns true if the response is empty func (r TxResponse) Empty() bool { - return r.TxHash == "" && r.Log == "" + return r.TxHash == "" && r.Logs == nil +} + +// ParseABCILogs attempts to parse a stringified ABCI tx log into a slice of +// ABCIMessageLog types. It returns an error upon JSON decoding failure. +func ParseABCILogs(logs string) (res ABCIMessageLogs, err error) { + err = json.Unmarshal([]byte(logs), &res) + return res, err } diff --git a/types/result_test.go b/types/result_test.go index 9765933d9..0b126bbd7 100644 --- a/types/result_test.go +++ b/types/result_test.go @@ -16,3 +16,14 @@ func TestResult(t *testing.T) { res.Code = CodeType(1) require.False(t, res.IsOK()) } + +func TestParseABCILog(t *testing.T) { + logs := `[{"log":"","msg_index":1,"success":true}]` + + res, err := ParseABCILogs(logs) + require.NoError(t, err) + require.Len(t, res, 1) + require.Equal(t, res[0].Log, "") + require.Equal(t, res[0].MsgIndex, 1) + require.True(t, res[0].Success) +} diff --git a/types/staking.go b/types/staking.go index d9143fa4e..428979a18 100644 --- a/types/staking.go +++ b/types/staking.go @@ -46,7 +46,7 @@ var PowerReduction = NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewIn // TokensToTendermintPower - convert input tokens to potential tendermint power func TokensToTendermintPower(tokens Int) int64 { - return (tokens.Div(PowerReduction)).Int64() + return (tokens.Quo(PowerReduction)).Int64() } // TokensFromTendermintPower - convert input power to tokens @@ -73,7 +73,8 @@ type Validator interface { GetCommission() Dec // validator commission rate GetMinSelfDelegation() Int // validator minimum self delegation GetDelegatorShares() Dec // total outstanding delegator shares - GetDelegatorShareExRate() Dec // tokens per delegator share exchange rate + ShareTokens(Dec) Dec // token worth of provided delegator shares + ShareTokensTruncated(Dec) Dec // token worth of provided delegator shares, truncated } // validator which fulfills abci validator interface for use in Tendermint diff --git a/types/uint.go b/types/uint.go new file mode 100644 index 000000000..d6e129e69 --- /dev/null +++ b/types/uint.go @@ -0,0 +1,172 @@ +package types + +import ( + "errors" + "fmt" + "math/big" +) + +// Uint 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 +} + +// NewUintFromBigUint constructs Uint from big.Uint +func NewUintFromBigInt(i *big.Int) Uint { + u, err := checkNewUint(i) + if err != nil { + panic(fmt.Errorf("overflow: %s", err)) + } + return u +} + +// NewUint constructs Uint from int64 +func NewUint(n uint64) Uint { + i := new(big.Int) + i.SetUint64(n) + return NewUintFromBigInt(i) +} + +// NewUintFromString constructs Uint from string +func NewUintFromString(s string) Uint { + u, err := ParseUint(s) + if err != nil { + panic(err) + } + return u +} + +// ZeroUint returns unsigned zero. +func ZeroUint() Uint { return Uint{big.NewInt(0)} } + +// OneUint returns Uint value with one. +func OneUint() Uint { return Uint{big.NewInt(1)} } + +// Uint64 converts Uint to uint64 +// Panics if the value is out of range +func (u Uint) Uint64() uint64 { + if !u.i.IsUint64() { + panic("Uint64() out of bound") + } + return u.i.Uint64() +} + +// IsZero returns 1 if the uint equals to 0. +func (u Uint) IsZero() bool { return u.Equal(ZeroUint()) } + +// Equal compares two Uints +func (u Uint) Equal(u2 Uint) bool { return equal(u.i, u2.i) } + +// GT returns true if first Uint is greater than second +func (u Uint) GT(u2 Uint) bool { return gt(u.i, u2.i) } + +// GTE returns true if first Uint is greater than second +func (u Uint) GTE(u2 Uint) bool { return u.GT(u2) || u.Equal(u2) } + +// LT returns true if first Uint is lesser than second +func (u Uint) LT(u2 Uint) bool { return lt(u.i, u2.i) } + +// LTE returns true if first Uint is lesser than or equal to the second +func (u Uint) LTE(u2 Uint) bool { return !u.GTE(u2) } + +// Add adds Uint from another +func (u Uint) Add(u2 Uint) Uint { return NewUintFromBigInt(new(big.Int).Add(u.i, u2.i)) } + +// Add convert uint64 and add it to Uint +func (u Uint) AddUint64(u2 uint64) Uint { return u.Add(NewUint(u2)) } + +// Sub adds Uint from another +func (u Uint) Sub(u2 Uint) Uint { return NewUintFromBigInt(new(big.Int).Sub(u.i, u2.i)) } + +// SubUint64 adds Uint from another +func (u Uint) SubUint64(u2 uint64) Uint { return u.Sub(NewUint(u2)) } + +// Mul multiplies two Uints +func (u Uint) Mul(u2 Uint) (res Uint) { + return NewUintFromBigInt(new(big.Int).Mul(u.i, u2.i)) +} + +// Mul multiplies two Uints +func (u Uint) MulUint64(u2 uint64) (res Uint) { return u.Mul(NewUint(u2)) } + +// Quo divides Uint with Uint +func (u Uint) Quo(u2 Uint) (res Uint) { return NewUintFromBigInt(div(u.i, u2.i)) } + +// Quo divides Uint with uint64 +func (u Uint) QuoUint64(u2 uint64) Uint { return u.Quo(NewUint(u2)) } + +// Return the minimum of the Uints +func MinUint(u1, u2 Uint) Uint { return NewUintFromBigInt(min(u1.i, u2.i)) } + +// Return the maximum of the Uints +func MaxUint(u1, u2 Uint) Uint { return NewUintFromBigInt(max(u1.i, u2.i)) } + +// Human readable string +func (u Uint) String() string { return u.i.String() } + +// Testing purpose random Uint generator +func randomUint(u Uint) Uint { return NewUintFromBigInt(random(u.i)) } + +// MarshalAmino defines custom encoding scheme +func (u Uint) MarshalAmino() (string, error) { + if u.i == nil { // Necessary since default Uint initialization has i.i as nil + u.i = new(big.Int) + } + return marshalAmino(u.i) +} + +// UnmarshalAmino defines custom decoding scheme +func (u *Uint) UnmarshalAmino(text string) error { + if u.i == nil { // Necessary since default Uint initialization has i.i as nil + u.i = new(big.Int) + } + return unmarshalAmino(u.i, text) +} + +// MarshalJSON defines custom encoding scheme +func (u Uint) MarshalJSON() ([]byte, error) { + if u.i == nil { // Necessary since default Uint initialization has i.i as nil + u.i = new(big.Int) + } + return marshalJSON(u.i) +} + +// UnmarshalJSON defines custom decoding scheme +func (u *Uint) UnmarshalJSON(bz []byte) error { + if u.i == nil { // Necessary since default Uint initialization has i.i as nil + u.i = new(big.Int) + } + return unmarshalJSON(u.i, bz) +} + +//__________________________________________________________________________ + +// UintOverflow returns true if a given unsigned integer overflows and false +// otherwise. +func UintOverflow(i *big.Int) error { + if i.Sign() < 0 { + return errors.New("non-positive integer") + } + if i.BitLen() > 256 { + return fmt.Errorf("bit length %d greater than 256", i.BitLen()) + } + return nil +} + +// ParseUint reads a string-encoded Uint value and return a Uint. +func ParseUint(s string) (Uint, error) { + i, ok := new(big.Int).SetString(s, 0) + if !ok { + return Uint{}, fmt.Errorf("cannot convert %q to big.Int", s) + } + return checkNewUint(i) +} + +func checkNewUint(i *big.Int) (Uint, error) { + if err := UintOverflow(i); err != nil { + return Uint{}, err + } + return Uint{i}, nil +} diff --git a/types/uint_test.go b/types/uint_test.go new file mode 100644 index 000000000..a3115512b --- /dev/null +++ b/types/uint_test.go @@ -0,0 +1,253 @@ +package types + +import ( + "math" + "math/big" + "math/rand" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUintPanics(t *testing.T) { + // Max Uint = 1.15e+77 + // Min Uint = 0 + u1 := NewUint(0) + u2 := OneUint() + + require.Equal(t, uint64(0), u1.Uint64()) + require.Equal(t, uint64(1), u2.Uint64()) + + require.Panics(t, func() { NewUintFromBigInt(big.NewInt(-5)) }) + require.Panics(t, func() { NewUintFromString("-1") }) + require.NotPanics(t, func() { + require.True(t, NewUintFromString("0").Equal(ZeroUint())) + require.True(t, NewUintFromString("5").Equal(NewUint(5))) + }) + + // Overflow check + require.True(t, u1.Add(u1).Equal(ZeroUint())) + require.True(t, u1.Add(OneUint()).Equal(OneUint())) + require.Equal(t, uint64(0), u1.Uint64()) + require.Equal(t, uint64(1), OneUint().Uint64()) + require.Panics(t, func() { u1.SubUint64(2) }) + require.True(t, u1.SubUint64(0).Equal(ZeroUint())) + require.True(t, u2.Add(OneUint()).Sub(OneUint()).Equal(OneUint())) // i2 == 1 + require.True(t, u2.Add(OneUint()).Mul(NewUint(5)).Equal(NewUint(10))) // i2 == 10 + require.True(t, NewUint(7).Quo(NewUint(2)).Equal(NewUint(3))) + require.True(t, NewUint(0).Quo(NewUint(2)).Equal(ZeroUint())) + require.True(t, NewUint(5).MulUint64(4).Equal(NewUint(20))) + require.True(t, NewUint(5).MulUint64(0).Equal(ZeroUint())) + + uintmax := NewUintFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))) + uintmin := ZeroUint() + + // divs by zero + require.Panics(t, func() { OneUint().Mul(ZeroUint().SubUint64(uint64(1))) }) + require.Panics(t, func() { OneUint().QuoUint64(0) }) + require.Panics(t, func() { OneUint().Quo(ZeroUint()) }) + require.Panics(t, func() { ZeroUint().QuoUint64(0) }) + require.Panics(t, func() { OneUint().Quo(ZeroUint().Sub(OneUint())) }) + require.Panics(t, func() { uintmax.Add(OneUint()) }) + require.Panics(t, func() { uintmin.Sub(OneUint()) }) + + require.Equal(t, uint64(0), MinUint(ZeroUint(), OneUint()).Uint64()) + require.Equal(t, uint64(1), MaxUint(ZeroUint(), OneUint()).Uint64()) + + // comparison ops + require.True(t, + OneUint().GT(ZeroUint()), + ) + require.False(t, + OneUint().LT(ZeroUint()), + ) + require.True(t, + OneUint().GTE(ZeroUint()), + ) + require.False(t, + OneUint().LTE(ZeroUint()), + ) + + require.False(t, ZeroUint().GT(OneUint())) + require.True(t, ZeroUint().LT(OneUint())) + require.False(t, ZeroUint().GTE(OneUint())) + require.True(t, ZeroUint().LTE(OneUint())) +} + +func TestIdentUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n := rand.Uint64() + i := NewUint(n) + + ifromstr := NewUintFromString(strconv.FormatUint(n, 10)) + + cases := []uint64{ + i.Uint64(), + i.i.Uint64(), + ifromstr.Uint64(), + NewUintFromBigInt(new(big.Int).SetUint64(n)).Uint64(), + } + + for tcnum, tc := range cases { + require.Equal(t, n, tc, "Uint is modified during conversion. tc #%d", tcnum) + } + } +} + +func TestArithUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := uint64(rand.Uint32()) + u1 := NewUint(n1) + n2 := uint64(rand.Uint32()) + u2 := NewUint(n2) + + cases := []struct { + ures Uint + nres uint64 + }{ + {u1.Add(u2), n1 + n2}, + {u1.Mul(u2), n1 * n2}, + {u1.Quo(u2), n1 / n2}, + {u1.AddUint64(n2), n1 + n2}, + {u1.MulUint64(n2), n1 * n2}, + {u1.QuoUint64(n2), n1 / n2}, + {MinUint(u1, u2), minuint(n1, n2)}, + {MaxUint(u1, u2), maxuint(n1, n2)}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ures.Uint64(), "Uint arithmetic operation does not match with uint64 operation. tc #%d", tcnum) + } + + if n2 > n1 { + n1, n2 = n2, n1 + u1, u2 = NewUint(n1), NewUint(n2) + } + + subs := []struct { + ures Uint + nres uint64 + }{ + {u1.Sub(u2), n1 - n2}, + {u1.SubUint64(n2), n1 - n2}, + } + + for tcnum, tc := range subs { + require.Equal(t, tc.nres, tc.ures.Uint64(), "Uint subtraction does not match with uint64 operation. tc #%d", tcnum) + } + } +} + +func TestCompUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := rand.Uint64() + i1 := NewUint(n1) + n2 := rand.Uint64() + i2 := NewUint(n2) + + cases := []struct { + ires bool + nres bool + }{ + {i1.Equal(i2), n1 == n2}, + {i1.GT(i2), n1 > n2}, + {i1.LT(i2), n1 < n2}, + {i1.GTE(i2), !i1.LT(i2)}, + {!i1.GTE(i2), i1.LT(i2)}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ires, "Uint comparison operation does not match with uint64 operation. tc #%d", tcnum) + } + } +} + +func TestImmutabilityAllUint(t *testing.T) { + ops := []func(*Uint){ + func(i *Uint) { _ = i.Add(NewUint(rand.Uint64())) }, + func(i *Uint) { _ = i.Sub(NewUint(rand.Uint64() % i.Uint64())) }, + func(i *Uint) { _ = i.Mul(randuint()) }, + func(i *Uint) { _ = i.Quo(randuint()) }, + func(i *Uint) { _ = i.AddUint64(rand.Uint64()) }, + func(i *Uint) { _ = i.SubUint64(rand.Uint64() % i.Uint64()) }, + func(i *Uint) { _ = i.MulUint64(rand.Uint64()) }, + func(i *Uint) { _ = i.QuoUint64(rand.Uint64()) }, + func(i *Uint) { _ = i.IsZero() }, + func(i *Uint) { _ = i.Equal(randuint()) }, + func(i *Uint) { _ = i.GT(randuint()) }, + func(i *Uint) { _ = i.GTE(randuint()) }, + func(i *Uint) { _ = i.LT(randuint()) }, + func(i *Uint) { _ = i.LTE(randuint()) }, + func(i *Uint) { _ = i.String() }, + } + + for i := 0; i < 1000; i++ { + n := rand.Uint64() + ni := NewUint(n) + + for opnum, op := range ops { + op(&ni) + + require.Equal(t, n, ni.Uint64(), "Uint is modified by operation. #%d", opnum) + require.Equal(t, NewUint(n), ni, "Uint is modified by operation. #%d", opnum) + } + } +} + +func TestSafeSub(t *testing.T) { + testCases := []struct { + x, y Uint + expected uint64 + panic bool + }{ + {NewUint(0), NewUint(0), 0, false}, + {NewUint(10), NewUint(5), 5, false}, + {NewUint(5), NewUint(10), 5, true}, + {NewUint(math.MaxUint64), NewUint(0), math.MaxUint64, false}, + } + + for i, tc := range testCases { + if tc.panic { + require.Panics(t, func() { tc.x.Sub(tc.y) }) + continue + } + require.Equal( + t, tc.expected, tc.x.Sub(tc.y).Uint64(), + "invalid subtraction result; x: %s, y: %s, tc: #%d", tc.x, tc.y, i, + ) + } +} + +func TestParseUint(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want Uint + wantErr bool + }{ + {"malformed", args{"malformed"}, Uint{}, true}, + {"empty", args{""}, Uint{}, true}, + {"positive", args{"50"}, NewUint(uint64(50)), false}, + {"negative", args{"-1"}, Uint{}, true}, + {"zero", args{"0"}, ZeroUint(), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseUint(tt.args.s) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.True(t, got.Equal(tt.want)) + }) + } +} + +func randuint() Uint { + return NewUint(rand.Uint64()) +} diff --git a/types/utils.go b/types/utils.go index f5b244d74..09bce6b76 100644 --- a/types/utils.go +++ b/types/utils.go @@ -3,7 +3,15 @@ package types import ( "encoding/binary" "encoding/json" + "fmt" "time" + + dbm "github.com/tendermint/tendermint/libs/db" +) + +var ( + // This is set at compile time. Could be cleveldb, defaults is goleveldb. + DBBackend = "" ) // SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces @@ -58,3 +66,17 @@ func ParseTimeBytes(bz []byte) (time.Time, error) { } return t.UTC().Round(0), nil } + +// NewLevelDB instantiate a new LevelDB instance according to DBBackend. +func NewLevelDB(name, dir string) (db dbm.DB, err error) { + backend := dbm.GoLevelDBBackend + if DBBackend == string(dbm.CLevelDBBackend) { + backend = dbm.CLevelDBBackend + } + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("couldn't create db: %v", r) + } + }() + return dbm.NewDB(name, backend, dir), err +} diff --git a/x/auth/account.go b/x/auth/account.go index bc5c75da6..0c0e71a48 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -233,7 +233,7 @@ func (bva BaseVestingAccount) spendableCoins(vestingCoins sdk.Coins) sdk.Coins { spendableCoin := sdk.NewCoin(coin.Denom, min) if !spendableCoin.IsZero() { - spendableCoins = spendableCoins.Plus(sdk.Coins{spendableCoin}) + spendableCoins = spendableCoins.Add(sdk.Coins{spendableCoin}) } } @@ -270,15 +270,15 @@ func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) { if !x.IsZero() { xCoin := sdk.NewCoin(coin.Denom, x) - bva.DelegatedVesting = bva.DelegatedVesting.Plus(sdk.Coins{xCoin}) + bva.DelegatedVesting = bva.DelegatedVesting.Add(sdk.Coins{xCoin}) } if !y.IsZero() { yCoin := sdk.NewCoin(coin.Denom, y) - bva.DelegatedFree = bva.DelegatedFree.Plus(sdk.Coins{yCoin}) + bva.DelegatedFree = bva.DelegatedFree.Add(sdk.Coins{yCoin}) } - bva.Coins = bva.Coins.Minus(sdk.Coins{coin}) + bva.Coins = bva.Coins.Sub(sdk.Coins{coin}) } } @@ -287,6 +287,11 @@ func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) { // by which amount the base coins need to increase. The resulting base coins are // returned. // +// NOTE: The undelegation (bond refund) amount may exceed the delegated +// vesting (bond) amount due to the way undelegation truncates the bond refund, +// which can increase the validator's exchange rate (tokens/shares) slightly if +// the undelegated tokens are non-integral. +// // CONTRACT: The account's coins and undelegation coins must be sorted. func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) { for _, coin := range amount { @@ -295,24 +300,25 @@ func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) { panic("undelegation attempt with zero coins") } delegatedFree := bva.DelegatedFree.AmountOf(coin.Denom) + delegatedVesting := bva.DelegatedVesting.AmountOf(coin.Denom) // compute x and y per the specification, where: // X := min(DF, D) - // Y := D - X + // Y := min(DV, D - X) x := sdk.MinInt(delegatedFree, coin.Amount) - y := coin.Amount.Sub(x) + y := sdk.MinInt(delegatedVesting, coin.Amount.Sub(x)) if !x.IsZero() { xCoin := sdk.NewCoin(coin.Denom, x) - bva.DelegatedFree = bva.DelegatedFree.Minus(sdk.Coins{xCoin}) + bva.DelegatedFree = bva.DelegatedFree.Sub(sdk.Coins{xCoin}) } if !y.IsZero() { yCoin := sdk.NewCoin(coin.Denom, y) - bva.DelegatedVesting = bva.DelegatedVesting.Minus(sdk.Coins{yCoin}) + bva.DelegatedVesting = bva.DelegatedVesting.Sub(sdk.Coins{yCoin}) } - bva.Coins = bva.Coins.Plus(sdk.Coins{coin}) + bva.Coins = bva.Coins.Add(sdk.Coins{coin}) } } @@ -407,7 +413,7 @@ func (cva ContinuousVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coin s := sdk.NewDec(x).Quo(sdk.NewDec(y)) for _, ovc := range cva.OriginalVesting { - vestedAmt := sdk.NewDecFromInt(ovc.Amount).Mul(s).RoundInt() + vestedAmt := ovc.Amount.ToDec().Mul(s).RoundInt() vestedCoins = append(vestedCoins, sdk.NewCoin(ovc.Denom, vestedAmt)) } @@ -417,7 +423,7 @@ func (cva ContinuousVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coin // GetVestingCoins returns the total number of vesting coins. If no coins are // vesting, nil is returned. func (cva ContinuousVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins { - return cva.OriginalVesting.Minus(cva.GetVestedCoins(blockTime)) + return cva.OriginalVesting.Sub(cva.GetVestedCoins(blockTime)) } // SpendableCoins returns the total number of spendable coins per denom for a @@ -480,7 +486,7 @@ func (dva DelayedVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins { // GetVestingCoins returns the total number of vesting coins for a delayed // vesting account. func (dva DelayedVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins { - return dva.OriginalVesting.Minus(dva.GetVestedCoins(blockTime)) + return dva.OriginalVesting.Sub(dva.GetVestedCoins(blockTime)) } // SpendableCoins returns the total number of spendable coins for a delayed diff --git a/x/auth/account_test.go b/x/auth/account_test.go index 7d4fb5346..d8f7c6fa8 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -181,14 +181,14 @@ func TestSpendableCoinsContVestingAcc(t *testing.T) { // receive some coins recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)} - cva.SetCoins(cva.GetCoins().Plus(recvAmt)) + cva.SetCoins(cva.GetCoins().Add(recvAmt)) // require that all vested coins (50%) are spendable plus any received spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 100)}, spendableCoins) // spend all spendable coins - cva.SetCoins(cva.GetCoins().Minus(spendableCoins)) + cva.SetCoins(cva.GetCoins().Sub(spendableCoins)) // require that no more coins are spendable spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) @@ -362,7 +362,7 @@ func TestSpendableCoinsDelVestingAcc(t *testing.T) { // receive some coins recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)} - dva.SetCoins(dva.GetCoins().Plus(recvAmt)) + dva.SetCoins(dva.GetCoins().Add(recvAmt)) // require that only received coins are spendable since the account is still // vesting @@ -370,7 +370,7 @@ func TestSpendableCoinsDelVestingAcc(t *testing.T) { require.Equal(t, recvAmt, spendableCoins) // spend all spendable coins - dva.SetCoins(dva.GetCoins().Minus(spendableCoins)) + dva.SetCoins(dva.GetCoins().Sub(spendableCoins)) // require that no more coins are spendable spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour)) diff --git a/x/auth/ante.go b/x/auth/ante.go index ddf9dbecd..376f36018 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -146,7 +146,7 @@ func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (Accou if acc := ak.GetAccount(ctx, addr); acc != nil { return acc, sdk.Result{} } - return nil, sdk.ErrUnknownAddress(addr.String()).Result() + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)).Result() } // ValidateMemo validates the memo size. @@ -188,7 +188,10 @@ func processSig( consumeSimSigGas(ctx.GasMeter(), pubKey, sig, params) } - consumeSigVerificationGas(ctx.GasMeter(), sig.Signature, pubKey, params) + if res := consumeSigVerificationGas(ctx.GasMeter(), sig.Signature, pubKey, params); !res.IsOK() { + return nil, res + } + if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) { return nil, sdk.ErrUnauthorized("signature verification failed").Result() } @@ -256,14 +259,20 @@ func ProcessPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, // by the concrete type. // // TODO: Design a cleaner and flexible way to match concrete public key types. -func consumeSigVerificationGas(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) { +func consumeSigVerificationGas( + meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params, +) sdk.Result { + pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) + switch { case strings.Contains(pubkeyType, "ed25519"): meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + return sdk.ErrInvalidPubKey("ED25519 public keys are unsupported").Result() case strings.Contains(pubkeyType, "secp256k1"): meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + return sdk.Result{} case strings.Contains(pubkeyType, "multisigthreshold"): var multisignature multisig.Multisignature @@ -271,9 +280,10 @@ func consumeSigVerificationGas(meter sdk.GasMeter, sig []byte, pubkey crypto.Pub multisigPubKey := pubkey.(multisig.PubKeyMultisigThreshold) consumeMultisignatureVerificationGas(meter, multisignature, multisigPubKey, params) + return sdk.Result{} default: - panic(fmt.Sprintf("unrecognized signature type: %s", pubkeyType)) + return sdk.ErrInvalidPubKey(fmt.Sprintf("unrecognized public key type: %s", pubkeyType)).Result() } } @@ -304,7 +314,7 @@ func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Resu } // get the resulting coins deducting the fees - newCoins, ok := coins.SafeMinus(feeAmount) + newCoins, ok := coins.SafeSub(feeAmount) if ok { return nil, sdk.ErrInsufficientFunds( fmt.Sprintf("insufficient funds to pay for fees; %s < %s", coins, feeAmount), @@ -314,7 +324,7 @@ func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Resu // Validate the account has enough "spendable" coins as this will cover cases // such as vesting accounts. spendableCoins := acc.SpendableCoins(blockTime) - if _, hasNeg := spendableCoins.SafeMinus(feeAmount); hasNeg { + if _, hasNeg := spendableCoins.SafeSub(feeAmount); hasNeg { return nil, sdk.ErrInsufficientFunds( fmt.Sprintf("insufficient funds to pay for fees; %s < %s", spendableCoins, feeAmount), ).Result() diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 9a554080a..8d1db6f1a 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -585,19 +585,21 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { name string args args gasConsumed uint64 - wantPanic bool + shouldErr bool }{ - {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, false}, + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, true}, {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostSecp256k1, false}, {"Multisig", args{sdk.NewInfiniteGasMeter(), multisignature1.Marshal(), multisigKey1, params}, expectedCost1, false}, {"unknown key", args{sdk.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.wantPanic { - require.Panics(t, func() { consumeSigVerificationGas(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) }) + res := consumeSigVerificationGas(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) + + if tt.shouldErr { + require.False(t, res.IsOK()) } else { - consumeSigVerificationGas(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) + require.True(t, res.IsOK()) require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) } }) diff --git a/x/auth/client/cli/broadcast.go b/x/auth/client/cli/broadcast.go deleted file mode 100644 index 8dc1992b5..000000000 --- a/x/auth/client/cli/broadcast.go +++ /dev/null @@ -1,45 +0,0 @@ -package cli - -import ( - "strings" - - "github.com/spf13/cobra" - amino "github.com/tendermint/go-amino" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" -) - -// GetSignCommand returns the sign command -func GetBroadcastCommand(codec *amino.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "broadcast [file_path]", - Short: "Broadcast transactions generated offline", - Long: strings.TrimSpace(`Broadcast transactions created with the --generate-only flag and signed with the sign command. -Read a transaction from [file_path] and broadcast it to a node. If you supply a dash (-) argument -in place of an input filename, the command reads from standard input. - -$ gaiacli tx broadcast ./mytxn.json -`), - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) (err error) { - cliCtx := context.NewCLIContext().WithCodec(codec) - stdTx, err := authclient.ReadStdTxFromFile(cliCtx.Codec, args[0]) - if err != nil { - return - } - - txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) - if err != nil { - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - cliCtx.PrintOutput(res) - return err - }, - } - - return client.PostCommands(cmd)[0] -} diff --git a/x/auth/client/cli/encode.go b/x/auth/client/cli/encode.go deleted file mode 100644 index 071791551..000000000 --- a/x/auth/client/cli/encode.go +++ /dev/null @@ -1,55 +0,0 @@ -package cli - -import ( - "encoding/base64" - - "github.com/spf13/cobra" - amino "github.com/tendermint/go-amino" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" -) - -// PrintOutput requires a Stringer, so we wrap string -type encodeResp string - -func (e encodeResp) String() string { - return string(e) -} - -// GetEncodeCommand returns the encode command to take a JSONified transaction and turn it into -// Amino-serialized bytes -func GetEncodeCommand(codec *amino.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "encode [file]", - Short: "encode transactions generated offline", - Long: `Encode transactions created with the --generate-only flag and signed with the sign command. -Read a transaction from <file>, serialize it to the Amino wire protocol, and output it as base64. -If you supply a dash (-) argument in place of an input filename, the command reads from standard input.`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) (err error) { - cliCtx := context.NewCLIContext().WithCodec(codec) - - stdTx, err := authclient.ReadStdTxFromFile(cliCtx.Codec, args[0]) - if err != nil { - return - } - - txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx) - if err != nil { - return err - } - - // Encode the bytes to base64 - txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes) - - // Write it back - response := encodeResp(txBytesBase64) - cliCtx.PrintOutput(response) - return nil - }, - } - - return client.PostCommands(cmd)[0] -} diff --git a/x/auth/client/cli/multisign.go b/x/auth/client/cli/multisign.go index fdcd1d142..d25983a12 100644 --- a/x/auth/client/cli/multisign.go +++ b/x/auth/client/cli/multisign.go @@ -14,9 +14,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/utils" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/x/auth" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) @@ -52,7 +52,7 @@ recommended to set such parameters manually. func makeMultiSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) (err error) { - stdTx, err := authclient.ReadStdTxFromFile(cdc, args[0]) + stdTx, err := utils.ReadStdTxFromFile(cdc, args[0]) if err != nil { return } @@ -66,9 +66,8 @@ func makeMultiSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) if err != nil { return } - if multisigInfo.GetType() != crkeys.TypeOffline { - return fmt.Errorf("%q must be of type offline: %s", - args[1], multisigInfo.GetType()) + if multisigInfo.GetType() != crkeys.TypeMulti { + return fmt.Errorf("%q must be of type %s: %s", args[1], crkeys.TypeMulti, multisigInfo.GetType()) } multisigPub := multisigInfo.GetPubKey().(multisig.PubKeyMultisigThreshold) diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index 371abd84b..e30fd2e4f 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -1,20 +1,20 @@ package cli import ( - "errors" "fmt" "os" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) @@ -55,27 +55,30 @@ be generated via the 'multisign' command. RunE: makeSignCmd(codec), Args: cobra.ExactArgs(1), } - cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign") - cmd.Flags().String(flagMultisig, "", - "Address of the multisig account on behalf of which the "+ - "transaction shall be signed") - cmd.Flags().Bool(flagAppend, true, - "Append the signature to the existing ones. "+ - "If disabled, old signatures would be overwritten. Ignored if --multisig is on") - cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit") - cmd.Flags().Bool(flagValidateSigs, false, "Print the addresses that must sign the transaction, "+ - "those who have already signed it, and make sure that signatures are in the correct order") - cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query a full node") - cmd.Flags().String(flagOutfile, "", - "The document will be written to the given file instead of STDOUT") - // Add the flags here and return the command + cmd.Flags().String( + flagMultisig, "", + "Address of the multisig account on behalf of which the transaction shall be signed", + ) + cmd.Flags().Bool( + flagAppend, true, + "Append the signature to the existing ones. If disabled, old signatures would be overwritten. Ignored if --multisig is on", + ) + cmd.Flags().Bool( + flagValidateSigs, false, + "Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order", + ) + cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit") + cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query a full node") + cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT") + + // add the flags here and return the command return client.PostCommands(cmd)[0] } func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) (err error) { - stdTx, err := authclient.ReadStdTxFromFile(cdc, args[0]) + stdTx, err := utils.ReadStdTxFromFile(cdc, args[0]) if err != nil { return } @@ -92,9 +95,9 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error return nil } - name := viper.GetString(client.FlagName) - if name == "" { - return errors.New("required flag \"name\" has not been set") + from := viper.GetString(client.FlagFrom) + if from == "" { + return fmt.Errorf("required flag '%s' has not been set", client.FlagFrom) } // if --signature-only is on, then override --append @@ -104,19 +107,21 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error if multisigAddrStr != "" { var multisigAddr sdk.AccAddress + multisigAddr, err = sdk.AccAddressFromBech32(multisigAddrStr) if err != nil { return err } newTx, err = utils.SignStdTxWithSignerAddress( - txBldr, cliCtx, multisigAddr, name, stdTx, offline) + txBldr, cliCtx, multisigAddr, cliCtx.GetFromName(), stdTx, offline, + ) generateSignatureOnly = true } else { appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly - newTx, err = utils.SignStdTx( - txBldr, cliCtx, name, stdTx, appendSig, offline) + newTx, err = utils.SignStdTx(txBldr, cliCtx, cliCtx.GetFromName(), stdTx, appendSig, offline) } + if err != nil { return err } @@ -128,13 +133,16 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error switch cliCtx.Indent { case true: json, err = cdc.MarshalJSONIndent(newTx.Signatures[0], "", " ") + default: json, err = cdc.MarshalJSON(newTx.Signatures[0]) } + default: switch cliCtx.Indent { case true: json, err = cdc.MarshalJSONIndent(newTx, "", " ") + default: json, err = cdc.MarshalJSON(newTx) } @@ -174,7 +182,7 @@ func printAndValidateSigs( signers := stdTx.GetSigners() for i, signer := range signers { - fmt.Printf(" %v: %v\n", i, signer.String()) + fmt.Printf(" %v: %v\n", i, signer.String()) } success := true @@ -191,6 +199,11 @@ func printAndValidateSigs( sigAddr := sdk.AccAddress(sig.Address()) sigSanity := "OK" + var ( + multiSigHeader string + multiSigMsg string + ) + if i >= len(signers) || !sigAddr.Equals(signers[i]) { sigSanity = "ERROR: signature does not match its respective signer" success = false @@ -216,7 +229,26 @@ func printAndValidateSigs( } } - fmt.Printf(" %v: %v\t[%s]\n", i, sigAddr.String(), sigSanity) + multiPK, ok := sig.PubKey.(multisig.PubKeyMultisigThreshold) + if ok { + var multiSig multisig.Multisignature + cliCtx.Codec.MustUnmarshalBinaryBare(sig.Signature, &multiSig) + + var b strings.Builder + b.WriteString("\n MultiSig Signatures:\n") + + for i := 0; i < multiSig.BitArray.Size(); i++ { + if multiSig.BitArray.GetIndex(i) { + addr := sdk.AccAddress(multiPK.PubKeys[i].Address().Bytes()) + b.WriteString(fmt.Sprintf(" %d: %s (weight: %d)\n", i, addr, 1)) + } + } + + multiSigHeader = fmt.Sprintf(" [multisig threshold: %d/%d]", multiPK.K, len(multiPK.PubKeys)) + multiSigMsg = b.String() + } + + fmt.Printf(" %d: %s\t\t\t[%s]%s%s\n", i, sigAddr.String(), sigSanity, multiSigHeader, multiSigMsg) } fmt.Println("") diff --git a/x/auth/client/rest/broadcast.go b/x/auth/client/rest/broadcast.go deleted file mode 100644 index 2e0cc9c42..000000000 --- a/x/auth/client/rest/broadcast.go +++ /dev/null @@ -1,53 +0,0 @@ -package rest - -import ( - "io/ioutil" - "net/http" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -type broadcastBody struct { - Tx auth.StdTx `json:"tx"` -} - -// BroadcastTxRequestHandlerFn returns the broadcast tx REST handler -func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var m broadcastBody - if ok := unmarshalBodyOrReturnBadRequest(cliCtx, w, r, &m); !ok { - return - } - - txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) - } -} - -func unmarshalBodyOrReturnBadRequest(cliCtx context.CLIContext, w http.ResponseWriter, r *http.Request, m interface{}) bool { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return false - } - err = cliCtx.Codec.UnmarshalJSON(body, m) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return false - } - return true -} diff --git a/x/auth/client/rest/encode.go b/x/auth/client/rest/encode.go deleted file mode 100644 index 97c25f15f..000000000 --- a/x/auth/client/rest/encode.go +++ /dev/null @@ -1,47 +0,0 @@ -package rest - -import ( - "encoding/base64" - "net/http" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types/rest" - - "github.com/cosmos/cosmos-sdk/x/auth" -) - -type encodeReq struct { - Tx auth.StdTx `json:"tx"` -} - -type encodeResp struct { - Tx string `json:"tx"` -} - -// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular, it takes a -// json-formatted transaction, encodes it to the Amino wire protocol, and responds with -// base64-encoded bytes -func EncodeTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var m encodeReq - // Decode the transaction from JSON - if ok := unmarshalBodyOrReturnBadRequest(cliCtx, w, r, &m); !ok { - return - } - - // Re-encode it to the wire protocol - txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // Encode the bytes to base64 - txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes) - - // Write it back - response := encodeResp{Tx: txBytesBase64} - rest.PostProcessResponse(w, cdc, response, cliCtx.Indent) - } -} diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 70301aa7f..e7345f3d4 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -19,22 +19,11 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, "/auth/accounts/{address}", QueryAccountRequestHandlerFn(storeName, cdc, context.GetAccountDecoder(cdc), cliCtx), ).Methods("GET") + r.HandleFunc( "/bank/balances/{address}", QueryBalancesRequestHandlerFn(storeName, cdc, context.GetAccountDecoder(cdc), cliCtx), ).Methods("GET") - r.HandleFunc( - "/tx/broadcast", - BroadcastTxRequestHandlerFn(cdc, cliCtx), - ).Methods("POST") - r.HandleFunc( - "/tx/encode", - EncodeTxRequestHandlerFn(cdc, cliCtx), - ).Methods("POST") - r.HandleFunc( - "/tx/sign", - SignTxRequestHandlerFn(cdc, cliCtx), - ).Methods("POST") } // query accountREST Handler diff --git a/x/auth/client/rest/requests.go b/x/auth/client/rest/requests.go deleted file mode 100644 index 2910c2158..000000000 --- a/x/auth/client/rest/requests.go +++ /dev/null @@ -1,9 +0,0 @@ -package rest - -import "github.com/cosmos/cosmos-sdk/x/auth" - -// BroadcastReq requests broadcasting a transaction -type BroadcastReq struct { - Tx auth.StdTx `json:"tx"` - Return string `json:"return"` -} diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go deleted file mode 100644 index deae5ce6b..000000000 --- a/x/auth/client/rest/sign.go +++ /dev/null @@ -1,81 +0,0 @@ -package rest - -import ( - "net/http" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/rest" - - "github.com/cosmos/cosmos-sdk/x/auth" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -// SignBody defines the properties of a sign request's body. -type SignBody struct { - Tx auth.StdTx `json:"tx"` - AppendSig bool `json:"append_sig"` - BaseReq rest.BaseReq `json:"base_req"` -} - -// nolint: unparam -// sign tx REST handler -func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var m SignBody - - if !rest.ReadRESTReq(w, r, cdc, &m) { - return - } - - if !m.BaseReq.ValidateBasic(w) { - return - } - - // validate tx - // discard error if it's CodeNoSignatures as the tx comes with no signatures - if err := m.Tx.ValidateBasic(); err != nil && err.Code() != sdk.CodeNoSignatures { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(m.BaseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - - txBldr := authtxb.NewTxBuilder( - utils.GetTxEncoder(cdc), - m.BaseReq.AccountNumber, - m.BaseReq.Sequence, - m.Tx.Fee.Gas, - 1.0, - false, - m.BaseReq.ChainID, - m.Tx.GetMemo(), - m.Tx.Fee.Amount, - nil, - ) - - signedTx, err := txBldr.SignStdTx(cliCtx.GetFromName(), m.BaseReq.Password, m.Tx, m.AppendSig) - if keyerror.IsErrKeyNotFound(err) { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } else if keyerror.IsErrWrongPassword(err) { - rest.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } else if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cdc, signedTx, cliCtx.Indent) - } -} diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index 7ac59426e..3ea6989e6 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -174,8 +174,9 @@ func (bldr TxBuilder) WithAccountNumber(accnum uint64) TxBuilder { return bldr } -// BuildSignMsg builds a single message to be signed from a TxBuilder given a set of -// messages. It returns an error if a fee is supplied but cannot be parsed. +// BuildSignMsg builds a single message to be signed from a TxBuilder given a +// set of messages. It returns an error if a fee is supplied but cannot be +// parsed. func (bldr TxBuilder) BuildSignMsg(msgs []sdk.Msg) (StdSignMsg, error) { chainID := bldr.chainID if chainID == "" { @@ -221,8 +222,7 @@ func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, err } // BuildAndSign builds a single message to be signed, and signs a transaction -// with the built message given a name, passphrase, and a set of -// messages. +// with the built message given a name, passphrase, and a set of messages. func (bldr TxBuilder) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]byte, error) { msg, err := bldr.BuildSignMsg(msgs) if err != nil { diff --git a/x/auth/client/util.go b/x/auth/client/util.go deleted file mode 100644 index 94eb480d6..000000000 --- a/x/auth/client/util.go +++ /dev/null @@ -1,27 +0,0 @@ -package client - -import ( - "io/ioutil" - "os" - - "github.com/tendermint/go-amino" - - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin. -func ReadStdTxFromFile(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) { - var bytes []byte - if filename == "-" { - bytes, err = ioutil.ReadAll(os.Stdin) - } else { - bytes, err = ioutil.ReadFile(filename) - } - if err != nil { - return - } - if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil { - return - } - return -} diff --git a/x/auth/client/util_test.go b/x/auth/client/util_test.go deleted file mode 100644 index 409219cd7..000000000 --- a/x/auth/client/util_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package client - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -func TestReadStdTxFromFile(t *testing.T) { - cdc := codec.New() - sdk.RegisterCodec(cdc) - - // Build a test transaction - fee := auth.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}) - stdTx := auth.NewStdTx([]sdk.Msg{}, fee, []auth.StdSignature{}, "foomemo") - - // Write it to the file - encodedTx, _ := cdc.MarshalJSON(stdTx) - jsonTxFile := clitest.WriteToNewTempFile(t, string(encodedTx)) - defer os.Remove(jsonTxFile.Name()) - - // Read it back - decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) - require.Nil(t, err) - require.Equal(t, decodedTx.Memo, "foomemo") -} diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index 2b37a1402..aa8cf9212 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -49,7 +49,7 @@ func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins // AddCollectedFees - add to the fee pool func (fck FeeCollectionKeeper) AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { - newCoins := fck.GetCollectedFees(ctx).Plus(coins) + newCoins := fck.GetCollectedFees(ctx).Add(coins) fck.setCollectedFees(ctx, newCoins) return newCoins diff --git a/x/auth/keeper.go b/x/auth/keeper.go index 84899fa1c..9476029a1 100644 --- a/x/auth/keeper.go +++ b/x/auth/keeper.go @@ -1,6 +1,8 @@ package auth import ( + "fmt" + "github.com/tendermint/tendermint/crypto" codec "github.com/cosmos/cosmos-sdk/codec" @@ -8,17 +10,22 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -var ( - // AddressStoreKeyPrefix prefix for account-by-address store - AddressStoreKeyPrefix = []byte{0x01} - - globalAccountNumberKey = []byte("globalAccountNumber") - +const ( // StoreKey is string representation of the store key for auth StoreKey = "acc" // FeeStoreKey is a string representation of the store key for fees FeeStoreKey = "fee" + + // QuerierRoute is the querier route for acc + QuerierRoute = StoreKey +) + +var ( + // AddressStoreKeyPrefix prefix for account-by-address store + AddressStoreKeyPrefix = []byte{0x01} + + globalAccountNumberKey = []byte("globalAccountNumber") ) // AccountKeeper encodes/decodes accounts using the go-amino (binary) @@ -143,7 +150,7 @@ func (ak AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) ( func (ak AccountKeeper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, sdk.Error) { acc := ak.GetAccount(ctx, addr) if acc == nil { - return nil, sdk.ErrUnknownAddress(addr.String()) + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) } return acc.GetPubKey(), nil } @@ -152,7 +159,7 @@ func (ak AccountKeeper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto. func (ak AccountKeeper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (uint64, sdk.Error) { acc := ak.GetAccount(ctx, addr) if acc == nil { - return 0, sdk.ErrUnknownAddress(addr.String()) + return 0, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) } return acc.GetSequence(), nil } @@ -160,7 +167,7 @@ func (ak AccountKeeper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (uint6 func (ak AccountKeeper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSequence uint64) sdk.Error { acc := ak.GetAccount(ctx, addr) if acc == nil { - return sdk.ErrUnknownAddress(addr.String()) + return sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) } if err := acc.SetSequence(newSequence); err != nil { @@ -191,7 +198,7 @@ func (ak AccountKeeper) GetNextAccountNumber(ctx sdk.Context) uint64 { return accNumber } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Params // SetParams sets the auth module's parameters. @@ -205,7 +212,7 @@ func (ak AccountKeeper) GetParams(ctx sdk.Context) (params Params) { return } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Misc. func (ak AccountKeeper) decodeAccount(bz []byte) (acc Account) { diff --git a/x/auth/querier.go b/x/auth/querier.go new file mode 100644 index 000000000..2a5dbf422 --- /dev/null +++ b/x/auth/querier.go @@ -0,0 +1,57 @@ +package auth + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// query endpoints supported by the auth Querier +const ( + QueryAccount = "account" +) + +// creates a querier for auth REST endpoints +func NewQuerier(keeper AccountKeeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QueryAccount: + return queryAccount(ctx, req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown auth query endpoint") + } + } +} + +// defines the params for query: "custom/acc/account" +type QueryAccountParams struct { + Address sdk.AccAddress +} + +func NewQueryAccountParams(addr sdk.AccAddress) QueryAccountParams { + return QueryAccountParams{ + Address: addr, + } +} + +func queryAccount(ctx sdk.Context, req abci.RequestQuery, keeper AccountKeeper) ([]byte, sdk.Error) { + var params QueryAccountParams + if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + account := keeper.GetAccount(ctx, params.Address) + if account == nil { + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", params.Address)) + } + + bz, err := codec.MarshalJSONIndent(keeper.cdc, account) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} diff --git a/x/auth/querier_test.go b/x/auth/querier_test.go new file mode 100644 index 000000000..6d9ec0e96 --- /dev/null +++ b/x/auth/querier_test.go @@ -0,0 +1,41 @@ +package auth + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +func Test_queryAccount(t *testing.T) { + input := setupTestInput() + req := abci.RequestQuery{ + Path: fmt.Sprintf("custom/%s/%s", QuerierRoute, QueryAccount), + Data: []byte{}, + } + + res, err := queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + req.Data = input.cdc.MustMarshalJSON(NewQueryAccountParams([]byte(""))) + res, err = queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + _, _, addr := keyPubAddr() + req.Data = input.cdc.MustMarshalJSON(NewQueryAccountParams(addr)) + res, err = queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + input.ak.SetAccount(input.ctx, input.ak.NewAccountWithAddress(input.ctx, addr)) + res, err = queryAccount(input.ctx, req, input.ak) + require.Nil(t, err) + require.NotNil(t, res) + + var account Account + err2 := input.cdc.UnmarshalJSON(res, &account) + require.Nil(t, err2) +} diff --git a/x/auth/simulation/fake.go b/x/auth/simulation/fake.go index c037d74c2..f28c27cd9 100644 --- a/x/auth/simulation/fake.go +++ b/x/auth/simulation/fake.go @@ -40,13 +40,13 @@ func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulat // balance. fees := sdk.Coins{sdk.NewCoin(randCoin.Denom, amt)} spendableCoins := stored.SpendableCoins(ctx.BlockHeader().Time) - if _, hasNeg := spendableCoins.SafeMinus(fees); hasNeg { + if _, hasNeg := spendableCoins.SafeSub(fees); hasNeg { event(fmt.Sprintf("auth/SimulateDeductFee/false")) return action, nil, nil } // get the new account balance - newCoins, hasNeg := initCoins.SafeMinus(fees) + newCoins, hasNeg := initCoins.SafeSub(fees) if hasNeg { event(fmt.Sprintf("auth/SimulateDeductFee/false")) return action, nil, nil diff --git a/x/auth/test_utils.go b/x/auth/test_utils.go index 096b3897a..800622b42 100644 --- a/x/auth/test_utils.go +++ b/x/auth/test_utils.go @@ -4,7 +4,7 @@ package auth import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -67,7 +67,7 @@ func newCoins() sdk.Coins { } func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { - key := ed25519.GenPrivKey() + key := secp256k1.GenPrivKey() pub := key.PubKey() addr := sdk.AccAddress(pub.Address()) return key, pub, addr diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 824f48670..8703d71ec 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -11,7 +11,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" ) type ( @@ -32,12 +32,12 @@ type ( ) var ( - priv1 = ed25519.GenPrivKey() + priv1 = secp256k1.GenPrivKey() addr1 = sdk.AccAddress(priv1.PubKey().Address()) - priv2 = ed25519.GenPrivKey() + priv2 = secp256k1.GenPrivKey() addr2 = sdk.AccAddress(priv2.PubKey().Address()) - addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - priv4 = ed25519.GenPrivKey() + addr3 = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + priv4 = secp256k1.GenPrivKey() addr4 = sdk.AccAddress(priv4.PubKey().Address()) coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} @@ -104,6 +104,36 @@ func getInitChainer(mapp *mock.App, keeper BaseKeeper) sdk.InitChainer { } } +func TestSendNotEnoughBalance(t *testing.T) { + mapp := getMockApp(t) + acc := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, + } + + mock.SetGenesis(mapp, []auth.Account{acc}) + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) + require.NotNil(t, res1) + require.Equal(t, acc, res1.(*auth.BaseAccount)) + + origAccNum := res1.GetAccountNumber() + origSeq := res1.GetSequence() + + sendMsg := NewMsgSend(addr1, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 100)}) + mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, []sdk.Msg{sendMsg}, []uint64{origAccNum}, []uint64{origSeq}, false, false, priv1) + + mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}) + + res2 := mapp.AccountKeeper.GetAccount(mapp.NewContext(true, abci.Header{}), addr1) + require.NotNil(t, res2) + + require.True(t, res2.GetAccountNumber() == origAccNum) + require.True(t, res2.GetSequence() == origSeq+1) +} + func TestMsgMultiSendWithAccounts(t *testing.T) { mapp := getMockApp(t) acc := &auth.BaseAccount{ diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 73d0a42ce..552aa2d8c 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -54,30 +54,13 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC return } - if req.BaseReq.GenerateOnly { - // When generate only is supplied, the from field must be a valid Bech32 - // address. - fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - msg := bank.NewMsgSend(fromAddr, toAddr, req.Amount) - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - msg := bank.NewMsgSend(cliCtx.GetFromAddress(), toAddr, req.Amount) - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + msg := bank.NewMsgSend(fromAddr, toAddr, req.Amount) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/x/bank/codec.go b/x/bank/codec.go index 505ffd5b8..58c59d5f6 100644 --- a/x/bank/codec.go +++ b/x/bank/codec.go @@ -6,8 +6,8 @@ import ( // Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/Send", nil) - cdc.RegisterConcrete(MsgMultiSend{}, "cosmos-sdk/MultiSend", nil) + cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/MsgSend", nil) + cdc.RegisterConcrete(MsgMultiSend{}, "cosmos-sdk/MsgMultiSend", nil) } var msgCdc = codec.New() diff --git a/x/bank/keeper.go b/x/bank/keeper.go index e7a93ce71..81db06387 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -47,7 +47,13 @@ func NewBaseKeeper(ak auth.AccountKeeper, } // SetCoins sets the coins at the addr. -func (keeper BaseKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { +func (keeper BaseKeeper) SetCoins( + ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, +) sdk.Error { + + if !amt.IsValid() { + return sdk.ErrInvalidCoins(amt.String()) + } return setCoins(ctx, keeper.ak, addr, amt) } @@ -56,6 +62,9 @@ func (keeper BaseKeeper) SubtractCoins( ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Coins, sdk.Tags, sdk.Error) { + if !amt.IsValid() { + return nil, nil, sdk.ErrInvalidCoins(amt.String()) + } return subtractCoins(ctx, keeper.ak, addr, amt) } @@ -64,6 +73,9 @@ func (keeper BaseKeeper) AddCoins( ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Coins, sdk.Tags, sdk.Error) { + if !amt.IsValid() { + return nil, nil, sdk.ErrInvalidCoins(amt.String()) + } return addCoins(ctx, keeper.ak, addr, amt) } @@ -78,14 +90,27 @@ func (keeper BaseKeeper) InputOutputCoins( // DelegateCoins performs delegation by deducting amt coins from an account with // address addr. For vesting accounts, delegations amounts are tracked for both // vesting and vested coins. -func (keeper BaseKeeper) DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { +func (keeper BaseKeeper) DelegateCoins( + ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, +) (sdk.Tags, sdk.Error) { + + if !amt.IsValid() { + return nil, sdk.ErrInvalidCoins(amt.String()) + } return delegateCoins(ctx, keeper.ak, addr, amt) } // UndelegateCoins performs undelegation by crediting amt coins to an account with // address addr. For vesting accounts, undelegation amounts are tracked for both // vesting and vested coins. -func (keeper BaseKeeper) UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { +// If any of the undelegation amounts are negative, an error is returned. +func (keeper BaseKeeper) UndelegateCoins( + ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, +) (sdk.Tags, sdk.Error) { + + if !amt.IsValid() { + return nil, sdk.ErrInvalidCoins(amt.String()) + } return undelegateCoins(ctx, keeper.ak, addr, amt) } @@ -126,6 +151,10 @@ func NewBaseSendKeeper(ak auth.AccountKeeper, func (keeper BaseSendKeeper) SendCoins( ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins, ) (sdk.Tags, sdk.Error) { + + if !amt.IsValid() { + return nil, sdk.ErrInvalidCoins(amt.String()) + } return sendCoins(ctx, keeper.ak, fromAddr, toAddr, amt) } @@ -188,6 +217,9 @@ func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.C } func setCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) sdk.Error { + if !amt.IsValid() { + return sdk.ErrInvalidCoins(amt.String()) + } acc := am.GetAccount(ctx, addr) if acc == nil { acc = am.NewAccountWithAddress(ctx, addr) @@ -218,6 +250,11 @@ func setAccount(ctx sdk.Context, ak auth.AccountKeeper, acc auth.Account) { // // CONTRACT: If the account is a vesting account, the amount has to be spendable. func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { + + if !amt.IsValid() { + return nil, nil, sdk.ErrInvalidCoins(amt.String()) + } + oldCoins, spendableCoins := sdk.Coins{}, sdk.Coins{} acc := getAccount(ctx, ak, addr) @@ -228,14 +265,14 @@ func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, // For non-vesting accounts, spendable coins will simply be the original coins. // So the check here is sufficient instead of subtracting from oldCoins. - _, hasNeg := spendableCoins.SafeMinus(amt) + _, hasNeg := spendableCoins.SafeSub(amt) if hasNeg { return amt, nil, sdk.ErrInsufficientCoins( fmt.Sprintf("insufficient account funds; %s < %s", spendableCoins, amt), ) } - newCoins := oldCoins.Minus(amt) // should not panic as spendable coins was already checked + newCoins := oldCoins.Sub(amt) // should not panic as spendable coins was already checked err := setCoins(ctx, ak, addr, newCoins) tags := sdk.NewTags(TagKeySender, addr.String()) @@ -244,8 +281,13 @@ func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, // AddCoins adds amt to the coins at the addr. func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { + + if !amt.IsValid() { + return nil, nil, sdk.ErrInvalidCoins(amt.String()) + } + oldCoins := getCoins(ctx, am, addr) - newCoins := oldCoins.Plus(amt) + newCoins := oldCoins.Add(amt) if newCoins.IsAnyNegative() { return amt, nil, sdk.ErrInsufficientCoins( @@ -260,9 +302,9 @@ func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s } // SendCoins moves coins from one account to another +// Returns ErrInvalidCoins if amt is invalid. func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) { // Safety check ensuring that when sending coins the keeper must maintain the - // supply invariant. if !amt.IsValid() { return nil, sdk.ErrInvalidCoins(amt.String()) } @@ -284,7 +326,7 @@ func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress, // NOTE: Make sure to revert state changes from tx on error func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { // Safety check ensuring that when sending coins the keeper must maintain the - // supply invariant. + // Check supply invariant and validity of Coins. if err := ValidateInputsOutputs(inputs, outputs); err != nil { return nil, err } @@ -314,6 +356,10 @@ func delegateCoins( ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Tags, sdk.Error) { + if !amt.IsValid() { + return nil, sdk.ErrInvalidCoins(amt.String()) + } + acc := getAccount(ctx, ak, addr) if acc == nil { return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) @@ -321,7 +367,7 @@ func delegateCoins( oldCoins := acc.GetCoins() - _, hasNeg := oldCoins.SafeMinus(amt) + _, hasNeg := oldCoins.SafeSub(amt) if hasNeg { return nil, sdk.ErrInsufficientCoins( fmt.Sprintf("insufficient account funds; %s < %s", oldCoins, amt), @@ -344,6 +390,10 @@ func undelegateCoins( ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins, ) (sdk.Tags, sdk.Error) { + if !amt.IsValid() { + return nil, sdk.ErrInvalidCoins(amt.String()) + } + acc := getAccount(ctx, ak, addr) if acc == nil { return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) @@ -361,22 +411,24 @@ func undelegateCoins( ), nil } -func trackDelegation(acc auth.Account, blockTime time.Time, amount sdk.Coins) error { +// CONTRACT: assumes that amt is valid. +func trackDelegation(acc auth.Account, blockTime time.Time, amt sdk.Coins) error { vacc, ok := acc.(auth.VestingAccount) if ok { - vacc.TrackDelegation(blockTime, amount) + vacc.TrackDelegation(blockTime, amt) return nil } - return acc.SetCoins(acc.GetCoins().Minus(amount)) + return acc.SetCoins(acc.GetCoins().Sub(amt)) } -func trackUndelegation(acc auth.Account, amount sdk.Coins) error { +// CONTRACT: assumes that amt is valid. +func trackUndelegation(acc auth.Account, amt sdk.Coins) error { vacc, ok := acc.(auth.VestingAccount) if ok { - vacc.TrackUndelegation(amount) + vacc.TrackUndelegation(amt) return nil } - return acc.SetCoins(acc.GetCoins().Plus(amount)) + return acc.SetCoins(acc.GetCoins().Add(amt)) } diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 23e6321ab..cb277882e 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -230,7 +230,7 @@ func TestVestingAccountSend(t *testing.T) { require.Error(t, err) // receive some coins - vacc.SetCoins(origCoins.Plus(sendCoins)) + vacc.SetCoins(origCoins.Add(sendCoins)) input.ak.SetAccount(ctx, vacc) // require that all vested coins are spendable plus any received @@ -268,7 +268,7 @@ func TestVestingAccountReceive(t *testing.T) { // require the coins are spendable vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount) - require.Equal(t, origCoins.Plus(sendCoins), vacc.GetCoins()) + require.Equal(t, origCoins.Add(sendCoins), vacc.GetCoins()) require.Equal(t, vacc.SpendableCoins(now), sendCoins) // require coins are spendable plus any that have vested diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 11479ff73..c9b4f5544 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -35,6 +35,9 @@ func (msg MsgSend) ValidateBasic() sdk.Error { if msg.ToAddress.Empty() { return sdk.ErrInvalidAddress("missing recipient address") } + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins("send amount is invalid: " + msg.Amount.String()) + } if !msg.Amount.IsAllPositive() { return sdk.ErrInsufficientCoins("send amount must be positive") } @@ -163,14 +166,14 @@ func ValidateInputsOutputs(inputs []Input, outputs []Output) sdk.Error { if err := in.ValidateBasic(); err != nil { return err.TraceSDK("") } - totalIn = totalIn.Plus(in.Coins) + totalIn = totalIn.Add(in.Coins) } for _, out := range outputs { if err := out.ValidateBasic(); err != nil { return err.TraceSDK("") } - totalOut = totalOut.Plus(out.Coins) + totalOut = totalOut.Add(out.Coins) } // make sure inputs and outputs match diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index b065dc9cc..baec34bcf 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -58,7 +58,7 @@ func TestMsgSendGetSignBytes(t *testing.T) { var msg = NewMsgSend(addr1, addr2, coins) res := msg.GetSignBytes() - expected := `{"type":"cosmos-sdk/Send","value":{"amount":[{"amount":"10","denom":"atom"}],"from_address":"cosmos1d9h8qat57ljhcm","to_address":"cosmos1da6hgur4wsmpnjyg"}}` + expected := `{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"10","denom":"atom"}],"from_address":"cosmos1d9h8qat57ljhcm","to_address":"cosmos1da6hgur4wsmpnjyg"}}` require.Equal(t, expected, string(res)) } @@ -224,7 +224,7 @@ func TestMsgMultiSendGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - expected := `{"type":"cosmos-sdk/MultiSend","value":{"inputs":[{"address":"cosmos1d9h8qat57ljhcm","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmos1da6hgur4wsmpnjyg","coins":[{"amount":"10","denom":"atom"}]}]}}` + expected := `{"type":"cosmos-sdk/MsgMultiSend","value":{"inputs":[{"address":"cosmos1d9h8qat57ljhcm","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmos1da6hgur4wsmpnjyg","coins":[{"amount":"10","denom":"atom"}]}]}}` require.Equal(t, expected, string(res)) } diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go index 70ca9b9db..7448b4687 100644 --- a/x/bank/simulation/invariants.go +++ b/x/bank/simulation/invariants.go @@ -32,7 +32,7 @@ func TotalCoinsInvariant(ak auth.AccountKeeper, totalSupplyFn func() sdk.Coins) chkAccount := func(acc auth.Account) bool { coins := acc.GetCoins() - totalCoins = totalCoins.Plus(coins) + totalCoins = totalCoins.Add(coins) return false } diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index a3b47823d..1e4c529a9 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -122,11 +122,11 @@ func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg b fromAcc = mapper.GetAccount(ctx, msg.FromAddress) toAcc = mapper.GetAccount(ctx, msg.ToAddress) - if !initialFromAddrCoins.Minus(msg.Amount).IsEqual(fromAcc.GetCoins()) { + if !initialFromAddrCoins.Sub(msg.Amount).IsEqual(fromAcc.GetCoins()) { return fmt.Errorf("fromAddress %s had an incorrect amount of coins", fromAcc.GetAddress()) } - if !initialToAddrCoins.Plus(msg.Amount).IsEqual(toAcc.GetCoins()) { + if !initialToAddrCoins.Add(msg.Amount).IsEqual(toAcc.GetCoins()) { return fmt.Errorf("toAddress %s had an incorrect amount of coins", toAcc.GetAddress()) } @@ -249,13 +249,13 @@ func sendAndVerifyMsgMultiSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, for i := 0; i < len(msg.Inputs); i++ { terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() - if !initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins).IsEqual(terminalInputCoins) { + if !initialInputAddrCoins[i].Sub(msg.Inputs[i].Coins).IsEqual(terminalInputCoins) { return fmt.Errorf("input #%d had an incorrect amount of coins", i) } } for i := 0; i < len(msg.Outputs); i++ { terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() - if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins)) { + if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Add(msg.Outputs[i].Coins)) { return fmt.Errorf("output #%d had an incorrect amount of coins", i) } } diff --git a/x/distribution/alias.go b/x/distribution/alias.go index 85b93fc17..34ad7dd57 100644 --- a/x/distribution/alias.go +++ b/x/distribution/alias.go @@ -50,14 +50,15 @@ var ( NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams - NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams - NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams - NewQueryDelegatorParams = keeper.NewQueryDelegatorParams - NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams - DefaultParamspace = keeper.DefaultParamspace + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + NewQueryValidatorOutstandingRewardsParams = keeper.NewQueryValidatorOutstandingRewardsParams + NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams + NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams + NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams + NewQueryDelegatorParams = keeper.NewQueryDelegatorParams + NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams + DefaultParamspace = keeper.DefaultParamspace RegisterCodec = types.RegisterCodec DefaultGenesisState = types.DefaultGenesisState diff --git a/x/distribution/client/cli/query.go b/x/distribution/client/cli/query.go index 4adcaa130..4ba5b03b1 100644 --- a/x/distribution/client/cli/query.go +++ b/x/distribution/client/cli/query.go @@ -32,22 +32,22 @@ func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { } } -// GetCmdQueryOutstandingRewards implements the query outstanding rewards command. -func GetCmdQueryOutstandingRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { +// GetCmdQueryValidatorOutstandingRewards implements the query validator outstanding rewards command. +func GetCmdQueryValidatorOutstandingRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "outstanding-rewards", + Use: "validator-outstanding-rewards", Args: cobra.NoArgs, - Short: "Query distribution outstanding (un-withdrawn) rewards", + Short: "Query distribution outstanding (un-withdrawn) rewards for a validator and all their delegations", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - route := fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute) + route := fmt.Sprintf("custom/%s/validator_outstanding_rewards", queryRoute) res, err := cliCtx.QueryWithData(route, []byte{}) if err != nil { return err } - var outstandingRewards types.OutstandingRewards + var outstandingRewards types.ValidatorOutstandingRewards cdc.MustUnmarshalJSON(res, &outstandingRewards) return cliCtx.PrintOutput(outstandingRewards) }, diff --git a/x/distribution/client/module_client.go b/x/distribution/client/module_client.go index b9dcb1c82..0acb62a72 100644 --- a/x/distribution/client/module_client.go +++ b/x/distribution/client/module_client.go @@ -27,7 +27,7 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { distQueryCmd.AddCommand(client.GetCommands( distCmds.GetCmdQueryParams(mc.storeKey, mc.cdc), - distCmds.GetCmdQueryOutstandingRewards(mc.storeKey, mc.cdc), + distCmds.GetCmdQueryValidatorOutstandingRewards(mc.storeKey, mc.cdc), distCmds.GetCmdQueryValidatorCommission(mc.storeKey, mc.cdc), distCmds.GetCmdQueryValidatorSlashes(mc.storeKey, mc.cdc), distCmds.GetCmdQueryDelegatorRewards(mc.storeKey, mc.cdc), diff --git a/x/distribution/client/rest/query.go b/x/distribution/client/rest/query.go index 954a097fd..a8a6e1326 100644 --- a/x/distribution/client/rest/query.go +++ b/x/distribution/client/rest/query.go @@ -49,17 +49,18 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, validatorRewardsHandlerFn(cliCtx, cdc, queryRoute), ).Methods("GET") + // Outstanding rewards of a single validator + r.HandleFunc( + "/distribution/validators/{validatorAddr}/outstanding_rewards", + outstandingRewardsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + // Get the current distribution parameter values r.HandleFunc( "/distribution/parameters", paramsHandlerFn(cliCtx, cdc, queryRoute), ).Methods("GET") - // Get the current distribution pool - r.HandleFunc( - "/distribution/outstanding_rewards", - outstandingRewardsHandlerFn(cliCtx, cdc, queryRoute), - ).Methods("GET") } // HTTP request handler to query the total rewards balance from all delegations @@ -118,7 +119,7 @@ func delegatorWithdrawalAddrHandlerFn(cliCtx context.CLIContext, cdc *codec.Code // ValidatorDistInfo defines the properties of // validator distribution information response. type ValidatorDistInfo struct { - OperatorAddress sdk.AccAddress `json:"operator_addr"` + OperatorAddress sdk.AccAddress `json:"operator_address"` SelfBondRewards sdk.DecCoins `json:"self_bond_rewards"` ValidatorCommission types.ValidatorAccumulatedCommission `json:"val_commission"` } @@ -211,7 +212,13 @@ func outstandingRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute), []byte{}) + validatorAddr, ok := checkValidatorAddressVar(w, r) + if !ok { + return + } + + bin := cdc.MustMarshalJSON(distribution.NewQueryValidatorOutstandingRewardsParams(validatorAddr)) + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_outstanding_rewards", queryRoute), bin) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/distribution/client/rest/tx.go b/x/distribution/client/rest/tx.go index b5607be80..80d442585 100644 --- a/x/distribution/client/rest/tx.go +++ b/x/distribution/client/rest/tx.go @@ -56,8 +56,10 @@ type ( ) // Withdraw delegator rewards -func withdrawDelegatorRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext, - queryRoute string) http.HandlerFunc { +func withdrawDelegatorRewardsHandlerFn( + cdc *codec.Codec, cliCtx context.CLIContext, queryRoute string, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { var req withdrawRewardsReq if !rest.ReadRESTReq(w, r, cdc, &req) { @@ -81,12 +83,7 @@ func withdrawDelegatorRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIConte return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, msgs, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) } } @@ -121,12 +118,7 @@ func withdrawDelegationRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLICont return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -156,12 +148,7 @@ func setDelegatorWithdrawalAddrHandlerFn(cdc *codec.Codec, cliCtx context.CLICon return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -192,12 +179,7 @@ func withdrawValidatorRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIConte return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, msgs, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) } } diff --git a/x/distribution/genesis.go b/x/distribution/genesis.go index 9212a5aab..524e2237c 100644 --- a/x/distribution/genesis.go +++ b/x/distribution/genesis.go @@ -13,24 +13,26 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { keeper.SetBonusProposerReward(ctx, data.BonusProposerReward) keeper.SetWithdrawAddrEnabled(ctx, data.WithdrawAddrEnabled) for _, dwi := range data.DelegatorWithdrawInfos { - keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddr, dwi.WithdrawAddr) + keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddress, dwi.WithdrawAddress) } keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer) - keeper.SetOutstandingRewards(ctx, data.OutstandingRewards) + for _, rew := range data.OutstandingRewards { + keeper.SetValidatorOutstandingRewards(ctx, rew.ValidatorAddress, rew.OutstandingRewards) + } for _, acc := range data.ValidatorAccumulatedCommissions { - keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddr, acc.Accumulated) + keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddress, acc.Accumulated) } for _, his := range data.ValidatorHistoricalRewards { - keeper.SetValidatorHistoricalRewards(ctx, his.ValidatorAddr, his.Period, his.Rewards) + keeper.SetValidatorHistoricalRewards(ctx, his.ValidatorAddress, his.Period, his.Rewards) } for _, cur := range data.ValidatorCurrentRewards { - keeper.SetValidatorCurrentRewards(ctx, cur.ValidatorAddr, cur.Rewards) + keeper.SetValidatorCurrentRewards(ctx, cur.ValidatorAddress, cur.Rewards) } for _, del := range data.DelegatorStartingInfos { - keeper.SetDelegatorStartingInfo(ctx, del.ValidatorAddr, del.DelegatorAddr, del.StartingInfo) + keeper.SetDelegatorStartingInfo(ctx, del.ValidatorAddress, del.DelegatorAddress, del.StartingInfo) } for _, evt := range data.ValidatorSlashEvents { - keeper.SetValidatorSlashEvent(ctx, evt.ValidatorAddr, evt.Height, evt.Event) + keeper.SetValidatorSlashEvent(ctx, evt.ValidatorAddress, evt.Height, evt.Event) } } @@ -44,19 +46,28 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { dwi := make([]types.DelegatorWithdrawInfo, 0) keeper.IterateDelegatorWithdrawAddrs(ctx, func(del sdk.AccAddress, addr sdk.AccAddress) (stop bool) { dwi = append(dwi, types.DelegatorWithdrawInfo{ - DelegatorAddr: del, - WithdrawAddr: addr, + DelegatorAddress: del, + WithdrawAddress: addr, }) return false }) pp := keeper.GetPreviousProposerConsAddr(ctx) - outstanding := keeper.GetOutstandingRewards(ctx) + outstanding := make([]types.ValidatorOutstandingRewardsRecord, 0) + keeper.IterateValidatorOutstandingRewards(ctx, + func(addr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { + outstanding = append(outstanding, types.ValidatorOutstandingRewardsRecord{ + ValidatorAddress: addr, + OutstandingRewards: rewards, + }) + return false + }, + ) acc := make([]types.ValidatorAccumulatedCommissionRecord, 0) keeper.IterateValidatorAccumulatedCommissions(ctx, func(addr sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool) { acc = append(acc, types.ValidatorAccumulatedCommissionRecord{ - ValidatorAddr: addr, - Accumulated: commission, + ValidatorAddress: addr, + Accumulated: commission, }) return false }, @@ -65,9 +76,9 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { keeper.IterateValidatorHistoricalRewards(ctx, func(val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) (stop bool) { his = append(his, types.ValidatorHistoricalRewardsRecord{ - ValidatorAddr: val, - Period: period, - Rewards: rewards, + ValidatorAddress: val, + Period: period, + Rewards: rewards, }) return false }, @@ -76,8 +87,8 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { keeper.IterateValidatorCurrentRewards(ctx, func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool) { cur = append(cur, types.ValidatorCurrentRewardsRecord{ - ValidatorAddr: val, - Rewards: rewards, + ValidatorAddress: val, + Rewards: rewards, }) return false }, @@ -86,9 +97,9 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { keeper.IterateDelegatorStartingInfos(ctx, func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool) { dels = append(dels, types.DelegatorStartingInfoRecord{ - ValidatorAddr: val, - DelegatorAddr: del, - StartingInfo: info, + ValidatorAddress: val, + DelegatorAddress: del, + StartingInfo: info, }) return false }, @@ -97,9 +108,9 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { keeper.IterateValidatorSlashEvents(ctx, func(val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) (stop bool) { slashes = append(slashes, types.ValidatorSlashEventRecord{ - ValidatorAddr: val, - Height: height, - Event: event, + ValidatorAddress: val, + Height: height, + Event: event, }) return false }, diff --git a/x/distribution/handler.go b/x/distribution/handler.go index e5e2c5aa8..081baa869 100644 --- a/x/distribution/handler.go +++ b/x/distribution/handler.go @@ -27,13 +27,13 @@ func NewHandler(k keeper.Keeper) sdk.Handler { func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAddress, k keeper.Keeper) sdk.Result { - err := k.SetWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr) + err := k.SetWithdrawAddr(ctx, msg.DelegatorAddress, msg.WithdrawAddress) if err != nil { return err.Result() } tags := sdk.NewTags( - tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.Delegator, []byte(msg.DelegatorAddress.String()), ) return sdk.Result{ Tags: tags, @@ -42,14 +42,14 @@ func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAdd func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result { - err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddress, msg.ValidatorAddress) if err != nil { return err.Result() } tags := sdk.NewTags( - tags.Delegator, []byte(msg.DelegatorAddr.String()), - tags.Validator, []byte(msg.ValidatorAddr.String()), + tags.Delegator, []byte(msg.DelegatorAddress.String()), + tags.Validator, []byte(msg.ValidatorAddress.String()), ) return sdk.Result{ Tags: tags, @@ -58,13 +58,13 @@ func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDele func handleMsgWithdrawValidatorCommission(ctx sdk.Context, msg types.MsgWithdrawValidatorCommission, k keeper.Keeper) sdk.Result { - err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddr) + err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddress) if err != nil { return err.Result() } tags := sdk.NewTags( - tags.Validator, []byte(msg.ValidatorAddr.String()), + tags.Validator, []byte(msg.ValidatorAddress.String()), ) return sdk.Result{ Tags: tags, diff --git a/x/distribution/keeper/alias_functions.go b/x/distribution/keeper/alias_functions.go index 575eafe53..6081cf348 100644 --- a/x/distribution/keeper/alias_functions.go +++ b/x/distribution/keeper/alias_functions.go @@ -5,8 +5,8 @@ import ( ) // get outstanding rewards -func (k Keeper) GetOutstandingRewardsCoins(ctx sdk.Context) sdk.DecCoins { - return k.GetOutstandingRewards(ctx) +func (k Keeper) GetValidatorOutstandingRewardsCoins(ctx sdk.Context, val sdk.ValAddress) sdk.DecCoins { + return k.GetValidatorOutstandingRewards(ctx, val) } // get the community coins diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index f43b018ff..50dcd2e93 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -8,6 +10,7 @@ import ( // allocate fees handles distribution of the collected fees func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower int64, proposer sdk.ConsAddress, votes []abci.VoteInfo) { + logger := ctx.Logger().With("module", "x/distribution") // fetch collected fees & fee pool feesCollectedInt := k.feeCollectionKeeper.GetCollectedFees(ctx) @@ -20,7 +23,7 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in // temporary workaround to keep CanWithdrawInvariant happy // general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634 if totalPower == 0 { - feePool.CommunityPool = feePool.CommunityPool.Plus(feesCollected) + feePool.CommunityPool = feePool.CommunityPool.Add(feesCollected) k.SetFeePool(ctx, feePool) return } @@ -31,13 +34,27 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in // calculate proposer reward baseProposerReward := k.GetBaseProposerReward(ctx) bonusProposerReward := k.GetBonusProposerReward(ctx) - proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(fractionVotes)) - proposerReward := feesCollected.MulDec(proposerMultiplier) + proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(fractionVotes)) + proposerReward := feesCollected.MulDecTruncate(proposerMultiplier) // pay proposer + remaining := feesCollected proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer) - k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward) - remaining := feesCollected.Minus(proposerReward) + if proposerValidator != nil { + k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward) + remaining = remaining.Sub(proposerReward) + } else { + // proposer can be unknown if say, the unbonding period is 1 block, so + // e.g. a validator undelegates at block X, it's removed entirely by + // block X+1's endblock, then X+2 we need to refer to the previous + // proposer for X+1, but we've forgotten about them. + logger.Error(fmt.Sprintf( + "WARNING: Attempt to allocate proposer rewards to unknown proposer %s. "+ + "This should happen only if the proposer unbonded completely within a single block, "+ + "which generally should not happen except in exceptional circumstances (or fuzz testing). "+ + "We recommend you investigate immediately.", + proposer.String())) + } // calculate fraction allocated to validators communityTax := k.GetCommunityTax(ctx) @@ -48,38 +65,40 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in for _, vote := range votes { validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) - // TODO likely we should only reward validators who actually signed the block. + // TODO consider microslashing for missing votes. // ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701 - powerFraction := sdk.NewDec(vote.Validator.Power).Quo(sdk.NewDec(totalPower)) - reward := feesCollected.MulDec(voteMultiplier).MulDec(powerFraction) + powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPower)) + reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction) + reward = reward.Intersect(remaining) k.AllocateTokensToValidator(ctx, validator, reward) - remaining = remaining.Minus(reward) + remaining = remaining.Sub(reward) } // allocate community funding - feePool.CommunityPool = feePool.CommunityPool.Plus(remaining) + feePool.CommunityPool = feePool.CommunityPool.Add(remaining) k.SetFeePool(ctx, feePool) - // update outstanding rewards - outstanding := k.GetOutstandingRewards(ctx) - outstanding = outstanding.Plus(feesCollected.Minus(remaining)) - k.SetOutstandingRewards(ctx, outstanding) - } // allocate tokens to a particular validator, splitting according to commission func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) { + // split tokens between validator and delegators according to commission commission := tokens.MulDec(val.GetCommission()) - shared := tokens.Minus(commission) + shared := tokens.Sub(commission) // update current commission currentCommission := k.GetValidatorAccumulatedCommission(ctx, val.GetOperator()) - currentCommission = currentCommission.Plus(commission) + currentCommission = currentCommission.Add(commission) k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), currentCommission) // update current rewards currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) - currentRewards.Rewards = currentRewards.Rewards.Plus(shared) + currentRewards.Rewards = currentRewards.Rewards.Add(shared) k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards) + + // update outstanding rewards + outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + outstanding = outstanding.Add(tokens) + k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding) } diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go index d2c354a10..8bbab7aef 100644 --- a/x/distribution/keeper/allocation_test.go +++ b/x/distribution/keeper/allocation_test.go @@ -14,9 +14,6 @@ func TestAllocateTokensToValidatorWithCommission(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, @@ -44,9 +41,6 @@ func TestAllocateTokensToManyValidators(t *testing.T) { ctx, _, k, sk, fck := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, @@ -69,7 +63,8 @@ func TestAllocateTokensToManyValidators(t *testing.T) { } // assert initial state: zero outstanding rewards, zero community pool, zero commission, zero current rewards - require.True(t, k.GetOutstandingRewards(ctx).IsZero()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsZero()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsZero()) require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero()) @@ -94,7 +89,8 @@ func TestAllocateTokensToManyValidators(t *testing.T) { k.AllocateTokens(ctx, 200, 200, valConsAddr2, votes) // 98 outstanding rewards (100 less 2 to community pool) - require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(98)}}, k.GetOutstandingRewards(ctx)) + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecWithPrec(465, 1)}}, k.GetValidatorOutstandingRewards(ctx, valOpAddr1)) + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecWithPrec(515, 1)}}, k.GetValidatorOutstandingRewards(ctx, valOpAddr2)) // 2 community pool coins require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(2)}}, k.GetFeePool(ctx).CommunityPool) // 50% commission for first proposer, (0.5 * 93%) * 100 / 2 = 23.25 @@ -106,3 +102,75 @@ func TestAllocateTokensToManyValidators(t *testing.T) { // proposer reward + staking.proportional for second proposer = (5 % + 0.5 * (93%)) * 100 = 51.5 require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecWithPrec(515, 1)}}, k.GetValidatorCurrentRewards(ctx, valOpAddr2).Rewards) } + +func TestAllocateTokensTruncation(t *testing.T) { + communityTax := sdk.NewDec(0) + ctx, _, k, sk, fck := CreateTestInputAdvanced(t, false, 1000000, communityTax) + sh := staking.NewHandler(sk) + + // create validator with 10% commission + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(1, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(110)), staking.Description{}, commission, sdk.OneInt()) + require.True(t, sh(ctx, msg).IsOK()) + + // create second validator with 10% commission + commission = staking.NewCommissionMsg(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(1, 1), sdk.NewDec(0)) + msg = staking.NewMsgCreateValidator(valOpAddr2, valConsPk2, + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission, sdk.OneInt()) + require.True(t, sh(ctx, msg).IsOK()) + + // create third validator with 10% commission + commission = staking.NewCommissionMsg(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(1, 1), sdk.NewDec(0)) + msg = staking.NewMsgCreateValidator(valOpAddr3, valConsPk3, + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission, sdk.OneInt()) + require.True(t, sh(ctx, msg).IsOK()) + + abciValA := abci.Validator{ + Address: valConsPk1.Address(), + Power: 11, + } + abciValB := abci.Validator{ + Address: valConsPk2.Address(), + Power: 10, + } + abciValС := abci.Validator{ + Address: valConsPk3.Address(), + Power: 10, + } + + // assert initial state: zero outstanding rewards, zero community pool, zero commission, zero current rewards + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsZero()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsZero()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr3).IsZero()) + require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero()) + require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) + require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero()) + require.True(t, k.GetValidatorCurrentRewards(ctx, valOpAddr1).Rewards.IsZero()) + require.True(t, k.GetValidatorCurrentRewards(ctx, valOpAddr2).Rewards.IsZero()) + + // allocate tokens as if both had voted and second was proposer + fees := sdk.Coins{ + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(634195840)), + } + fck.SetCollectedFees(fees) + votes := []abci.VoteInfo{ + { + Validator: abciValA, + SignedLastBlock: true, + }, + { + Validator: abciValB, + SignedLastBlock: true, + }, + { + Validator: abciValС, + SignedLastBlock: true, + }, + } + k.AllocateTokens(ctx, 31, 31, valConsAddr2, votes) + + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsValid()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsValid()) + require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr3).IsValid()) +} diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 6738deb92..00e7669bb 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -20,7 +22,7 @@ func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sd // calculate delegation stake in tokens // we don't store directly, so multiply delegation shares * (tokens per share) // note: necessary to truncate so we don't allow withdrawing more rewards than owed - stake := delegation.GetShares().MulTruncate(validator.GetDelegatorShareExRate()) + stake := validator.ShareTokensTruncated(delegation.GetShares()) k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) } @@ -40,7 +42,7 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid // return staking * (ending - starting) starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod) ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod) - difference := ending.CumulativeRewardRatio.Minus(starting.CumulativeRewardRatio) + difference := ending.CumulativeRewardRatio.Sub(starting.CumulativeRewardRatio) if difference.IsAnyNegative() { panic("negative rewards should not be possible") } @@ -53,32 +55,51 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation, endingPeriod uint64) (rewards sdk.DecCoins) { // fetch starting info for delegation startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) + + if startingInfo.Height == uint64(ctx.BlockHeight()) { + // started this height, no rewards yet + return + } + startingPeriod := startingInfo.PreviousPeriod stake := startingInfo.Stake // iterate through slashes and withdraw with calculated staking for sub-intervals // these offsets are dependent on *when* slashes happen - namely, in BeginBlock, after rewards are allocated... - // ... so we don't reduce stake for slashes which happened in the *first* block, because the delegation wouldn't have existed - startingHeight := startingInfo.Height + 1 - // ... or slashes which happened in *this* block, since they would have happened after reward allocation - endingHeight := uint64(ctx.BlockHeight()) - 1 - if endingHeight >= startingHeight { + // slashes which happened in the first block would have been before this delegation existed, + // UNLESS they were slashes of a redelegation to this validator which was itself slashed + // (from a fault committed by the redelegation source validator) earlier in the same BeginBlock + startingHeight := startingInfo.Height + // slashes this block happened after reward allocation, but we have to account for them for the stake sanity check below + endingHeight := uint64(ctx.BlockHeight()) + if endingHeight > startingHeight { k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight, func(height uint64, event types.ValidatorSlashEvent) (stop bool) { endingPeriod := event.ValidatorPeriod - rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) - // note: necessary to truncate so we don't allow withdrawing more rewards than owed - stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction)) - startingPeriod = endingPeriod + if endingPeriod > startingPeriod { + rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) + // note: necessary to truncate so we don't allow withdrawing more rewards than owed + stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction)) + startingPeriod = endingPeriod + } return false }, ) } - // calculate rewards for final period - rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) + // a stake sanity check - recalculated final stake should be less than or equal to current stake + // here we cannot use Equals because stake is truncated when multiplied by slash fractions + // we could only use equals if we had arbitrary-precision rationals + currentStake := val.ShareTokens(del.GetShares()) + if stake.GT(currentStake) { + panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake: %s, %s", + del.GetDelegatorAddr(), stake, currentStake)) + } - return + // calculate rewards for final period + rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) + + return rewards } func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error { @@ -90,7 +111,18 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de // end current period and calculate rewards endingPeriod := k.incrementValidatorPeriod(ctx, val) - rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + rewardsRaw := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + outstanding := k.GetValidatorOutstandingRewards(ctx, del.GetValidatorAddr()) + + // defensive edge case may happen on the very final digits + // of the decCoins due to operation order of the distribution mechanism. + rewards := rewardsRaw.Intersect(outstanding) + if !rewards.IsEqual(rewardsRaw) { + logger := ctx.Logger().With("module", "x/distr") + logger.Info(fmt.Sprintf("missing rewards rounding error, delegator %v"+ + "withdrawing rewards from validator %v, should have received %v, got %v", + val.GetOperator(), del.GetDelegatorAddr(), rewardsRaw, rewards)) + } // decrement reference count of starting period startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) @@ -99,17 +131,18 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de // truncate coins, return remainder to community pool coins, remainder := rewards.TruncateDecimal() - outstanding := k.GetOutstandingRewards(ctx) - k.SetOutstandingRewards(ctx, outstanding.Minus(rewards)) + k.SetValidatorOutstandingRewards(ctx, del.GetValidatorAddr(), outstanding.Sub(rewards)) feePool := k.GetFeePool(ctx) - feePool.CommunityPool = feePool.CommunityPool.Plus(remainder) + feePool.CommunityPool = feePool.CommunityPool.Add(remainder) k.SetFeePool(ctx, feePool) // add coins to user account - withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr()) - if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { - return err + if !coins.IsZero() { + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr()) + if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + return err + } } // remove delegator starting info diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go index 5286a141f..d6eb848e7 100644 --- a/x/distribution/keeper/delegation_test.go +++ b/x/distribution/keeper/delegation_test.go @@ -13,9 +13,6 @@ func TestCalculateRewardsBasic(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, @@ -25,6 +22,9 @@ func TestCalculateRewardsBasic(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -66,9 +66,6 @@ func TestCalculateRewardsAfterSlash(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) valPower := int64(100) @@ -81,6 +78,9 @@ func TestCalculateRewardsAfterSlash(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -108,7 +108,7 @@ func TestCalculateRewardsAfterSlash(t *testing.T) { // allocate some rewards initial := sdk.TokensFromTendermintPower(10) - tokens := sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial)}} + tokens := sdk.DecCoins{{sdk.DefaultBondDenom, initial.ToDec()}} k.AllocateTokensToValidator(ctx, val, tokens) // end period @@ -118,10 +118,10 @@ func TestCalculateRewardsAfterSlash(t *testing.T) { rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) // rewards should be half the tokens - require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial.DivRaw(2))}}, rewards) + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, initial.QuoRaw(2).ToDec()}}, rewards) // commission should be the other half - require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial.DivRaw(2))}}, + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, initial.QuoRaw(2).ToDec()}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) } @@ -129,9 +129,6 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission power := int64(100) valTokens := sdk.TokensFromTendermintPower(power) @@ -143,6 +140,9 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -170,7 +170,7 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) { // allocate some rewards initial := sdk.TokensFromTendermintPower(10) - tokens := sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial)}} + tokens := sdk.DecCoins{{sdk.DefaultBondDenom, initial.ToDec()}} k.AllocateTokensToValidator(ctx, val, tokens) // slash the validator by 50% again @@ -192,10 +192,10 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) { rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) // rewards should be half the tokens - require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial)}}, rewards) + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, initial.ToDec()}}, rewards) // commission should be the other half - require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecFromInt(initial)}}, + require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, initial.ToDec()}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) } @@ -203,9 +203,6 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, @@ -215,6 +212,9 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -235,6 +235,9 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) { // end block staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // allocate some more rewards k.AllocateTokensToValidator(ctx, val, tokens) @@ -263,9 +266,6 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) { ctx, ak, k, sk, _ := CreateTestInputDefault(t, false, balancePower) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission power := int64(100) valTokens := sdk.TokensFromTendermintPower(power) @@ -287,6 +287,9 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) @@ -294,7 +297,6 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) { initial := sdk.TokensFromTendermintPower(10) tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, initial)} - k.SetOutstandingRewards(ctx, tokens) k.AllocateTokensToValidator(ctx, val, tokens) // historical count should be 2 (initial + latest for delegation) @@ -307,7 +309,7 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) { require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) // assert correct balance - exp := balanceTokens.Sub(valTokens).Add(initial.DivRaw(2)) + exp := balanceTokens.Sub(valTokens).Add(initial.QuoRaw(2)) require.Equal(t, sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, exp)}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins(), @@ -328,9 +330,6 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission power := int64(100) valTokens := sdk.TokensFromTendermintPower(power) @@ -342,6 +341,9 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -359,7 +361,7 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) { ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) // allocate some rewards - initial := sdk.NewDecFromInt(sdk.TokensFromTendermintPower(10)) + initial := sdk.TokensFromTendermintPower(10).ToDec() tokens := sdk.DecCoins{{sdk.DefaultBondDenom, initial}} k.AllocateTokensToValidator(ctx, val, tokens) @@ -395,9 +397,6 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) { ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) sh := staking.NewHandler(sk) - // initialize state - k.SetOutstandingRewards(ctx, sdk.DecCoins{}) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) power := int64(100) @@ -409,12 +408,15 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) // allocate some rewards - initial := sdk.NewDecFromInt(sdk.TokensFromTendermintPower(30)) + initial := sdk.TokensFromTendermintPower(30).ToDec() tokens := sdk.DecCoins{{sdk.DefaultBondDenom, initial}} k.AllocateTokensToValidator(ctx, val, tokens) @@ -433,6 +435,9 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) { // end block staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // allocate some more rewards k.AllocateTokensToValidator(ctx, val, tokens) @@ -471,9 +476,6 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { totalRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial*2))} tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial))} - // initialize state - k.SetOutstandingRewards(ctx, totalRewards) - // create validator with 50% commission commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, @@ -483,6 +485,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { // end block to bond validator staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // fetch validator and delegation val := sk.Validator(ctx, valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) @@ -507,6 +512,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { // end block staking.EndBlocker(ctx, sk) + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // allocate some more rewards k.AllocateTokensToValidator(ctx, val, tokens) @@ -540,8 +548,10 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { // commission should be zero require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) - totalRewards = k.GetOutstandingRewards(ctx).Plus(tokens) - k.SetOutstandingRewards(ctx, totalRewards) + totalRewards = totalRewards.Add(tokens) + + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // allocate some more rewards k.AllocateTokensToValidator(ctx, val, tokens) @@ -567,8 +577,10 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { // commission should be half initial require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) - totalRewards = k.GetOutstandingRewards(ctx).Plus(tokens) - k.SetOutstandingRewards(ctx, totalRewards) + totalRewards = k.GetValidatorOutstandingRewards(ctx, valOpAddr1).Add(tokens) + + // next block + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // allocate some more rewards k.AllocateTokensToValidator(ctx, val, tokens) diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 49d09b317..1ce7a1fd6 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -22,28 +22,44 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { } func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + + // fetch outstanding + outstanding := h.k.GetValidatorOutstandingRewards(ctx, valAddr) + // force-withdraw commission commission := h.k.GetValidatorAccumulatedCommission(ctx, valAddr) if !commission.IsZero() { + // subtract from outstanding + outstanding = outstanding.Sub(commission) + + // split into integral & remainder coins, remainder := commission.TruncateDecimal() // remainder to community pool feePool := h.k.GetFeePool(ctx) - feePool.CommunityPool = feePool.CommunityPool.Plus(remainder) + feePool.CommunityPool = feePool.CommunityPool.Add(remainder) h.k.SetFeePool(ctx, feePool) - // update outstanding - outstanding := h.k.GetOutstandingRewards(ctx) - h.k.SetOutstandingRewards(ctx, outstanding.Minus(commission)) - // add to validator account - accAddr := sdk.AccAddress(valAddr) - withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr) + if !coins.IsZero() { - if _, _, err := h.k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { - panic(err) + accAddr := sdk.AccAddress(valAddr) + withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr) + + if _, _, err := h.k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + panic(err) + } } } + + // add outstanding to community pool + feePool := h.k.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Add(outstanding) + h.k.SetFeePool(ctx, feePool) + + // delete outstanding + h.k.DeleteValidatorOutstandingRewards(ctx, valAddr) + // remove commission record h.k.DeleteValidatorAccumulatedCommission(ctx, valAddr) diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index 1d38ebcc7..0652a5166 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -84,14 +84,16 @@ func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddr k.SetValidatorAccumulatedCommission(ctx, valAddr, remainder) // update outstanding - outstanding := k.GetOutstandingRewards(ctx) - k.SetOutstandingRewards(ctx, outstanding.Minus(sdk.NewDecCoins(coins))) + outstanding := k.GetValidatorOutstandingRewards(ctx, valAddr) + k.SetValidatorOutstandingRewards(ctx, valAddr, outstanding.Sub(sdk.NewDecCoins(coins))) - accAddr := sdk.AccAddress(valAddr) - withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr) + if !coins.IsZero() { + accAddr := sdk.AccAddress(valAddr) + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr) - if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { - return err + if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + return err + } } return nil diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go index fc6f7cc0a..59471a132 100644 --- a/x/distribution/keeper/keeper_test.go +++ b/x/distribution/keeper/keeper_test.go @@ -30,9 +30,6 @@ func TestWithdrawValidatorCommission(t *testing.T) { sdk.NewDecCoinFromDec("stake", sdk.NewDec(3).Quo(sdk.NewDec(2))), } - // set zero outstanding rewards - keeper.SetOutstandingRewards(ctx, valCommission) - // check initial balance balance := ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins() expTokens := sdk.TokensFromTendermintPower(1000) @@ -40,6 +37,9 @@ func TestWithdrawValidatorCommission(t *testing.T) { sdk.NewCoin("stake", sdk.TokensFromTendermintPower(1000)), }, balance) + // set outstanding rewards + keeper.SetValidatorOutstandingRewards(ctx, valOpAddr3, valCommission) + // set commission keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr3, valCommission) diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go index 1b8437b4c..068e05b85 100644 --- a/x/distribution/keeper/key.go +++ b/x/distribution/keeper/key.go @@ -13,9 +13,9 @@ const ( // keys var ( - FeePoolKey = []byte{0x00} // key for global distribution state - ProposerKey = []byte{0x01} // key for the proposer operator address - OutstandingRewardsKey = []byte{0x02} // key for outstanding rewards + FeePoolKey = []byte{0x00} // key for global distribution state + ProposerKey = []byte{0x01} // key for the proposer operator address + ValidatorOutstandingRewardsPrefix = []byte{0x02} // key for outstanding rewards DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address DelegatorStartingInfoPrefix = []byte{0x04} // key for delegator starting info @@ -30,6 +30,15 @@ var ( ParamStoreKeyWithdrawAddrEnabled = []byte("withdrawaddrenabled") ) +// gets an address from a validator's outstanding rewards key +func GetValidatorOutstandingRewardsAddress(key []byte) (valAddr sdk.ValAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.ValAddress(addr) +} + // gets an address from a delegator's withdraw info key func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) { addr := key[1:] @@ -102,6 +111,11 @@ func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, he return } +// gets the outstanding rewards key for a validator +func GetValidatorOutstandingRewardsKey(valAddr sdk.ValAddress) []byte { + return append(ValidatorOutstandingRewardsPrefix, valAddr.Bytes()...) +} + // gets the key for a delegator's withdraw addr func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...) diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go index d8948f2fb..348c8b9a0 100644 --- a/x/distribution/keeper/querier.go +++ b/x/distribution/keeper/querier.go @@ -12,14 +12,14 @@ import ( // nolint const ( - QueryParams = "params" - QueryOutstandingRewards = "outstanding_rewards" - QueryValidatorCommission = "validator_commission" - QueryValidatorSlashes = "validator_slashes" - QueryDelegationRewards = "delegation_rewards" - QueryDelegatorTotalRewards = "delegator_total_rewards" - QueryDelegatorValidators = "delegator_validators" - QueryWithdrawAddr = "withdraw_addr" + QueryParams = "params" + QueryValidatorOutstandingRewards = "validator_outstanding_rewards" + QueryValidatorCommission = "validator_commission" + QueryValidatorSlashes = "validator_slashes" + QueryDelegationRewards = "delegation_rewards" + QueryDelegatorTotalRewards = "delegator_total_rewards" + QueryDelegatorValidators = "delegator_validators" + QueryWithdrawAddr = "withdraw_addr" ParamCommunityTax = "community_tax" ParamBaseProposerReward = "base_proposer_reward" @@ -33,8 +33,8 @@ func NewQuerier(k Keeper) sdk.Querier { case QueryParams: return queryParams(ctx, path[1:], req, k) - case QueryOutstandingRewards: - return queryOutstandingRewards(ctx, path[1:], req, k) + case QueryValidatorOutstandingRewards: + return queryValidatorOutstandingRewards(ctx, path[1:], req, k) case QueryValidatorCommission: return queryValidatorCommission(ctx, path[1:], req, k) @@ -91,8 +91,25 @@ func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper } } -func queryOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - bz, err := codec.MarshalJSONIndent(k.cdc, k.GetOutstandingRewards(ctx)) +// params for query 'custom/distr/validator_outstanding_rewards' +type QueryValidatorOutstandingRewardsParams struct { + ValidatorAddress sdk.ValAddress `json:"validator_address"` +} + +// creates a new instance of QueryValidatorOutstandingRewardsParams +func NewQueryValidatorOutstandingRewardsParams(validatorAddr sdk.ValAddress) QueryValidatorOutstandingRewardsParams { + return QueryValidatorOutstandingRewardsParams{ + ValidatorAddress: validatorAddr, + } +} + +func queryValidatorOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryValidatorOutstandingRewardsParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetValidatorOutstandingRewards(ctx, params.ValidatorAddress)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } @@ -101,13 +118,13 @@ func queryOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQue // params for query 'custom/distr/validator_commission' type QueryValidatorCommissionParams struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` } // creates a new instance of QueryValidatorCommissionParams func NewQueryValidatorCommissionParams(validatorAddr sdk.ValAddress) QueryValidatorCommissionParams { return QueryValidatorCommissionParams{ - ValidatorAddr: validatorAddr, + ValidatorAddress: validatorAddr, } } @@ -117,7 +134,7 @@ func queryValidatorCommission(ctx sdk.Context, path []string, req abci.RequestQu if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - commission := k.GetValidatorAccumulatedCommission(ctx, params.ValidatorAddr) + commission := k.GetValidatorAccumulatedCommission(ctx, params.ValidatorAddress) bz, err := codec.MarshalJSONIndent(k.cdc, commission) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) @@ -127,17 +144,17 @@ func queryValidatorCommission(ctx sdk.Context, path []string, req abci.RequestQu // params for query 'custom/distr/validator_slashes' type QueryValidatorSlashesParams struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - StartingHeight uint64 `json:"starting_height"` - EndingHeight uint64 `json:"ending_height"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + StartingHeight uint64 `json:"starting_height"` + EndingHeight uint64 `json:"ending_height"` } // creates a new instance of QueryValidatorSlashesParams func NewQueryValidatorSlashesParams(validatorAddr sdk.ValAddress, startingHeight uint64, endingHeight uint64) QueryValidatorSlashesParams { return QueryValidatorSlashesParams{ - ValidatorAddr: validatorAddr, - StartingHeight: startingHeight, - EndingHeight: endingHeight, + ValidatorAddress: validatorAddr, + StartingHeight: startingHeight, + EndingHeight: endingHeight, } } @@ -148,7 +165,7 @@ func queryValidatorSlashes(ctx sdk.Context, path []string, req abci.RequestQuery return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } events := make([]types.ValidatorSlashEvent, 0) - k.IterateValidatorSlashEventsBetween(ctx, params.ValidatorAddr, params.StartingHeight, params.EndingHeight, + k.IterateValidatorSlashEventsBetween(ctx, params.ValidatorAddress, params.StartingHeight, params.EndingHeight, func(height uint64, event types.ValidatorSlashEvent) (stop bool) { events = append(events, event) return false @@ -163,15 +180,15 @@ func queryValidatorSlashes(ctx sdk.Context, path []string, req abci.RequestQuery // params for query 'custom/distr/delegation_rewards' type QueryDelegationRewardsParams struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` } // creates a new instance of QueryDelegationRewardsParams func NewQueryDelegationRewardsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryDelegationRewardsParams { return QueryDelegationRewardsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, + DelegatorAddress: delegatorAddr, + ValidatorAddress: validatorAddr, } } @@ -185,8 +202,8 @@ func queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, // cache-wrap context as to not persist state changes during querying ctx, _ = ctx.CacheContext() - val := k.stakingKeeper.Validator(ctx, params.ValidatorAddr) - del := k.stakingKeeper.Delegation(ctx, params.DelegatorAddr, params.ValidatorAddr) + val := k.stakingKeeper.Validator(ctx, params.ValidatorAddress) + del := k.stakingKeeper.Delegation(ctx, params.DelegatorAddress, params.ValidatorAddress) endingPeriod := k.incrementValidatorPeriod(ctx, val) rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) @@ -200,13 +217,13 @@ func queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, // params for query 'custom/distr/delegator_total_rewards' and 'custom/distr/delegator_validators' type QueryDelegatorParams struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` } // creates a new instance of QueryDelegationRewardsParams func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { return QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, + DelegatorAddress: delegatorAddr, } } @@ -223,13 +240,13 @@ func queryDelegatorTotalRewards(ctx sdk.Context, _ []string, req abci.RequestQue var totalRewards sdk.DecCoins k.stakingKeeper.IterateDelegations( - ctx, params.DelegatorAddr, + ctx, params.DelegatorAddress, func(_ int64, del sdk.Delegation) (stop bool) { val := k.stakingKeeper.Validator(ctx, del.GetValidatorAddr()) endingPeriod := k.incrementValidatorPeriod(ctx, val) rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) - totalRewards = totalRewards.Plus(rewards) + totalRewards = totalRewards.Add(rewards) return false }, ) @@ -255,7 +272,7 @@ func queryDelegatorValidators(ctx sdk.Context, _ []string, req abci.RequestQuery var validators []sdk.ValAddress k.stakingKeeper.IterateDelegations( - ctx, params.DelegatorAddr, + ctx, params.DelegatorAddress, func(_ int64, del sdk.Delegation) (stop bool) { validators = append(validators[:], del.GetValidatorAddr()) return false @@ -271,12 +288,12 @@ func queryDelegatorValidators(ctx sdk.Context, _ []string, req abci.RequestQuery // params for query 'custom/distr/withdraw_addr' type QueryDelegatorWithdrawAddrParams struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` } // NewQueryDelegatorWithdrawAddrParams creates a new instance of QueryDelegatorWithdrawAddrParams. func NewQueryDelegatorWithdrawAddrParams(delegatorAddr sdk.AccAddress) QueryDelegatorWithdrawAddrParams { - return QueryDelegatorWithdrawAddrParams{DelegatorAddr: delegatorAddr} + return QueryDelegatorWithdrawAddrParams{DelegatorAddress: delegatorAddr} } func queryDelegatorWithdrawAddress(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { @@ -288,7 +305,7 @@ func queryDelegatorWithdrawAddress(ctx sdk.Context, _ []string, req abci.Request // cache-wrap context as to not persist state changes during querying ctx, _ = ctx.CacheContext() - withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, params.DelegatorAddr) + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, params.DelegatorAddress) bz, err := codec.MarshalJSONIndent(k.cdc, withdrawAddr) if err != nil { diff --git a/x/distribution/keeper/querier_test.go b/x/distribution/keeper/querier_test.go index 874b85034..9593f1bff 100644 --- a/x/distribution/keeper/querier_test.go +++ b/x/distribution/keeper/querier_test.go @@ -57,13 +57,13 @@ func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier s return } -func getQueriedOutstandingRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (outstandingRewards sdk.DecCoins) { +func getQueriedValidatorOutstandingRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, validatorAddr sdk.ValAddress) (outstandingRewards sdk.DecCoins) { query := abci.RequestQuery{ - Path: strings.Join([]string{custom, types.QuerierRoute, QueryOutstandingRewards}, "/"), - Data: []byte{}, + Path: strings.Join([]string{custom, types.QuerierRoute, QueryValidatorOutstandingRewards}, "/"), + Data: cdc.MustMarshalJSON(NewQueryValidatorOutstandingRewardsParams(validatorAddr)), } - bz, err := querier(ctx, []string{QueryOutstandingRewards}, query) + bz, err := querier(ctx, []string{QueryValidatorOutstandingRewards}, query) require.Nil(t, err) require.Nil(t, cdc.UnmarshalJSON(bz, &outstandingRewards)) @@ -131,8 +131,8 @@ func TestQueries(t *testing.T) { // test outstanding rewards query outstandingRewards := sdk.DecCoins{{"mytoken", sdk.NewDec(3)}, {"myothertoken", sdk.NewDecWithPrec(3, 7)}} - keeper.SetOutstandingRewards(ctx, outstandingRewards) - retOutstandingRewards := getQueriedOutstandingRewards(t, ctx, cdc, querier) + keeper.SetValidatorOutstandingRewards(ctx, valOpAddr1, outstandingRewards) + retOutstandingRewards := getQueriedValidatorOutstandingRewards(t, ctx, cdc, querier, valOpAddr1) require.Equal(t, outstandingRewards, retOutstandingRewards) // test validator commission query @@ -155,7 +155,6 @@ func TestQueries(t *testing.T) { // test delegation rewards query sh := staking.NewHandler(sk) - keeper.SetOutstandingRewards(ctx, sdk.DecCoins{}) comm := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, comm, sdk.OneInt()) @@ -165,6 +164,7 @@ func TestQueries(t *testing.T) { rewards := getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) require.True(t, rewards.IsZero()) initial := int64(10) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) tokens := sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial)}} keeper.AllocateTokensToValidator(ctx, val, tokens) rewards = getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) diff --git a/x/distribution/keeper/store.go b/x/distribution/keeper/store.go index ef1f7c783..1326c89d4 100644 --- a/x/distribution/keeper/store.go +++ b/x/distribution/keeper/store.go @@ -263,19 +263,40 @@ func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler } } -// get outstanding rewards -func (k Keeper) GetOutstandingRewards(ctx sdk.Context) (rewards types.OutstandingRewards) { +// get validator outstanding rewards +func (k Keeper) GetValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorOutstandingRewards) { store := ctx.KVStore(k.storeKey) - b := store.Get(OutstandingRewardsKey) + b := store.Get(GetValidatorOutstandingRewardsKey(val)) k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards) return } -// set outstanding rewards -func (k Keeper) SetOutstandingRewards(ctx sdk.Context, rewards types.OutstandingRewards) { +// set validator outstanding rewards +func (k Keeper) SetValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress, rewards types.ValidatorOutstandingRewards) { store := ctx.KVStore(k.storeKey) b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards) - store.Set(OutstandingRewardsKey, b) + store.Set(GetValidatorOutstandingRewardsKey(val), b) +} + +// delete validator outstanding rewards +func (k Keeper) DeleteValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorOutstandingRewardsKey(val)) +} + +// iterate validator outstanding rewards +func (k Keeper) IterateValidatorOutstandingRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorOutstandingRewardsPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var rewards types.ValidatorOutstandingRewards + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards) + addr := GetValidatorOutstandingRewardsAddress(iter.Key()) + if handler(addr, rewards) { + break + } + } } // get slash event for height diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 05326ff12..18c6cc6e8 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -16,6 +16,9 @@ func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) { // set accumulated commission k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), types.InitialValidatorAccumulatedCommission()) + + // set outstanding rewards + k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), sdk.DecCoins{}) } // increment validator period, returning the period just ended @@ -30,16 +33,16 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin // can't calculate ratio for zero-token validators // ergo we instead add to the community pool feePool := k.GetFeePool(ctx) - outstanding := k.GetOutstandingRewards(ctx) - feePool.CommunityPool = feePool.CommunityPool.Plus(rewards.Rewards) - outstanding = outstanding.Minus(rewards.Rewards) + outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + feePool.CommunityPool = feePool.CommunityPool.Add(rewards.Rewards) + outstanding = outstanding.Sub(rewards.Rewards) k.SetFeePool(ctx, feePool) - k.SetOutstandingRewards(ctx, outstanding) + k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding) current = sdk.DecCoins{} } else { // note: necessary to truncate so we don't allow withdrawing more rewards than owed - current = rewards.Rewards.QuoDecTruncate(sdk.NewDecFromInt(val.GetTokens())) + current = rewards.Rewards.QuoDecTruncate(val.GetTokens().ToDec()) } // fetch historical rewards for last period @@ -49,7 +52,7 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin k.decrementReferenceCount(ctx, val.GetOperator(), rewards.Period-1) // set new historical rewards with reference count of 1 - k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, types.NewValidatorHistoricalRewards(historical.Plus(current), 1)) + k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, types.NewValidatorHistoricalRewards(historical.Add(current), 1)) // set current rewards, incrementing period by 1 k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, rewards.Period+1)) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index f72241065..853e9494b 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -30,11 +30,23 @@ func AllInvariants(d distr.Keeper, stk types.StakingKeeper) sdk.Invariant { // NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant { return func(ctx sdk.Context) error { - outstanding := k.GetOutstandingRewards(ctx) + + var outstanding sdk.DecCoins + + k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { + outstanding = rewards + if outstanding.IsAnyNegative() { + return true + } + return false + }) + if outstanding.IsAnyNegative() { return fmt.Errorf("negative outstanding coins: %v", outstanding) } + return nil + } } @@ -45,19 +57,34 @@ func CanWithdrawInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant // cache, we don't want to write changes ctx, _ = ctx.CacheContext() - // iterate over all bonded validators, withdraw commission - sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { - _ = k.WithdrawValidatorCommission(ctx, val.GetOperator()) - return false - }) + var remaining sdk.DecCoins - // iterate over all current delegations, withdraw rewards - dels := sk.GetAllSDKDelegations(ctx) - for _, delegation := range dels { - _ = k.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr()) + valDelegationAddrs := make(map[string][]sdk.AccAddress) + for _, del := range sk.GetAllSDKDelegations(ctx) { + valAddr := del.GetValidatorAddr().String() + valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], del.GetDelegatorAddr()) } - remaining := k.GetOutstandingRewards(ctx) + // iterate over all validators + sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + _ = k.WithdrawValidatorCommission(ctx, val.GetOperator()) + + delegationAddrs, ok := valDelegationAddrs[val.GetOperator().String()] + if ok { + for _, delAddr := range delegationAddrs { + if err := k.WithdrawDelegationRewards(ctx, delAddr, val.GetOperator()); err != nil { + panic(err) + } + } + } + + remaining = k.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) { + return true + } + + return false + }) if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) { return fmt.Errorf("negative remaining coins: %v", remaining) diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go index bb34ab47c..caf9905e1 100644 --- a/x/distribution/types/fee_pool.go +++ b/x/distribution/types/fee_pool.go @@ -27,8 +27,3 @@ func (f FeePool) ValidateGenesis() error { return nil } - -// outstanding (un-withdrawn) rewards for everyone -// excludes the community pool -// inexpensive to track, allows simple sanity checks -type OutstandingRewards = sdk.DecCoins diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go index 2ae3bd6c6..e87d8a33f 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -9,41 +9,47 @@ import ( // the address for where distributions rewards are withdrawn to by default // this struct is only used at genesis to feed in default withdraw addresses type DelegatorWithdrawInfo struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - WithdrawAddr sdk.AccAddress `json:"withdraw_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + WithdrawAddress sdk.AccAddress `json:"withdraw_address"` +} + +// used for import/export via genesis json +type ValidatorOutstandingRewardsRecord struct { + ValidatorAddress sdk.ValAddress `json:"validator_address"` + OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"` } // used for import / export via genesis json type ValidatorAccumulatedCommissionRecord struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Accumulated ValidatorAccumulatedCommission `json:"accumulated"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Accumulated ValidatorAccumulatedCommission `json:"accumulated"` } // used for import / export via genesis json type ValidatorHistoricalRewardsRecord struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Period uint64 `json:"period"` - Rewards ValidatorHistoricalRewards `json:"rewards"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Period uint64 `json:"period"` + Rewards ValidatorHistoricalRewards `json:"rewards"` } // used for import / export via genesis json type ValidatorCurrentRewardsRecord struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Rewards ValidatorCurrentRewards `json:"rewards"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Rewards ValidatorCurrentRewards `json:"rewards"` } // used for import / export via genesis json type DelegatorStartingInfoRecord struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - StartingInfo DelegatorStartingInfo `json:"starting_info"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + StartingInfo DelegatorStartingInfo `json:"starting_info"` } // used for import / export via genesis json type ValidatorSlashEventRecord struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Height uint64 `json:"height"` - Event ValidatorSlashEvent `json:"validator_slash_event"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Height uint64 `json:"height"` + Event ValidatorSlashEvent `json:"validator_slash_event"` } // GenesisState - all distribution state that must be provided at genesis @@ -55,7 +61,7 @@ type GenesisState struct { WithdrawAddrEnabled bool `json:"withdraw_addr_enabled"` DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` PreviousProposer sdk.ConsAddress `json:"previous_proposer"` - OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"` + OutstandingRewards []ValidatorOutstandingRewardsRecord `json:"outstanding_rewards"` ValidatorAccumulatedCommissions []ValidatorAccumulatedCommissionRecord `json:"validator_accumulated_commissions"` ValidatorHistoricalRewards []ValidatorHistoricalRewardsRecord `json:"validator_historical_rewards"` ValidatorCurrentRewards []ValidatorCurrentRewardsRecord `json:"validator_current_rewards"` @@ -64,7 +70,7 @@ type GenesisState struct { } func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, - withdrawAddrEnabled bool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r OutstandingRewards, + withdrawAddrEnabled bool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r []ValidatorOutstandingRewardsRecord, acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord, cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord, slashes []ValidatorSlashEventRecord) GenesisState { @@ -96,7 +102,7 @@ func DefaultGenesisState() GenesisState { WithdrawAddrEnabled: true, DelegatorWithdrawInfos: []DelegatorWithdrawInfo{}, PreviousProposer: nil, - OutstandingRewards: sdk.DecCoins{}, + OutstandingRewards: []ValidatorOutstandingRewardsRecord{}, ValidatorAccumulatedCommissions: []ValidatorAccumulatedCommissionRecord{}, ValidatorHistoricalRewards: []ValidatorHistoricalRewardsRecord{}, ValidatorCurrentRewards: []ValidatorCurrentRewardsRecord{}, diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go index a6fb7d518..30f170f03 100644 --- a/x/distribution/types/msg.go +++ b/x/distribution/types/msg.go @@ -13,14 +13,14 @@ var _, _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorReward{}, & // msg struct for changing the withdraw address for a delegator (or validator self-delegation) type MsgSetWithdrawAddress struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - WithdrawAddr sdk.AccAddress `json:"withdraw_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + WithdrawAddress sdk.AccAddress `json:"withdraw_address"` } func NewMsgSetWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgSetWithdrawAddress { return MsgSetWithdrawAddress{ - DelegatorAddr: delAddr, - WithdrawAddr: withdrawAddr, + DelegatorAddress: delAddr, + WithdrawAddress: withdrawAddr, } } @@ -29,7 +29,7 @@ func (msg MsgSetWithdrawAddress) Type() string { return "set_withdraw_address" // Return address that must sign over msg.GetSignBytes() func (msg MsgSetWithdrawAddress) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} + return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddress)} } // get the bytes for the message signer to sign on @@ -40,10 +40,10 @@ func (msg MsgSetWithdrawAddress) GetSignBytes() []byte { // quick validity check func (msg MsgSetWithdrawAddress) ValidateBasic() sdk.Error { - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.WithdrawAddr.Empty() { + if msg.WithdrawAddress.Empty() { return ErrNilWithdrawAddr(DefaultCodespace) } return nil @@ -51,23 +51,23 @@ func (msg MsgSetWithdrawAddress) ValidateBasic() sdk.Error { // msg struct for delegation withdraw from a single validator type MsgWithdrawDelegatorReward struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` } func NewMsgWithdrawDelegatorReward(delAddr sdk.AccAddress, valAddr sdk.ValAddress) MsgWithdrawDelegatorReward { return MsgWithdrawDelegatorReward{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, } } func (msg MsgWithdrawDelegatorReward) Route() string { return MsgRoute } -func (msg MsgWithdrawDelegatorReward) Type() string { return "withdraw_delegation_reward" } +func (msg MsgWithdrawDelegatorReward) Type() string { return "withdraw_delegator_reward" } // Return address that must sign over msg.GetSignBytes() func (msg MsgWithdrawDelegatorReward) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} + return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddress)} } // get the bytes for the message signer to sign on @@ -78,10 +78,10 @@ func (msg MsgWithdrawDelegatorReward) GetSignBytes() []byte { // quick validity check func (msg MsgWithdrawDelegatorReward) ValidateBasic() sdk.Error { - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } return nil @@ -89,12 +89,12 @@ func (msg MsgWithdrawDelegatorReward) ValidateBasic() sdk.Error { // msg struct for validator withdraw type MsgWithdrawValidatorCommission struct { - ValidatorAddr sdk.ValAddress `json:"validator_addr"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` } func NewMsgWithdrawValidatorCommission(valAddr sdk.ValAddress) MsgWithdrawValidatorCommission { return MsgWithdrawValidatorCommission{ - ValidatorAddr: valAddr, + ValidatorAddress: valAddr, } } @@ -103,7 +103,7 @@ func (msg MsgWithdrawValidatorCommission) Type() string { return "withdraw_vali // Return address that must sign over msg.GetSignBytes() func (msg MsgWithdrawValidatorCommission) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr.Bytes())} + return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddress.Bytes())} } // get the bytes for the message signer to sign on @@ -114,7 +114,7 @@ func (msg MsgWithdrawValidatorCommission) GetSignBytes() []byte { // quick validity check func (msg MsgWithdrawValidatorCommission) ValidateBasic() sdk.Error { - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } return nil diff --git a/x/distribution/types/validator.go b/x/distribution/types/validator.go index 0bde7fc07..a7fc0dd2a 100644 --- a/x/distribution/types/validator.go +++ b/x/distribution/types/validator.go @@ -91,3 +91,7 @@ func (vs ValidatorSlashEvents) String() string { } return strings.TrimSpace(out) } + +// outstanding (un-withdrawn) rewards for a validator +// inexpensive to track, allows simple sanity checks +type ValidatorOutstandingRewards = sdk.DecCoins diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 48d1fe782..5bfd676a9 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -101,12 +101,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -143,12 +138,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -191,12 +181,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/x/gov/depositsvotes.go b/x/gov/depositsvotes.go index 059d0a6bc..9a97401f7 100644 --- a/x/gov/depositsvotes.go +++ b/x/gov/depositsvotes.go @@ -135,7 +135,7 @@ func (vo *VoteOption) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } bz2, err := VoteOptionFromString(s) diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 957381d59..6426f30df 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -58,10 +58,9 @@ func DefaultGenesisState() GenesisState { VotingPeriod: DefaultPeriod, }, TallyParams: TallyParams{ - Quorum: sdk.NewDecWithPrec(334, 3), - Threshold: sdk.NewDecWithPrec(5, 1), - Veto: sdk.NewDecWithPrec(334, 3), - GovernancePenalty: sdk.NewDecWithPrec(1, 2), + Quorum: sdk.NewDecWithPrec(334, 3), + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), }, } } @@ -93,12 +92,6 @@ func ValidateGenesis(data GenesisState) error { veto.String()) } - govPenalty := data.TallyParams.GovernancePenalty - if govPenalty.IsNegative() || govPenalty.GT(sdk.OneDec()) { - return fmt.Errorf("Governance vote veto threshold should be positive and less or equal to one, is %s", - govPenalty.String()) - } - if data.DepositParams.MaxDepositPeriod > data.VotingParams.VotingPeriod { return fmt.Errorf("Governance deposit period should be less than or equal to the voting period (%ds), is %ds", data.VotingParams.VotingPeriod, data.DepositParams.MaxDepositPeriod) diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 9616791dd..2ea507977 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -387,7 +387,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd } // Update proposal - proposal.SetTotalDeposit(proposal.GetTotalDeposit().Plus(depositAmount)) + proposal.SetTotalDeposit(proposal.GetTotalDeposit().Add(depositAmount)) keeper.SetProposal(ctx, proposal) // Check if deposit has provided sufficient total funds to transition the proposal into the voting period @@ -403,7 +403,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd newDeposit := Deposit{depositorAddr, proposalID, depositAmount} keeper.setDeposit(ctx, proposalID, depositorAddr, newDeposit) } else { - currDeposit.Amount = currDeposit.Amount.Plus(depositAmount) + currDeposit.Amount = currDeposit.Amount.Add(depositAmount) keeper.setDeposit(ctx, proposalID, depositorAddr, currDeposit) } diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 452450553..44861877d 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -93,7 +93,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, fourSteak, deposit.Amount) require.Equal(t, addrs[0], deposit.Depositor) require.Equal(t, fourSteak, keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) - require.Equal(t, addr0Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[0])) + require.Equal(t, addr0Initial.Sub(fourSteak), keeper.ck.GetCoins(ctx, addrs[0])) // Check a second deposit from same address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], fiveSteak) @@ -101,10 +101,10 @@ func TestDeposits(t *testing.T) { require.False(t, votingStarted) deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0]) require.True(t, found) - require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) + require.Equal(t, fourSteak.Add(fiveSteak), deposit.Amount) require.Equal(t, addrs[0], deposit.Depositor) - require.Equal(t, fourSteak.Plus(fiveSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) - require.Equal(t, addr0Initial.Minus(fourSteak).Minus(fiveSteak), keeper.ck.GetCoins(ctx, addrs[0])) + require.Equal(t, fourSteak.Add(fiveSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + require.Equal(t, addr0Initial.Sub(fourSteak).Sub(fiveSteak), keeper.ck.GetCoins(ctx, addrs[0])) // Check third deposit from a new address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fourSteak) @@ -114,8 +114,8 @@ func TestDeposits(t *testing.T) { require.True(t, found) require.Equal(t, addrs[1], deposit.Depositor) require.Equal(t, fourSteak, deposit.Amount) - require.Equal(t, fourSteak.Plus(fiveSteak).Plus(fourSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) - require.Equal(t, addr1Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[1])) + require.Equal(t, fourSteak.Add(fiveSteak).Add(fourSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + require.Equal(t, addr1Initial.Sub(fourSteak), keeper.ck.GetCoins(ctx, addrs[1])) // Check that proposal moved to voting period require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) @@ -125,7 +125,7 @@ func TestDeposits(t *testing.T) { require.True(t, depositsIterator.Valid()) keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[0], deposit.Depositor) - require.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount) + require.Equal(t, fourSteak.Add(fiveSteak), deposit.Amount) depositsIterator.Next() keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, addrs[1], deposit.Depositor) diff --git a/x/gov/params.go b/x/gov/params.go index 54601c7a6..ed0fa5ea4 100644 --- a/x/gov/params.go +++ b/x/gov/params.go @@ -26,19 +26,17 @@ func (dp DepositParams) Equal(dp2 DepositParams) bool { // Param around Tallying votes in governance type TallyParams struct { - Quorum sdk.Dec `json:"quorum"` // Minimum percentage of total stake needed to vote for a result to be considered valid - Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 - GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote + Quorum sdk.Dec `json:"quorum"` // Minimum percentage of total stake needed to vote for a result to be considered valid + Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 } func (tp TallyParams) String() string { return fmt.Sprintf(`Tally Params: Quorum: %s Threshold: %s - Veto: %s - Governance Penalty: %s`, tp.Quorum, - tp.Threshold, tp.Veto, tp.GovernancePenalty) + Veto: %s`, + tp.Quorum, tp.Threshold, tp.Veto) } // Param around Voting in governance diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 49095fb1a..94ffc151b 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -206,7 +206,7 @@ func (pt *ProposalKind) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } bz2, err := ProposalTypeFromString(s) @@ -307,7 +307,7 @@ func (status *ProposalStatus) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } bz2, err := ProposalStatusFromString(s) diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index d3707d525..4c5eddd37 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -89,9 +89,12 @@ func SimulateMsgSubmitProposal(k gov.Keeper) simulation.Operation { } func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { - ctx, _ = ctx.CacheContext() + ctx, write := ctx.CacheContext() result := handler(ctx, msg) ok = result.IsOK() + if ok { + write() + } event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok)) action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes()) return @@ -125,8 +128,11 @@ func SimulateMsgDeposit(k gov.Keeper) simulation.Operation { if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } - ctx, _ = ctx.CacheContext() + ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK())) action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil @@ -183,7 +189,7 @@ func randomDeposit(r *rand.Rand) sdk.Coins { // Pick a random proposal ID func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) { lastProposalID := k.GetLastProposalID(ctx) - if lastProposalID < 1 { + if lastProposalID < 1 || lastProposalID == (2<<63-1) { return 0, false } proposalID = uint64(r.Intn(1+int(lastProposalID)) - 1) diff --git a/x/gov/tally.go b/x/gov/tally.go index 1bf28e493..bdc5003fa 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -107,7 +107,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall return false, tallyResults } // If there is not enough quorum of votes, the proposal fails - percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(keeper.vs.TotalBondedTokens(ctx))) + percentVoting := totalVotingPower.Quo(keeper.vs.TotalBondedTokens(ctx).ToDec()) if percentVoting.LT(tallyParams.Quorum) { return false, tallyResults } diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 6527c79eb..0b1e7d9da 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -11,7 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" ) // initialize the mock application for this module @@ -36,7 +36,7 @@ func TestIBCMsgs(t *testing.T) { sourceChain := "source-chain" destChain := "dest-chain" - priv1 := ed25519.GenPrivKey() + priv1 := secp256k1.GenPrivKey() addr1 := sdk.AccAddress(priv1.PubKey().Address()) coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} var emptyCoins sdk.Coins @@ -62,11 +62,11 @@ func TestIBCMsgs(t *testing.T) { DestChain: destChain, } - transferMsg := IBCTransferMsg{ + transferMsg := MsgIBCTransfer{ IBCPacket: packet, } - receiveMsg := IBCReceiveMsg{ + receiveMsg := MsgIBCReceive{ IBCPacket: packet, Relayer: addr1, Sequence: 0, diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 3b3f5a368..a15752579 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -65,7 +65,7 @@ func buildMsg(from sdk.AccAddress) (sdk.Msg, error) { packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(client.FlagChainID), viper.GetString(flagChain)) - msg := ibc.IBCTransferMsg{ + msg := ibc.MsgIBCTransfer{ IBCPacket: packet, } diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index 231e0917f..c1c45611e 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -191,7 +191,7 @@ func (c relayCommander) refine(bz []byte, ibcSeq, accSeq uint64, passphrase stri panic(err) } - msg := ibc.IBCReceiveMsg{ + msg := ibc.MsgIBCReceive{ IBCPacket: packet, Relayer: c.address, Sequence: ibcSeq, diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index e0a228bb5..ee2b4da8c 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -48,28 +48,15 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - var fromAddr sdk.AccAddress - - if req.BaseReq.GenerateOnly { - // When generate only is supplied, the from field must be a valid Bech32 - // address. - addr, err := sdk.AccAddressFromBech32(req.BaseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - fromAddr = addr - } - - packet := ibc.NewIBCPacket(fromAddr, to, req.Amount, req.BaseReq.ChainID, destChainID) - msg := ibc.IBCTransferMsg{IBCPacket: packet} - - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + from, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + packet := ibc.NewIBCPacket(from, to, req.Amount, req.BaseReq.ChainID, destChainID) + msg := ibc.MsgIBCTransfer{IBCPacket: packet} + + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/x/ibc/codec.go b/x/ibc/codec.go index 43ffeb519..d47abde1a 100644 --- a/x/ibc/codec.go +++ b/x/ibc/codec.go @@ -6,6 +6,6 @@ import ( // Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(IBCTransferMsg{}, "cosmos-sdk/IBCTransferMsg", nil) - cdc.RegisterConcrete(IBCReceiveMsg{}, "cosmos-sdk/IBCReceiveMsg", nil) + cdc.RegisterConcrete(MsgIBCTransfer{}, "cosmos-sdk/MsgIBCTransfer", nil) + cdc.RegisterConcrete(MsgIBCReceive{}, "cosmos-sdk/MsgIBCReceive", nil) } diff --git a/x/ibc/handler.go b/x/ibc/handler.go index afc302768..7d7bda575 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -7,9 +7,9 @@ import ( func NewHandler(ibcm Mapper, ck BankKeeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { - case IBCTransferMsg: + case MsgIBCTransfer: return handleIBCTransferMsg(ctx, ibcm, ck, msg) - case IBCReceiveMsg: + case MsgIBCReceive: return handleIBCReceiveMsg(ctx, ibcm, ck, msg) default: errMsg := "Unrecognized IBC Msg type: " + msg.Type() @@ -18,8 +18,8 @@ func NewHandler(ibcm Mapper, ck BankKeeper) sdk.Handler { } } -// IBCTransferMsg deducts coins from the account and creates an egress IBC packet. -func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg IBCTransferMsg) sdk.Result { +// MsgIBCTransfer deducts coins from the account and creates an egress IBC packet. +func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg MsgIBCTransfer) sdk.Result { packet := msg.IBCPacket _, _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) @@ -35,8 +35,8 @@ func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg IBCTr return sdk.Result{} } -// IBCReceiveMsg adds coins to the destination address and creates an ingress IBC packet. -func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg IBCReceiveMsg) sdk.Result { +// MsgIBCReceive adds coins to the destination address and creates an ingress IBC packet. +func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg MsgIBCReceive) sdk.Result { packet := msg.IBCPacket seq := ibcm.GetIngressSequence(ctx, packet.SrcChain) @@ -44,6 +44,7 @@ func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck BankKeeper, msg IBCRec return ErrInvalidSequence(ibcm.codespace).Result() } + // XXX Check that packet.Coins is valid and positive (nonzero) _, _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) if err != nil { return err.Result() diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index f2719f7b3..b796b9c63 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -64,8 +64,8 @@ func makeCodec() *codec.Codec { // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/ibc/Send", nil) - cdc.RegisterConcrete(IBCTransferMsg{}, "test/ibc/IBCTransferMsg", nil) - cdc.RegisterConcrete(IBCReceiveMsg{}, "test/ibc/IBCReceiveMsg", nil) + cdc.RegisterConcrete(MsgIBCTransfer{}, "test/ibc/MsgIBCTransfer", nil) + cdc.RegisterConcrete(MsgIBCReceive{}, "test/ibc/MsgIBCReceive", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) @@ -121,7 +121,7 @@ func TestIBC(t *testing.T) { egl = ibcm.getEgressLength(store, chainid) require.Equal(t, egl, uint64(0)) - msg = IBCTransferMsg{ + msg = MsgIBCTransfer{ IBCPacket: packet, } res = h(ctx, msg) @@ -137,7 +137,7 @@ func TestIBC(t *testing.T) { igs = ibcm.GetIngressSequence(ctx, chainid) require.Equal(t, igs, uint64(0)) - msg = IBCReceiveMsg{ + msg = MsgIBCReceive{ IBCPacket: packet, Relayer: src, Sequence: 0, diff --git a/x/ibc/types.go b/x/ibc/types.go index 0f596bb03..3b626fdf8 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -62,53 +62,53 @@ func (p IBCPacket) ValidateBasic() sdk.Error { } // ---------------------------------- -// IBCTransferMsg +// MsgIBCTransfer // nolint - TODO rename to TransferMsg as folks will reference with ibc.TransferMsg -// IBCTransferMsg defines how another module can send an IBCPacket. -type IBCTransferMsg struct { +// MsgIBCTransfer defines how another module can send an IBCPacket. +type MsgIBCTransfer struct { IBCPacket } // nolint -func (msg IBCTransferMsg) Route() string { return "ibc" } -func (msg IBCTransferMsg) Type() string { return "transfer" } +func (msg MsgIBCTransfer) Route() string { return "ibc" } +func (msg MsgIBCTransfer) Type() string { return "transfer" } // x/bank/tx.go MsgSend.GetSigners() -func (msg IBCTransferMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.SrcAddr} } +func (msg MsgIBCTransfer) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.SrcAddr} } // get the sign bytes for ibc transfer message -func (msg IBCTransferMsg) GetSignBytes() []byte { +func (msg MsgIBCTransfer) GetSignBytes() []byte { return msg.IBCPacket.GetSignBytes() } // validate ibc transfer message -func (msg IBCTransferMsg) ValidateBasic() sdk.Error { +func (msg MsgIBCTransfer) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } // ---------------------------------- -// IBCReceiveMsg +// MsgIBCReceive // nolint - TODO rename to ReceiveMsg as folks will reference with ibc.ReceiveMsg -// IBCReceiveMsg defines the message that a relayer uses to post an IBCPacket +// MsgIBCReceive defines the message that a relayer uses to post an IBCPacket // to the destination chain. -type IBCReceiveMsg struct { +type MsgIBCReceive struct { IBCPacket Relayer sdk.AccAddress Sequence uint64 } // nolint -func (msg IBCReceiveMsg) Route() string { return "ibc" } -func (msg IBCReceiveMsg) Type() string { return "receive" } -func (msg IBCReceiveMsg) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } +func (msg MsgIBCReceive) Route() string { return "ibc" } +func (msg MsgIBCReceive) Type() string { return "receive" } +func (msg MsgIBCReceive) ValidateBasic() sdk.Error { return msg.IBCPacket.ValidateBasic() } // x/bank/tx.go MsgSend.GetSigners() -func (msg IBCReceiveMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Relayer} } +func (msg MsgIBCReceive) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Relayer} } // get the sign bytes for ibc receive message -func (msg IBCReceiveMsg) GetSignBytes() []byte { +func (msg MsgIBCReceive) GetSignBytes() []byte { b, err := msgCdc.MarshalJSON(struct { IBCPacket json.RawMessage Relayer sdk.AccAddress diff --git a/x/ibc/types_test.go b/x/ibc/types_test.go index 7e0f537d6..674b44982 100644 --- a/x/ibc/types_test.go +++ b/x/ibc/types_test.go @@ -31,11 +31,11 @@ func TestIBCPacketValidation(t *testing.T) { } // ------------------------------- -// IBCTransferMsg Tests +// MsgIBCTransfer Tests func TestIBCTransferMsg(t *testing.T) { packet := constructIBCPacket(true) - msg := IBCTransferMsg{packet} + msg := MsgIBCTransfer{packet} require.Equal(t, msg.Route(), "ibc") } @@ -46,10 +46,10 @@ func TestIBCTransferMsgValidation(t *testing.T) { cases := []struct { valid bool - msg IBCTransferMsg + msg MsgIBCTransfer }{ - {true, IBCTransferMsg{validPacket}}, - {false, IBCTransferMsg{invalidPacket}}, + {true, MsgIBCTransfer{validPacket}}, + {false, MsgIBCTransfer{invalidPacket}}, } for i, tc := range cases { @@ -63,11 +63,11 @@ func TestIBCTransferMsgValidation(t *testing.T) { } // ------------------------------- -// IBCReceiveMsg Tests +// MsgIBCReceive Tests func TestIBCReceiveMsg(t *testing.T) { packet := constructIBCPacket(true) - msg := IBCReceiveMsg{packet, sdk.AccAddress([]byte("relayer")), 0} + msg := MsgIBCReceive{packet, sdk.AccAddress([]byte("relayer")), 0} require.Equal(t, msg.Route(), "ibc") } @@ -78,10 +78,10 @@ func TestIBCReceiveMsgValidation(t *testing.T) { cases := []struct { valid bool - msg IBCReceiveMsg + msg MsgIBCReceive }{ - {true, IBCReceiveMsg{validPacket, sdk.AccAddress([]byte("relayer")), 0}}, - {false, IBCReceiveMsg{invalidPacket, sdk.AccAddress([]byte("relayer")), 0}}, + {true, MsgIBCReceive{validPacket, sdk.AccAddress([]byte("relayer")), 0}}, + {false, MsgIBCReceive{invalidPacket, sdk.AccAddress([]byte("relayer")), 0}}, } for i, tc := range cases { diff --git a/x/mock/app.go b/x/mock/app.go index 1864e3941..3e24bee0d 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -177,7 +177,7 @@ func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrKeysSlice := AddrKeysSlice{} for i := 0; i < numAccs; i++ { - privKey := ed25519.GenPrivKey() + privKey := secp256k1.GenPrivKey() pubKey := privKey.PubKey() addr := sdk.AccAddress(pubKey.Address()) @@ -235,12 +235,12 @@ func GenTx(msgs []sdk.Msg, accnums []uint64, seq []uint64, priv ...crypto.PrivKe return auth.NewStdTx(msgs, fee, sigs, memo) } -// GeneratePrivKeys generates a total n Ed25519 private keys. +// GeneratePrivKeys generates a total n secp256k1 private keys. func GeneratePrivKeys(n int) (keys []crypto.PrivKey) { // TODO: Randomize this between ed25519 and secp256k1 keys = make([]crypto.PrivKey, n) for i := 0; i < n; i++ { - keys[i] = ed25519.GenPrivKey() + keys[i] = secp256k1.GenPrivKey() } return @@ -304,7 +304,7 @@ func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.AccAddress, denoms []s } } - app.TotalCoinsSupply = app.TotalCoinsSupply.Plus(coins) + app.TotalCoinsSupply = app.TotalCoinsSupply.Add(coins) baseAcc := auth.NewBaseAccountWithAddress(addrs[i]) (&baseAcc).SetCoins(coins) diff --git a/x/mock/simulation/mock_tendermint.go b/x/mock/simulation/mock_tendermint.go index 54e38d5c7..e241e9c67 100644 --- a/x/mock/simulation/mock_tendermint.go +++ b/x/mock/simulation/mock_tendermint.go @@ -17,6 +17,14 @@ type mockValidator struct { livenessState int } +func (mv mockValidator) String() string { + return fmt.Sprintf("mockValidator{%s:%X power:%v state:%v}", + mv.val.PubKey.Type, + mv.val.PubKey.Data, + mv.val.Power, + mv.livenessState) +} + type mockValidators map[string]mockValidator // get mockValidators from abci validators diff --git a/x/mock/simulation/rand_util.go b/x/mock/simulation/rand_util.go index a57775ecb..1a5e4566f 100644 --- a/x/mock/simulation/rand_util.go +++ b/x/mock/simulation/rand_util.go @@ -37,13 +37,32 @@ func RandStringOfLength(r *rand.Rand, n int) string { } // Generate a random amount +// Note: The range of RandomAmount includes max, and is, in fact, biased to return max as well as 0. func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { - return sdk.NewInt(int64(r.Intn(int(max.Int64())))) + var randInt = big.NewInt(0) + switch r.Intn(10) { + case 0: + // randInt = big.NewInt(0) + case 1: + randInt = max.BigInt() + default: // NOTE: there are 10 total cases. + randInt = big.NewInt(0).Rand(r, max.BigInt()) // up to max - 1 + } + return sdk.NewIntFromBigInt(randInt) } // RandomDecAmount generates a random decimal amount +// Note: The range of RandomDecAmount includes max, and is, in fact, biased to return max as well as 0. func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec { - randInt := big.NewInt(0).Rand(r, max.Int) + var randInt = big.NewInt(0) + switch r.Intn(10) { + case 0: + // randInt = big.NewInt(0) + case 1: + randInt = max.Int // the underlying big int with all precision bits. + default: // NOTE: there are 10 total cases. + randInt = big.NewInt(0).Rand(r, max.Int) + } return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision) } diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 2ede44922..6f15e4aee 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -15,7 +15,7 @@ import ( ) var ( - priv1 = ed25519.GenPrivKey() + priv1 = secp256k1.GenPrivKey() addr1 = sdk.AccAddress(priv1.PubKey().Address()) coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} ) @@ -112,11 +112,11 @@ func TestSlashingMsgs(t *testing.T) { sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, sdk.OneInt(), ) mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []uint64{0}, []uint64{0}, true, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Sub(bondCoin)}) mapp.BeginBlock(abci.RequestBeginBlock{}) validator := checkValidator(t, mapp, stakingKeeper, addr1, true) - require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddress) require.Equal(t, sdk.Bonded, validator.Status) require.True(sdk.IntEq(t, bondTokens, validator.BondedTokens())) unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.ConsPubKey.Address())} diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 8233543f7..95b93740b 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -49,6 +49,17 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if !bytes.Equal(fromAddr, valAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") + return + } + msg := slashing.NewMsgUnjail(valAddr) err = msg.ValidateBasic() if err != nil { @@ -56,25 +67,6 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - - if !bytes.Equal(cliCtx.GetFromAddress(), valAddr) { - rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") - return - } - - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index d9ae9f2d8..fcc141a86 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -31,7 +31,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrMissingSelfDelegation(k.codespace).Result() } - if validator.GetDelegatorShareExRate().Mul(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { + if validator.ShareTokens(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { return ErrSelfDelegationTooLowToUnjail(k.codespace).Result() } diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 569fb54ad..07cf45001 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -92,7 +92,7 @@ func TestJailedValidatorDelegations(t *testing.T) { got = staking.NewHandler(stakingKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) - unbondShares := sdk.NewDecFromInt(bondAmount) + unbondShares := bondAmount.ToDec() // unbond validator total self-delegations (which should jail the validator) msgUndelegate := staking.NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index de3751a15..8b8360492 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -47,7 +47,16 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio consAddr := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { - panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) + // Ignore evidence that cannot be handled. + // NOTE: + // We used to panic with: + // `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`, + // but this couples the expectations of the app to both Tendermint and + // the simulator. Both are expected to provide the full range of + // allowable but none of the disallowed evidence types. Instead of + // getting this coordination right, it is easier to relax the + // constraints and ignore evidence that cannot be handled. + return } // Reject evidence if the double-sign is too old @@ -154,7 +163,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } if missed { - logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d missed, threshold %d", addr, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) + logger.Info(fmt.Sprintf("Absent validator %s (%v) at height %d, %d missed, threshold %d", addr, pubkey, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 266e443ca..4dbc1e727 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -189,7 +189,7 @@ func TestHandleAbsentValidator(t *testing.T) { validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) - slashAmt := sdk.NewDecFromInt(amt).Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() + slashAmt := amt.ToDec().Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() // validator should have been slashed require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) diff --git a/x/staking/alias.go b/x/staking/alias.go index 5e9ddaccd..999814b6d 100644 --- a/x/staking/alias.go +++ b/x/staking/alias.go @@ -85,12 +85,11 @@ var ( DefaultGenesisState = types.DefaultGenesisState RegisterCodec = types.RegisterCodec - NewMsgCreateValidator = types.NewMsgCreateValidator - NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf - NewMsgEditValidator = types.NewMsgEditValidator - NewMsgDelegate = types.NewMsgDelegate - NewMsgUndelegate = types.NewMsgUndelegate - NewMsgBeginRedelegate = types.NewMsgBeginRedelegate + NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgEditValidator = types.NewMsgEditValidator + NewMsgDelegate = types.NewMsgDelegate + NewMsgUndelegate = types.NewMsgUndelegate + NewMsgBeginRedelegate = types.NewMsgBeginRedelegate NewQuerier = querier.NewQuerier NewQueryDelegatorParams = querier.NewQueryDelegatorParams diff --git a/x/staking/app_test.go b/x/staking/app_test.go index d9781201d..d4481cef7 100644 --- a/x/staking/app_test.go +++ b/x/staking/app_test.go @@ -123,36 +123,21 @@ func TestStakingMsgs(t *testing.T) { ) mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []uint64{0}, []uint64{0}, true, true, priv1) - mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Sub(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) validator := checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) - require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddress) require.Equal(t, sdk.Bonded, validator.Status) require.True(sdk.IntEq(t, bondTokens, validator.BondedTokens())) - // addr1 create validator on behalf of addr2 - createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf( - addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, commissionMsg, sdk.OneInt(), - ) - - mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []uint64{0, 0}, []uint64{1, 0}, true, true, priv1, priv2) - mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) - validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr2), true) - require.Equal(t, sdk.ValAddress(addr2), validator.OperatorAddr) - require.Equal(t, sdk.Bonded, validator.Status) - require.True(sdk.IntEq(t, bondTokens, validator.Tokens)) - - // check the bond that should have been created as well - checkDelegation(t, mApp, keeper, addr1, sdk.ValAddress(addr1), true, sdk.NewDecFromInt(bondTokens)) - // edit the validator description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description, nil, nil) - mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []uint64{0}, []uint64{2}, true, true, priv1) + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []uint64{0}, []uint64{1}, true, true, priv1) validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) require.Equal(t, description, validator.Description) @@ -160,17 +145,17 @@ func TestStakingMsgs(t *testing.T) { mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) - mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{delegateMsg}, []uint64{0}, []uint64{1}, true, true, priv2) - mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) - checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDecFromInt(bondTokens)) + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{delegateMsg}, []uint64{0}, []uint64{0}, true, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Sub(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, bondTokens.ToDec()) // begin unbonding - beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), sdk.NewDecFromInt(bondTokens)) - mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []uint64{0}, []uint64{2}, true, true, priv2) + beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), bondTokens.ToDec()) + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []uint64{0}, []uint64{1}, true, true, priv2) // delegation should exist anymore checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) // balance should be the same because bonding not yet complete - mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Sub(bondCoin)}) } diff --git a/x/staking/client/cli/flags.go b/x/staking/client/cli/flags.go index 14cac4c07..761d533ee 100644 --- a/x/staking/client/cli/flags.go +++ b/x/staking/client/cli/flags.go @@ -8,7 +8,6 @@ import ( // nolint const ( - FlagAddressDelegator = "address-delegator" FlagAddressValidator = "validator" FlagAddressValidatorSrc = "addr-validator-source" FlagAddressValidatorDst = "addr-validator-dest" @@ -67,7 +66,6 @@ func init() { fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "The validator's (optional) website") fsDescriptionEdit.String(FlagDetails, types.DoNotModifyDesc, "The validator's (optional) details") fsValidator.String(FlagAddressValidator, "", "The Bech32 address of the validator") - fsDelegator.String(FlagAddressDelegator, "", "The Bech32 address of the delegator") fsRedelegation.String(FlagAddressValidatorSrc, "", "The Bech32 address of the source validator") fsRedelegation.String(FlagAddressValidatorDst, "", "The Bech32 address of the destination validator") } diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 74f4a76f4..b5eb8defa 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -44,7 +44,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsDescriptionCreate) cmd.Flags().AddFlagSet(FsCommissionCreate) cmd.Flags().AddFlagSet(FsMinSelfDelegation) - cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().String(FlagIP, "", fmt.Sprintf("The node's public IP. It takes effect only when used in combination with --%s", client.FlagGenerateOnly)) cmd.Flags().String(FlagNodeID, "", "The node's ID") @@ -259,23 +259,9 @@ func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder return txBldr, nil, fmt.Errorf(staking.ErrMinSelfDelegationInvalid(staking.DefaultCodespace).Error()) } - delAddr := viper.GetString(FlagAddressDelegator) - - var msg sdk.Msg - if delAddr != "" { - delAddr, err := sdk.AccAddressFromBech32(delAddr) - if err != nil { - return txBldr, nil, err - } - - msg = staking.NewMsgCreateValidatorOnBehalfOf( - delAddr, sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, minSelfDelegation, - ) - } else { - msg = staking.NewMsgCreateValidator( - sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, minSelfDelegation, - ) - } + msg := staking.NewMsgCreateValidator( + sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, minSelfDelegation, + ) if viper.GetBool(client.FlagGenerateOnly) { ip := viper.GetString(FlagIP) @@ -284,5 +270,6 @@ func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder txBldr = txBldr.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip)) } } + return txBldr, msg, nil } diff --git a/x/staking/client/rest/tx.go b/x/staking/client/rest/tx.go index b7a417351..f937dd333 100644 --- a/x/staking/client/rest/tx.go +++ b/x/staking/client/rest/tx.go @@ -33,27 +33,27 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec type ( // MsgBeginRedelegateInput defines the properties of a delegation request's body. MsgDelegationsInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 + Delegation sdk.Coin `json:"delegation"` } // MsgBeginRedelegateInput defines the properties of a redelegate request's body. MsgBeginRedelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` // in bech32 + ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } // MsgUndelegateInput defines the properties of a undelegate request's body. MsgUndelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + BaseReq rest.BaseReq `json:"base_req"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 + ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } ) @@ -70,32 +70,24 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - msg := staking.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) + msg := staking.NewMsgDelegate(req.DelegatorAddress, req.ValidatorAddress, req.Delegation) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - - if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + if !bytes.Equal(fromAddr, req.DelegatorAddress) { rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") return } - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -112,32 +104,24 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return } - msg := staking.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) + msg := staking.NewMsgBeginRedelegate(req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress, req.SharesAmount) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - - if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + if !bytes.Equal(fromAddr, req.DelegatorAddress) { rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") return } - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } @@ -154,31 +138,23 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return } - msg := staking.NewMsgUndelegate(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) + msg := staking.NewMsgUndelegate(req.DelegatorAddress, req.ValidatorAddress, req.SharesAmount) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if req.BaseReq.GenerateOnly { - clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) - return - } - - // derive the from account address and name from the Keybase - fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) - - if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + if !bytes.Equal(fromAddr, req.DelegatorAddress) { rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") return } - clientrest.CompleteAndBroadcastTxREST(w, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/x/staking/genesis.go b/x/staking/genesis.go index 1374e75be..7c10584b5 100644 --- a/x/staking/genesis.go +++ b/x/staking/genesis.go @@ -37,7 +37,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ // Call the creation hook if not exported if !data.Exported { - keeper.AfterValidatorCreated(ctx, validator.OperatorAddr) + keeper.AfterValidatorCreated(ctx, validator.OperatorAddress) } // Set timeslice if necessary @@ -49,12 +49,12 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ for _, delegation := range data.Delegations { // Call the before-creation hook if not exported if !data.Exported { - keeper.BeforeDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + keeper.BeforeDelegationCreated(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) } keeper.SetDelegation(ctx, delegation) // Call the after-modification hook if not exported if !data.Exported { - keeper.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + keeper.AfterDelegationModified(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) } } diff --git a/x/staking/genesis_test.go b/x/staking/genesis_test.go index 82b076104..c62b7caa5 100644 --- a/x/staking/genesis_test.go +++ b/x/staking/genesis_test.go @@ -28,18 +28,18 @@ func TestInitGenesis(t *testing.T) { var delegations []Delegation // initialize the validators - validators[0].OperatorAddr = sdk.ValAddress(keep.Addrs[0]) + validators[0].OperatorAddress = sdk.ValAddress(keep.Addrs[0]) validators[0].ConsPubKey = keep.PKs[0] validators[0].Description = NewDescription("hoop", "", "", "") validators[0].Status = sdk.Bonded validators[0].Tokens = valTokens - validators[0].DelegatorShares = sdk.NewDecFromInt(valTokens) - validators[1].OperatorAddr = sdk.ValAddress(keep.Addrs[1]) + validators[0].DelegatorShares = valTokens.ToDec() + validators[1].OperatorAddress = sdk.ValAddress(keep.Addrs[1]) validators[1].ConsPubKey = keep.PKs[1] validators[1].Description = NewDescription("bloop", "", "", "") validators[1].Status = sdk.Bonded validators[1].Tokens = valTokens - validators[1].DelegatorShares = sdk.NewDecFromInt(valTokens) + validators[1].DelegatorShares = valTokens.ToDec() genesisState := types.NewGenesisState(pool, params, validators, delegations) vals, err := InitGenesis(ctx, keeper, genesisState) @@ -94,7 +94,7 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { tokens = sdk.TokensFromTendermintPower(2) } validators[i].Tokens = tokens - validators[i].DelegatorShares = sdk.NewDecFromInt(tokens) + validators[i].DelegatorShares = tokens.ToDec() } genesisState := types.NewGenesisState(pool, params, validators, delegations) diff --git a/x/staking/handler.go b/x/staking/handler.go index 9565b1d93..2897317ee 100644 --- a/x/staking/handler.go +++ b/x/staking/handler.go @@ -54,32 +54,32 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, sdk.T // Remove all mature unbonding delegations from the ubd queue. matureUnbonds := k.DequeueAllMatureUBDQueue(ctx, ctx.BlockHeader().Time) for _, dvPair := range matureUnbonds { - err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) + err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddress, dvPair.ValidatorAddress) if err != nil { continue } resTags.AppendTags(sdk.NewTags( tags.Action, ActionCompleteUnbonding, - tags.Delegator, dvPair.DelegatorAddr.String(), - tags.SrcValidator, dvPair.ValidatorAddr.String(), + tags.Delegator, dvPair.DelegatorAddress.String(), + tags.SrcValidator, dvPair.ValidatorAddress.String(), )) } // Remove all mature redelegations from the red queue. matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time) for _, dvvTriplet := range matureRedelegations { - err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddr, - dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr) + err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddress, + dvvTriplet.ValidatorSrcAddress, dvvTriplet.ValidatorDstAddress) if err != nil { continue } resTags.AppendTags(sdk.NewTags( tags.Action, tags.ActionCompleteRedelegation, - tags.Delegator, dvvTriplet.DelegatorAddr.String(), - tags.SrcValidator, dvvTriplet.ValidatorSrcAddr.String(), - tags.DstValidator, dvvTriplet.ValidatorDstAddr.String(), + tags.Delegator, dvvTriplet.DelegatorAddress.String(), + tags.SrcValidator, dvvTriplet.ValidatorSrcAddress.String(), + tags.DstValidator, dvvTriplet.ValidatorDstAddress.String(), )) } @@ -91,7 +91,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, sdk.T 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 - if _, found := k.GetValidator(ctx, msg.ValidatorAddr); found { + if _, found := k.GetValidator(ctx, msg.ValidatorAddress); found { return ErrValidatorOwnerExists(k.Codespace()).Result() } @@ -116,7 +116,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k } } - validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) + validator := NewValidator(msg.ValidatorAddress, msg.PubKey, msg.Description) commission := NewCommissionWithTime( msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, @@ -133,17 +133,17 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k k.SetNewValidatorByPowerIndex(ctx, validator) // call the after-creation hook - k.AfterValidatorCreated(ctx, validator.OperatorAddr) + k.AfterValidatorCreated(ctx, validator.OperatorAddress) // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here - _, err = k.Delegate(ctx, msg.DelegatorAddr, msg.Value.Amount, validator, true) + _, err = k.Delegate(ctx, msg.DelegatorAddress, msg.Value.Amount, validator, true) if err != nil { return err.Result() } tags := sdk.NewTags( - tags.DstValidator, msg.ValidatorAddr.String(), + tags.DstValidator, msg.ValidatorAddress.String(), tags.Moniker, msg.Description.Moniker, tags.Identity, msg.Description.Identity, ) @@ -155,7 +155,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k 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) + validator, found := k.GetValidator(ctx, msg.ValidatorAddress) if !found { return ErrNoValidatorFound(k.Codespace()).Result() } @@ -175,7 +175,7 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe } // call the before-modification hook since we're about to update the commission - k.BeforeValidatorModified(ctx, msg.ValidatorAddr) + k.BeforeValidatorModified(ctx, msg.ValidatorAddress) validator.Commission = commission } @@ -193,7 +193,7 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe k.SetValidator(ctx, validator) tags := sdk.NewTags( - tags.DstValidator, msg.ValidatorAddr.String(), + tags.DstValidator, msg.ValidatorAddress.String(), tags.Moniker, description.Moniker, tags.Identity, description.Identity, ) @@ -204,7 +204,7 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe } func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) + validator, found := k.GetValidator(ctx, msg.ValidatorAddress) if !found { return ErrNoValidatorFound(k.Codespace()).Result() } @@ -213,14 +213,14 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return ErrBadDenom(k.Codespace()).Result() } - _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Value.Amount, validator, true) + _, err := k.Delegate(ctx, msg.DelegatorAddress, msg.Value.Amount, validator, true) if err != nil { return err.Result() } tags := sdk.NewTags( - tags.Delegator, msg.DelegatorAddr.String(), - tags.DstValidator, msg.ValidatorAddr.String(), + tags.Delegator, msg.DelegatorAddress.String(), + tags.DstValidator, msg.ValidatorAddress.String(), ) return sdk.Result{ @@ -229,15 +229,15 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) } func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keeper) sdk.Result { - completionTime, err := k.Undelegate(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) + completionTime, err := k.Undelegate(ctx, msg.DelegatorAddress, msg.ValidatorAddress, msg.SharesAmount) if err != nil { return err.Result() } finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime) tags := sdk.NewTags( - tags.Delegator, msg.DelegatorAddr.String(), - tags.SrcValidator, msg.ValidatorAddr.String(), + tags.Delegator, msg.DelegatorAddress.String(), + tags.SrcValidator, msg.ValidatorAddress.String(), tags.EndTime, completionTime.Format(time.RFC3339), ) @@ -245,17 +245,17 @@ func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keep } func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { - completionTime, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, - msg.ValidatorDstAddr, msg.SharesAmount) + completionTime, err := k.BeginRedelegation(ctx, msg.DelegatorAddress, msg.ValidatorSrcAddress, + msg.ValidatorDstAddress, msg.SharesAmount) if err != nil { return err.Result() } finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime) resTags := sdk.NewTags( - tags.Delegator, msg.DelegatorAddr.String(), - tags.SrcValidator, msg.ValidatorSrcAddr.String(), - tags.DstValidator, msg.ValidatorDstAddr.String(), + tags.Delegator, msg.DelegatorAddress.String(), + tags.SrcValidator, msg.ValidatorSrcAddress.String(), + tags.DstValidator, msg.ValidatorDstAddress.String(), tags.EndTime, completionTime.Format(time.RFC3339), ) diff --git a/x/staking/handler_test.go b/x/staking/handler_test.go index 2daea30e6..73e895637 100644 --- a/x/staking/handler_test.go +++ b/x/staking/handler_test.go @@ -74,7 +74,7 @@ func TestValidatorByPowerIndex(t *testing.T) { validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding - require.Equal(t, initBond.DivRaw(2), validator.Tokens) // ensure tokens slashed + require.Equal(t, initBond.QuoRaw(2), validator.Tokens) // ensure tokens slashed keeper.Unjail(ctx, consAddr0) // the old power record should have been deleted as the power changed @@ -91,7 +91,7 @@ func TestValidatorByPowerIndex(t *testing.T) { require.Equal(t, power2, power3) // unbond self-delegation - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(initBond)) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, initBond.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) var finishTime time.Time @@ -123,10 +123,10 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addr1) require.True(t, found) assert.Equal(t, sdk.Bonded, validator.Status) - assert.Equal(t, addr1, validator.OperatorAddr) + assert.Equal(t, addr1, validator.OperatorAddress) assert.Equal(t, pk1, validator.ConsPubKey) assert.Equal(t, valTokens, validator.BondedTokens()) - assert.Equal(t, sdk.NewDecFromInt(valTokens), validator.DelegatorShares) + assert.Equal(t, valTokens.ToDec(), validator.DelegatorShares) assert.Equal(t, Description{}, validator.Description) // two validators can't have the same operator address @@ -152,10 +152,10 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { require.True(t, found) assert.Equal(t, sdk.Bonded, validator.Status) - assert.Equal(t, addr2, validator.OperatorAddr) + assert.Equal(t, addr2, validator.OperatorAddress) assert.Equal(t, pk2, validator.ConsPubKey) assert.True(sdk.IntEq(t, valTokens, validator.Tokens)) - assert.True(sdk.DecEq(t, sdk.NewDecFromInt(valTokens), validator.DelegatorShares)) + assert.True(sdk.DecEq(t, valTokens.ToDec(), validator.DelegatorShares)) assert.Equal(t, Description{}, validator.Description) } @@ -178,38 +178,6 @@ func TestInvalidPubKeyTypeMsgCreateValidator(t *testing.T) { require.True(t, got.IsOK(), "%v", got) } -func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { - ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - - validatorAddr := sdk.ValAddress(keep.Addrs[0]) - delegatorAddr := keep.Addrs[1] - pk := keep.PKs[0] - valTokens := sdk.TokensFromTendermintPower(10) - msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, valTokens) - got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) - require.True(t, got.IsOK(), "%v", got) - - // must end-block - updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(updates)) - - validator, found := keeper.GetValidator(ctx, validatorAddr) - - require.True(t, found) - assert.Equal(t, sdk.Bonded, validator.Status) - assert.Equal(t, validatorAddr, validator.OperatorAddr) - assert.Equal(t, pk, validator.ConsPubKey) - assert.True(sdk.IntEq(t, valTokens, validator.Tokens)) - assert.True(sdk.DecEq(t, sdk.NewDecFromInt(valTokens), validator.DelegatorShares)) - assert.Equal(t, Description{}, validator.Description) - - // one validator cannot be created twice even from different delegator - msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2] - msgCreateValidatorOnBehalfOf.PubKey = keep.PKs[1] - got = handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) - require.False(t, got.IsOK(), "%v", got) -} - func TestLegacyValidatorDelegations(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, int64(1000)) setInstantUnbondPeriod(keeper, ctx) @@ -248,7 +216,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { // unbond validator total self-delegations (which should jail the validator) unbondShares := sdk.TokensFromTendermintPower(10) - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, sdk.NewDecFromInt(unbondShares)) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) @@ -333,8 +301,6 @@ func TestIncrementsMsgDelegate(t *testing.T) { require.Equal(t, bondAmount, bond.Shares.RoundInt()) pool := keeper.GetPool(ctx) - exRate := validator.DelegatorShareExRate() - require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v", exRate) require.Equal(t, bondAmount, pool.BondedTokens) // just send the same msgbond multiple times @@ -352,9 +318,6 @@ func TestIncrementsMsgDelegate(t *testing.T) { bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) - exRate := validator.DelegatorShareExRate() - require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v, i = %v", exRate, i) - expBond := bondAmount.MulRaw(i + 1) expDelegatorShares := bondAmount.MulRaw(i + 2) // (1 self delegation) expDelegatorAcc := initBond.Sub(expBond) @@ -520,7 +483,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { initBond, } for i, c := range errorCases { - unbondShares := sdk.NewDecFromInt(c) + unbondShares := c.ToDec() msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail, index: %v", i) @@ -529,14 +492,14 @@ func TestIncrementsMsgUnbond(t *testing.T) { leftBonded := initBond.Sub(unbondShares.MulInt64(numUnbonds).RoundInt()) // should be unable to unbond one more than we have - unbondShares = sdk.NewDecFromInt(leftBonded.AddRaw(1)) + unbondShares = leftBonded.AddRaw(1).ToDec() msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.False(t, got.IsOK(), "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUndelegate, unbondShares.String(), leftBonded) // should be able to unbond just what we have - unbondShares = sdk.NewDecFromInt(leftBonded) + unbondShares = leftBonded.ToDec() msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), @@ -555,25 +518,27 @@ func TestMultipleMsgCreateValidator(t *testing.T) { sdk.ValAddress(keep.Addrs[2]), } delegatorAddrs := []sdk.AccAddress{ - keep.Addrs[3], - keep.Addrs[4], - keep.Addrs[5], + keep.Addrs[0], + keep.Addrs[1], + keep.Addrs[2], } // bond them all for i, validatorAddr := range validatorAddrs { valTokens := sdk.TokensFromTendermintPower(10) - msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf( - delegatorAddrs[i], validatorAddr, keep.PKs[i], valTokens) + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidator(validatorAddr, keep.PKs[i], valTokens) + got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) - //Check that the account is bonded + // verify that the account is bonded validators := keeper.GetValidators(ctx, 100) require.Equal(t, (i + 1), len(validators)) + val := validators[i] balanceExpd := initTokens.Sub(valTokens) balanceGot := accMapper.GetAccount(ctx, delegatorAddrs[i]).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, valTokens, val.DelegatorShares.RoundInt(), "expected %d shares, got %d", 10, val.DelegatorShares) require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) @@ -584,7 +549,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) { _, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) unbondingTokens := sdk.TokensFromTendermintPower(10) - msgUndelegate := NewMsgUndelegate(delegatorAddrs[i], validatorAddr, sdk.NewDecFromInt(unbondingTokens)) // remove delegation + msgUndelegate := NewMsgUndelegate(delegatorAddrs[i], validatorAddr, unbondingTokens.ToDec()) // remove delegation got := handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time @@ -710,7 +675,7 @@ func TestValidatorQueue(t *testing.T) { // unbond the all self-delegation to put validator in unbonding state msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), - validatorAddr, sdk.NewDecFromInt(delTokens)) + validatorAddr, delTokens.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time @@ -758,7 +723,7 @@ func TestUnbondingPeriod(t *testing.T) { // begin unbonding unbondingTokens := sdk.TokensFromTendermintPower(10) msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), - validatorAddr, sdk.NewDecFromInt(unbondingTokens)) + validatorAddr, unbondingTokens.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error") origHeader := ctx.BlockHeader() @@ -951,7 +916,7 @@ func TestMultipleRedelegationAtSameTime(t *testing.T) { // begin a redelegate selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, - valAddr, valAddr2, sdk.NewDecFromInt(valTokens.DivRaw(2))) + valAddr, valAddr2, valTokens.QuoRaw(2).ToDec()) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1003,7 +968,7 @@ func TestMultipleRedelegationAtUniqueTimes(t *testing.T) { // begin a redelegate selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, - valAddr, valAddr2, sdk.NewDecFromInt(valTokens.DivRaw(2))) + valAddr, valAddr2, valTokens.QuoRaw(2).ToDec()) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1051,7 +1016,7 @@ func TestMultipleUnbondingDelegationAtSameTime(t *testing.T) { // begin an unbonding delegation selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, sdk.NewDecFromInt(valTokens.DivRaw(2))) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, valTokens.QuoRaw(2).ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1097,7 +1062,7 @@ func TestMultipleUnbondingDelegationAtUniqueTimes(t *testing.T) { // begin an unbonding delegation selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, sdk.NewDecFromInt(valTokens.DivRaw(2))) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, valTokens.QuoRaw(2).ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1168,7 +1133,7 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) // unbond the valdator-2 - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDecFromInt(valTokens2)) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr2), validatorAddr2, valTokens2.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") @@ -1213,20 +1178,20 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // begin unbonding 4 stake ubdTokens := sdk.TokensFromTendermintPower(4) - msgUndelegate := NewMsgUndelegate(del, valA, sdk.NewDecFromInt(ubdTokens)) + msgUndelegate := NewMsgUndelegate(del, valA, ubdTokens.ToDec()) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") // begin redelegate 6 stake rdTokens := sdk.TokensFromTendermintPower(6) - msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, sdk.NewDecFromInt(rdTokens)) + msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, rdTokens.ToDec()) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgBeginRedelegate") // destination delegation should have 6 shares delegation, found := keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, sdk.NewDecFromInt(rdTokens), delegation.Shares) + require.Equal(t, rdTokens.ToDec(), delegation.Shares) // must apply validator updates updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -1239,7 +1204,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ubd, found := keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) require.Len(t, ubd.Entries, 1) - require.Equal(t, ubdTokens.DivRaw(2), ubd.Entries[0].Balance) + require.Equal(t, ubdTokens.QuoRaw(2), ubd.Entries[0].Balance) // redelegation should have been slashed by half redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB) @@ -1249,12 +1214,12 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // destination delegation should have been slashed by half delegation, found = keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, sdk.NewDecFromInt(rdTokens.DivRaw(2)), delegation.Shares) + require.Equal(t, rdTokens.QuoRaw(2).ToDec(), delegation.Shares) // validator power should have been reduced by half validator, found := keeper.GetValidator(ctx, valA) require.True(t, found) - require.Equal(t, valTokens.DivRaw(2), validator.GetBondedTokens()) + require.Equal(t, valTokens.QuoRaw(2), validator.GetBondedTokens()) // slash the validator for an infraction committed after the unbonding and redelegation begin ctx = ctx.WithBlockHeight(3) @@ -1264,7 +1229,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ubd, found = keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) require.Len(t, ubd.Entries, 1) - require.Equal(t, ubdTokens.DivRaw(2), ubd.Entries[0].Balance) + require.Equal(t, ubdTokens.QuoRaw(2), ubd.Entries[0].Balance) // redelegation should be unchanged redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB) @@ -1274,7 +1239,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // destination delegation should be unchanged delegation, found = keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, sdk.NewDecFromInt(rdTokens.DivRaw(2)), delegation.Shares) + require.Equal(t, rdTokens.QuoRaw(2).ToDec(), delegation.Shares) // end blocker EndBlocker(ctx, keeper) diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 589a5c524..86adf577c 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -76,15 +76,15 @@ func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddres func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) b := types.MustMarshalDelegation(k.cdc, delegation) - store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b) + store.Set(GetDelegationKey(delegation.DelegatorAddress, delegation.ValidatorAddress), b) } // remove a delegation func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { // TODO: Consider calling hooks outside of the store wrapper functions, it's unobvious. - k.BeforeDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + k.BeforeDelegationRemoved(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) + store.Delete(GetDelegationKey(delegation.DelegatorAddress, delegation.ValidatorAddress)) } // return a given amount of all the delegator unbonding-delegations @@ -167,17 +167,17 @@ func (k Keeper) HasMaxUnbondingDelegationEntries(ctx sdk.Context, func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) bz := types.MustMarshalUBD(k.cdc, ubd) - key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + key := GetUBDKey(ubd.DelegatorAddress, ubd.ValidatorAddress) store.Set(key, bz) - store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr), []byte{}) // index, store empty bytes + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddress, ubd.ValidatorAddress), []byte{}) // index, store empty bytes } // remove the unbonding delegation object and associated index func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - key := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) + key := GetUBDKey(ubd.DelegatorAddress, ubd.ValidatorAddress) store.Delete(key) - store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddress, ubd.ValidatorAddress)) } // SetUnbondingDelegationEntry adds an entry to the unbonding delegation at @@ -222,7 +222,7 @@ func (k Keeper) InsertUBDQueue(ctx sdk.Context, ubd types.UnbondingDelegation, completionTime time.Time) { timeSlice := k.GetUBDQueueTimeSlice(ctx, completionTime) - dvPair := types.DVPair{DelegatorAddr: ubd.DelegatorAddr, ValidatorAddr: ubd.ValidatorAddr} + dvPair := types.DVPair{DelegatorAddress: ubd.DelegatorAddress, ValidatorAddress: ubd.ValidatorAddress} if len(timeSlice) == 0 { k.SetUBDQueueTimeSlice(ctx, completionTime, []types.DVPair{dvPair}) } else { @@ -333,10 +333,10 @@ func (k Keeper) HasMaxRedelegationEntries(ctx sdk.Context, func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) bz := types.MustMarshalRED(k.cdc, red) - key := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) + key := GetREDKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress) store.Set(key, bz) - store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) - store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress), []byte{}) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress), []byte{}) } // SetUnbondingDelegationEntry adds an entry to the unbonding delegation at @@ -376,10 +376,10 @@ func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red t // 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) + redKey := GetREDKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress) store.Delete(redKey) - store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) - store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddress, red.ValidatorSrcAddress, red.ValidatorDstAddress)) } // redelegation queue timeslice operations @@ -409,9 +409,9 @@ func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation, timeSlice := k.GetRedelegationQueueTimeSlice(ctx, completionTime) dvvTriplet := types.DVVTriplet{ - DelegatorAddr: red.DelegatorAddr, - ValidatorSrcAddr: red.ValidatorSrcAddr, - ValidatorDstAddr: red.ValidatorDstAddr} + DelegatorAddress: red.DelegatorAddress, + ValidatorSrcAddress: red.ValidatorSrcAddress, + ValidatorDstAddress: red.ValidatorDstAddress} if len(timeSlice) == 0 { k.SetRedelegationQueueTimeSlice(ctx, completionTime, []types.DVVTriplet{dvvTriplet}) @@ -450,25 +450,25 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.In // In some situations, the exchange rate becomes invalid, e.g. if // Validator loses all tokens due to slashing. In this case, // make all future delegations invalid. - if validator.DelegatorShareExRate().IsZero() { + if validator.InvalidExRate() { return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid(k.Codespace()) } // Get or create the delegation object - delegation, found := k.GetDelegation(ctx, delAddr, validator.OperatorAddr) + delegation, found := k.GetDelegation(ctx, delAddr, validator.OperatorAddress) if !found { - delegation = types.NewDelegation(delAddr, validator.OperatorAddr, sdk.ZeroDec()) + delegation = types.NewDelegation(delAddr, validator.OperatorAddress, sdk.ZeroDec()) } // call the appropriate hook if present if found { - k.BeforeDelegationSharesModified(ctx, delAddr, validator.OperatorAddr) + k.BeforeDelegationSharesModified(ctx, delAddr, validator.OperatorAddress) } else { - k.BeforeDelegationCreated(ctx, delAddr, validator.OperatorAddr) + k.BeforeDelegationCreated(ctx, delAddr, validator.OperatorAddress) } if subtractAccount { - _, err := k.bankKeeper.DelegateCoins(ctx, delegation.DelegatorAddr, sdk.Coins{sdk.NewCoin(k.GetParams(ctx).BondDenom, bondAmt)}) + _, err := k.bankKeeper.DelegateCoins(ctx, delegation.DelegatorAddress, sdk.Coins{sdk.NewCoin(k.GetParams(ctx).BondDenom, bondAmt)}) if err != nil { return sdk.Dec{}, err } @@ -481,7 +481,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.In k.SetDelegation(ctx, delegation) // Call the after-modification hook - k.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + k.AfterDelegationModified(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) return newShares, nil } @@ -513,13 +513,15 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // subtract shares from delegation delegation.Shares = delegation.Shares.Sub(shares) - isValidatorOperator := bytes.Equal(delegation.DelegatorAddr, validator.OperatorAddr) + isValidatorOperator := bytes.Equal(delegation.DelegatorAddress, validator.OperatorAddress) // if the delegation is the operator of the validator and undelegating will decrease the validator's self delegation below their minimum // trigger a jail validator - if isValidatorOperator && !validator.Jailed && validator.DelegatorShareExRate().Mul(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { + if isValidatorOperator && !validator.Jailed && + validator.ShareTokens(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { + k.jailValidator(ctx, validator) - validator = k.mustGetValidator(ctx, validator.OperatorAddr) + validator = k.mustGetValidator(ctx, validator.OperatorAddress) } // remove the delegation @@ -528,7 +530,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA } else { k.SetDelegation(ctx, delegation) // call the after delegation modification hook - k.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + k.AfterDelegationModified(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) } // remove the shares and coins from the validator @@ -536,7 +538,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period - k.RemoveValidator(ctx, validator.OperatorAddr) + k.RemoveValidator(ctx, validator.OperatorAddress) } return amount, nil @@ -627,7 +629,7 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, // track undelegation only when remaining or truncated shares are non-zero if !entry.Balance.IsZero() { - _, err := k.bankKeeper.UndelegateCoins(ctx, ubd.DelegatorAddr, sdk.Coins{sdk.NewCoin(k.GetParams(ctx).BondDenom, entry.Balance)}) + _, err := k.bankKeeper.UndelegateCoins(ctx, ubd.DelegatorAddress, sdk.Coins{sdk.NewCoin(k.GetParams(ctx).BondDenom, entry.Balance)}) if err != nil { return err } diff --git a/x/staking/keeper/delegation_test.go b/x/staking/keeper/delegation_test.go index 45c6320c5..bc9d8e72b 100644 --- a/x/staking/keeper/delegation_test.go +++ b/x/staking/keeper/delegation_test.go @@ -189,7 +189,7 @@ func TestUnbondDelegation(t *testing.T) { keeper.SetDelegation(ctx, delegation) bondTokens := sdk.TokensFromTendermintPower(6) - amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(bondTokens)) + amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], bondTokens.ToDec()) require.NoError(t, err) require.Equal(t, bondTokens, amount) // shares to be added to an unbonding delegation @@ -207,7 +207,7 @@ func TestUnbondDelegation(t *testing.T) { } func TestUnbondingDelegationsMaxEntries(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 1) pool := keeper.GetPool(ctx) startTokens := sdk.TokensFromTendermintPower(10) pool.NotBondedTokens = startTokens @@ -285,7 +285,7 @@ func TestUndelegateSelfDelegationBelowMinSelfDelegation(t *testing.T) { keeper.SetDelegation(ctx, delegation) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.TokensFromTendermintPower(6))) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.TokensFromTendermintPower(6).ToDec()) require.NoError(t, err) // end block @@ -337,7 +337,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { // unbond the all self-delegation to put validator in unbonding state val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(valTokens)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], valTokens.ToDec()) require.NoError(t, err) // end block @@ -372,7 +372,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { } func TestUndelegateFromUnbondedValidator(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 0) + ctx, _, keeper := CreateTestInput(t, false, 1) pool := keeper.GetPool(ctx) startTokens := sdk.TokensFromTendermintPower(20) pool.NotBondedTokens = startTokens @@ -405,7 +405,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(valTokens)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], valTokens.ToDec()) require.NoError(t, err) // end block @@ -429,7 +429,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { // unbond some of the other delegation's shares unbondTokens := sdk.TokensFromTendermintPower(6) - _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(unbondTokens)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], unbondTokens.ToDec()) require.NoError(t, err) // no ubd should have been found, coins should have been returned direcly to account @@ -438,7 +438,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { // unbond rest of the other delegation's shares remainingTokens := delTokens.Sub(unbondTokens) - _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(remainingTokens)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], remainingTokens.ToDec()) require.NoError(t, err) // now validator should now be deleted from state @@ -480,7 +480,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(valTokens)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], valTokens.ToDec()) require.NoError(t, err) // end block @@ -488,7 +488,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { require.Equal(t, 1, len(updates)) // unbond all the remaining delegation - _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(delTokens)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], delTokens.ToDec()) require.NoError(t, err) // validator should still be in state and still be in unbonding state @@ -702,7 +702,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { delegation := types.NewDelegation(addrDels[0], addrVals[0], issuedShares) keeper.SetDelegation(ctx, delegation) - _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDecFromInt(delTokens)) + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], delTokens.ToDec()) require.NoError(t, err) // end block @@ -760,7 +760,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(delTokens)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], delTokens.ToDec()) require.NoError(t, err) // end block @@ -783,7 +783,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { // unbond some of the other delegation's shares redelegateTokens := sdk.TokensFromTendermintPower(6) - _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDecFromInt(redelegateTokens)) + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], redelegateTokens.ToDec()) require.NoError(t, err) // retrieve the unbonding delegation @@ -836,7 +836,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(delTokens)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], delTokens.ToDec()) require.NoError(t, err) // end block @@ -854,7 +854,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { // redelegate some of the delegation's shares redelegationTokens := sdk.TokensFromTendermintPower(6) - _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDecFromInt(redelegationTokens)) + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], redelegationTokens.ToDec()) require.NoError(t, err) // no red should have been found diff --git a/x/staking/keeper/keeper_test.go b/x/staking/keeper/keeper_test.go index 89ffe13d1..30feb8b28 100644 --- a/x/staking/keeper/keeper_test.go +++ b/x/staking/keeper/keeper_test.go @@ -30,11 +30,11 @@ func TestPool(t *testing.T) { //check that the empty keeper loads the default resPool := keeper.GetPool(ctx) - require.True(t, expPool.Equal(resPool)) + require.Equal(t, expPool, resPool) //modify a params, save, and retrieve expPool.BondedTokens = sdk.NewInt(777) keeper.SetPool(ctx, expPool) resPool = keeper.GetPool(ctx) - require.True(t, expPool.Equal(resPool)) + require.Equal(t, expPool, resPool) } diff --git a/x/staking/keeper/key.go b/x/staking/keeper/key.go index c8b9e5fe5..0a97741ae 100644 --- a/x/staking/keeper/key.go +++ b/x/staking/keeper/key.go @@ -86,7 +86,7 @@ func getValidatorPowerRank(validator types.Validator) []byte { key[0] = ValidatorsByPowerIndexKey[0] copy(key[1:powerBytesLen+1], powerBytes) - operAddrInvr := cp(validator.OperatorAddr) + operAddrInvr := cp(validator.OperatorAddress) for i, b := range operAddrInvr { operAddrInvr[i] = ^b } diff --git a/x/staking/keeper/query_utils.go b/x/staking/keeper/query_utils.go index 6e5e3a0dc..73d93c4e9 100644 --- a/x/staking/keeper/query_utils.go +++ b/x/staking/keeper/query_utils.go @@ -19,7 +19,7 @@ func (k Keeper) GetDelegatorValidators(ctx sdk.Context, delegatorAddr sdk.AccAdd for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) - validator, found := k.GetValidator(ctx, delegation.ValidatorAddr) + validator, found := k.GetValidator(ctx, delegation.ValidatorAddress) if !found { panic(types.ErrNoValidatorFound(types.DefaultCodespace)) } @@ -38,7 +38,7 @@ func (k Keeper) GetDelegatorValidator(ctx sdk.Context, delegatorAddr sdk.AccAddr return validator, types.ErrNoDelegation(types.DefaultCodespace) } - validator, found = k.GetValidator(ctx, delegation.ValidatorAddr) + validator, found = k.GetValidator(ctx, delegation.ValidatorAddress) if !found { panic(types.ErrNoValidatorFound(types.DefaultCodespace)) } @@ -98,10 +98,10 @@ func (k Keeper) GetAllRedelegations(ctx sdk.Context, delegator sdk.AccAddress, for ; iterator.Valid(); iterator.Next() { redelegation := types.MustUnmarshalRED(k.cdc, iterator.Value()) - if srcValFilter && !(srcValAddress.Equals(redelegation.ValidatorSrcAddr)) { + if srcValFilter && !(srcValAddress.Equals(redelegation.ValidatorSrcAddress)) { continue } - if dstValFilter && !(dstValAddress.Equals(redelegation.ValidatorDstAddr)) { + if dstValFilter && !(dstValAddress.Equals(redelegation.ValidatorDstAddress)) { continue } redelegations = append(redelegations, redelegation) diff --git a/x/staking/keeper/slash.go b/x/staking/keeper/slash.go index 9793883f0..dbd19a941 100644 --- a/x/staking/keeper/slash.go +++ b/x/staking/keeper/slash.go @@ -30,7 +30,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // Amount of slashing = slash slashFactor * power at time of infraction amount := sdk.TokensFromTendermintPower(power) - slashAmountDec := sdk.NewDecFromInt(amount).Mul(slashFactor) + slashAmountDec := amount.ToDec().Mul(slashFactor) slashAmount := slashAmountDec.TruncateInt() // ref https://github.com/cosmos/cosmos-sdk/issues/1348 @@ -57,17 +57,6 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // call the before-modification hook k.BeforeValidatorModified(ctx, operatorAddress) - // we need to calculate the *effective* slash fraction for distribution - if validator.Tokens.GT(sdk.ZeroInt()) { - effectiveFraction := slashAmountDec.Quo(sdk.NewDecFromInt(validator.Tokens)) - // possible if power has changed - if effectiveFraction.GT(sdk.OneDec()) { - effectiveFraction = sdk.OneDec() - } - // call the before-slashed hook - k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction) - } - // Track remaining slash amount for the validator // This will decrease when we slash unbondings and // redelegations, as that stake has since unbonded @@ -115,6 +104,17 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive. + // we need to calculate the *effective* slash fraction for distribution + if validator.Tokens.GT(sdk.ZeroInt()) { + effectiveFraction := tokensToBurn.ToDec().QuoRoundUp(validator.Tokens.ToDec()) + // possible if power has changed + if effectiveFraction.GT(sdk.OneDec()) { + effectiveFraction = sdk.OneDec() + } + // call the before-slashed hook + k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction) + } + // Deduct from validator's bonded tokens and update the validator. // The deducted tokens are returned to pool.NotBondedTokens. // TODO: Move the token accounting outside of `RemoveValidatorTokens` so it is less confusing @@ -241,7 +241,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if sharesToUnbond.IsZero() { continue } - delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr) + delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddress, redelegation.ValidatorDstAddress) if !found { // If deleted, delegation has zero shares, and we can't unbond any more continue @@ -250,7 +250,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re sharesToUnbond = delegation.Shares } - tokensToBurn, err := k.unbond(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr, sharesToUnbond) + tokensToBurn, err := k.unbond(ctx, redelegation.DelegatorAddress, redelegation.ValidatorDstAddress, sharesToUnbond) if err != nil { panic(fmt.Errorf("error unbonding delegator: %v", err)) } diff --git a/x/staking/keeper/slash_test.go b/x/staking/keeper/slash_test.go index b2cec2ce8..99f68a332 100644 --- a/x/staking/keeper/slash_test.go +++ b/x/staking/keeper/slash_test.go @@ -195,7 +195,7 @@ func TestSlashAtNegativeHeight(t *testing.T) { updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) - validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) + validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) // power decreased require.Equal(t, int64(5), validator.GetTendermintPower()) // pool bonded shares decreased @@ -222,7 +222,7 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) - validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) + validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) // power decreased require.Equal(t, int64(5), validator.GetTendermintPower()) // pool bonded shares decreased @@ -344,11 +344,11 @@ func TestSlashWithRedelegation(t *testing.T) { // set a redelegation rdTokens := sdk.TokensFromTendermintPower(6) rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, - time.Unix(0, 0), rdTokens, sdk.NewDecFromInt(rdTokens)) + time.Unix(0, 0), rdTokens, rdTokens.ToDec()) keeper.SetRedelegation(ctx, rd) // set the associated delegation - del := types.NewDelegation(addrDels[0], addrVals[1], sdk.NewDecFromInt(rdTokens)) + del := types.NewDelegation(addrDels[0], addrVals[1], rdTokens.ToDec()) keeper.SetDelegation(ctx, del) // update bonded tokens @@ -453,11 +453,11 @@ func TestSlashBoth(t *testing.T) { rdATokens := sdk.TokensFromTendermintPower(6) rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdATokens, - sdk.NewDecFromInt(rdATokens)) + rdATokens.ToDec()) keeper.SetRedelegation(ctx, rdA) // set the associated delegation - delA := types.NewDelegation(addrDels[0], addrVals[1], sdk.NewDecFromInt(rdATokens)) + delA := types.NewDelegation(addrDels[0], addrVals[1], rdATokens.ToDec()) keeper.SetDelegation(ctx, delA) // set an unbonding delegation with expiration timestamp (beyond which the diff --git a/x/staking/keeper/test_common.go b/x/staking/keeper/test_common.go index 853583acb..8edbe8e36 100644 --- a/x/staking/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -74,8 +74,9 @@ func MakeTestCodec() *codec.Codec { return cdc } -// hogpodge of all sorts of input required for testing -// init power is converted to an amount of tokens +// Hogpodge of all sorts of input required for testing. +// `initPower` is converted to an amount of tokens. +// If `initPower` is 0, no addrs get created. func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context, auth.AccountKeeper, Keeper) { initCoins := sdk.TokensFromTendermintPower(initPower) @@ -128,9 +129,12 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range Addrs { pool := keeper.GetPool(ctx) - _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.BondDenom(ctx), initCoins}, - }) + err := error(nil) + if !initCoins.IsZero() { + _, _, err = ck.AddCoins(ctx, addr, sdk.Coins{ + {keeper.BondDenom(ctx), initCoins}, + }) + } require.Nil(t, err) pool.NotBondedTokens = pool.NotBondedTokens.Add(initCoins) keeper.SetPool(ctx, pool) @@ -226,7 +230,7 @@ func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Vali deleted := false for ; iterator.Valid(); iterator.Next() { valAddr := parseValidatorPowerRankKey(iterator.Key()) - if bytes.Equal(valAddr, validator.OperatorAddr) { + if bytes.Equal(valAddr, validator.OperatorAddress) { if deleted { panic("found duplicate power index key") } else { @@ -239,7 +243,7 @@ func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Vali keeper.SetValidatorByPowerIndex(ctx, validator) if apply { keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validator, found := keeper.GetValidator(ctx, validator.OperatorAddr) + validator, found := keeper.GetValidator(ctx, validator.OperatorAddress) if !found { panic("validator expected but not found") } @@ -247,7 +251,7 @@ func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Vali } cachectx, _ := ctx.CacheContext() keeper.ApplyAndReturnValidatorSetUpdates(cachectx) - validator, found := keeper.GetValidator(cachectx, validator.OperatorAddr) + validator, found := keeper.GetValidator(cachectx, validator.OperatorAddress) if !found { panic("validator expected but not found") } diff --git a/x/staking/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go index b9b94aa1b..37fecd6b8 100644 --- a/x/staking/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -189,7 +189,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. k.DeleteValidatorQueue(ctx, validator) // trigger hook - k.AfterValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.AfterValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddress) return validator } @@ -224,7 +224,7 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat k.InsertValidatorQueue(ctx, validator) // trigger hook - k.AfterValidatorBeginUnbonding(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.AfterValidatorBeginUnbonding(ctx, validator.ConsAddress(), validator.OperatorAddress) return validator } diff --git a/x/staking/keeper/validator.go b/x/staking/keeper/validator.go index 0d8b07c28..cf9b8a5b7 100644 --- a/x/staking/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -38,7 +38,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty if val, ok := k.validatorCache[strValue]; ok { valToReturn := val.val // Doesn't mutate the cache's value - valToReturn.OperatorAddr = addr + valToReturn.OperatorAddress = addr return valToReturn, true } @@ -88,14 +88,14 @@ func (k Keeper) mustGetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAdd func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) bz := types.MustMarshalValidator(k.cdc, validator) - store.Set(GetValidatorKey(validator.OperatorAddr), bz) + store.Set(GetValidatorKey(validator.OperatorAddress), bz) } // validator index func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) consAddr := sdk.ConsAddress(validator.ConsPubKey.Address()) - store.Set(GetValidatorByConsAddrKey(consAddr), validator.OperatorAddr) + store.Set(GetValidatorByConsAddrKey(consAddr), validator.OperatorAddress) } // validator index @@ -105,7 +105,7 @@ func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Valida return } store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsByPowerIndexKey(validator), validator.OperatorAddr) + store.Set(GetValidatorsByPowerIndexKey(validator), validator.OperatorAddress) } // validator index @@ -117,7 +117,7 @@ func (k Keeper) DeleteValidatorByPowerIndex(ctx sdk.Context, validator types.Val // validator index func (k Keeper) SetNewValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsByPowerIndexKey(validator), validator.OperatorAddr) + store.Set(GetValidatorsByPowerIndexKey(validator), validator.OperatorAddress) } // Update the tokens of an existing validator, update the validators power index key @@ -204,7 +204,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store.Delete(GetValidatorsByPowerIndexKey(validator)) // call hooks - k.AfterValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.AfterValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddress) } // get groups of validators @@ -378,9 +378,9 @@ func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime) var keys []sdk.ValAddress if len(timeSlice) == 0 { - keys = []sdk.ValAddress{val.OperatorAddr} + keys = []sdk.ValAddress{val.OperatorAddress} } else { - keys = append(timeSlice, val.OperatorAddr) + keys = append(timeSlice, val.OperatorAddress) } k.SetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime, keys) } @@ -390,7 +390,7 @@ func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) { timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime) newTimeSlice := []sdk.ValAddress{} for _, addr := range timeSlice { - if !bytes.Equal(addr, val.OperatorAddr) { + if !bytes.Equal(addr, val.OperatorAddress) { newTimeSlice = append(newTimeSlice, addr) } } @@ -437,7 +437,7 @@ func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { } k.unbondingToUnbonded(ctx, val) if val.GetDelegatorShares().IsZero() { - k.RemoveValidator(ctx, val.OperatorAddr) + k.RemoveValidator(ctx, val.OperatorAddress) } } store.Delete(validatorTimesliceIterator.Key()) diff --git a/x/staking/keeper/validator_test.go b/x/staking/keeper/validator_test.go index 1528b1681..d2aa5b270 100644 --- a/x/staking/keeper/validator_test.go +++ b/x/staking/keeper/validator_test.go @@ -146,7 +146,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { // validator and next in line cliff validator keeper.DeleteValidatorByPowerIndex(ctx, nextCliffVal) shares := sdk.TokensFromTendermintPower(21) - nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, sdk.NewDecFromInt(shares)) + nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, shares.ToDec()) keeper.SetPool(ctx, pool) nextCliffVal = TestingUpdateValidator(keeper, ctx, nextCliffVal, true) @@ -157,7 +157,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { // require all the validators have their respective statuses for valIdx, status := range expectedValStatus { - valAddr := validators[valIdx].OperatorAddr + valAddr := validators[valIdx].OperatorAddress val, _ := keeper.GetValidator(ctx, valAddr) assert.Equal( @@ -253,7 +253,7 @@ func TestValidatorBasics(t *testing.T) { // modify a records, save, and retrieve validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.TokensFromTendermintPower(10) - validators[0].DelegatorShares = sdk.NewDecFromInt(validators[0].Tokens) + validators[0].DelegatorShares = validators[0].Tokens.ToDec() validators[0] = TestingUpdateValidator(keeper, ctx, validators[0], true) resVal, found = keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) @@ -284,18 +284,18 @@ func TestValidatorBasics(t *testing.T) { // shouldn't be able to remove if status is not unbonded assert.PanicsWithValue(t, "cannot call RemoveValidator on bonded or unbonding validators", - func() { keeper.RemoveValidator(ctx, validators[1].OperatorAddr) }) + func() { keeper.RemoveValidator(ctx, validators[1].OperatorAddress) }) // shouldn't be able to remove if there are still tokens left validators[1].Status = sdk.Unbonded keeper.SetValidator(ctx, validators[1]) assert.PanicsWithValue(t, "attempting to remove a validator which still contains tokens", - func() { keeper.RemoveValidator(ctx, validators[1].OperatorAddr) }) + func() { keeper.RemoveValidator(ctx, validators[1].OperatorAddress) }) - validators[1].Tokens = sdk.ZeroInt() // ...remove all tokens - keeper.SetValidator(ctx, validators[1]) // ...set the validator - keeper.RemoveValidator(ctx, validators[1].OperatorAddr) // Now it can be removed. + validators[1].Tokens = sdk.ZeroInt() // ...remove all tokens + keeper.SetValidator(ctx, validators[1]) // ...set the validator + keeper.RemoveValidator(ctx, validators[1].OperatorAddress) // Now it can be removed. _, found = keeper.GetValidator(ctx, addrVals[1]) require.False(t, found) } @@ -324,11 +324,11 @@ func GetValidatorSortingUnmixed(t *testing.T) { assert.Equal(t, sdk.NewInt(100), resValidators[2].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewInt(1), resValidators[3].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewInt(0), resValidators[4].BondedTokens(), "%v", resValidators) - assert.Equal(t, validators[3].OperatorAddr, resValidators[0].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[4].OperatorAddr, resValidators[1].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[1].OperatorAddr, resValidators[2].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[2].OperatorAddr, resValidators[3].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[0].OperatorAddr, resValidators[4].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[3].OperatorAddress, resValidators[0].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[4].OperatorAddress, resValidators[1].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[1].OperatorAddress, resValidators[2].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[2].OperatorAddress, resValidators[3].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[0].OperatorAddress, resValidators[4].OperatorAddress, "%v", resValidators) // test a basic increase in voting power validators[3].Tokens = sdk.NewInt(500) @@ -433,11 +433,11 @@ func GetValidatorSortingMixed(t *testing.T) { assert.Equal(t, sdk.NewInt(100), resValidators[2].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewInt(1), resValidators[3].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewInt(0), resValidators[4].BondedTokens(), "%v", resValidators) - assert.Equal(t, validators[3].OperatorAddr, resValidators[0].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[4].OperatorAddr, resValidators[1].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[1].OperatorAddr, resValidators[2].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[2].OperatorAddr, resValidators[3].OperatorAddr, "%v", resValidators) - assert.Equal(t, validators[0].OperatorAddr, resValidators[4].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[3].OperatorAddress, resValidators[0].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[4].OperatorAddress, resValidators[1].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[1].OperatorAddress, resValidators[2].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[2].OperatorAddress, resValidators[3].OperatorAddress, "%v", resValidators) + assert.Equal(t, validators[0].OperatorAddress, resValidators[4].OperatorAddress, "%v", resValidators) } // TODO separate out into multiple tests @@ -465,7 +465,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { } for i := range powers { - validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) + validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddress) require.True(t, found) } resValidators := keeper.GetBondedValidatorsByPower(ctx) @@ -491,7 +491,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { // validator 3 enters bonded validator set ctx = ctx.WithBlockHeight(40) - validators[3], found = keeper.GetValidator(ctx, validators[3].OperatorAddr) + validators[3], found = keeper.GetValidator(ctx, validators[3].OperatorAddress) require.True(t, found) keeper.DeleteValidatorByPowerIndex(ctx, validators[3]) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(1)) @@ -521,7 +521,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) - _, exists := keeper.GetValidator(ctx, validators[3].OperatorAddr) + _, exists := keeper.GetValidator(ctx, validators[3].OperatorAddress) require.True(t, exists) } @@ -597,7 +597,7 @@ func TestFullValidatorSetPowerChange(t *testing.T) { } for i := range powers { var found bool - validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) + validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddress) require.True(t, found) } assert.Equal(t, sdk.Unbonded, validators[0].Status) @@ -649,8 +649,8 @@ func TestApplyAndReturnValidatorSetUpdatesAllNone(t *testing.T) { updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) assert.Equal(t, 2, len(updates)) - validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) - validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddress) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddress) assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) } @@ -765,7 +765,7 @@ func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { keeper.SetValidator(ctx, validators[2]) keeper.SetValidatorByPowerIndex(ctx, validators[2]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddress) require.Equal(t, 1, len(updates)) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) @@ -774,7 +774,7 @@ func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { keeper.SetValidator(ctx, validators[3]) keeper.SetValidatorByPowerIndex(ctx, validators[3]) updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validators[3], _ = keeper.GetValidator(ctx, validators[3].OperatorAddr) + validators[3], _ = keeper.GetValidator(ctx, validators[3].OperatorAddress) require.Equal(t, 1, len(updates)) require.Equal(t, validators[3].ABCIValidatorUpdate(), updates[0]) @@ -783,7 +783,7 @@ func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { keeper.SetValidator(ctx, validators[4]) keeper.SetValidatorByPowerIndex(ctx, validators[4]) updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validators[4], _ = keeper.GetValidator(ctx, validators[4].OperatorAddr) + validators[4], _ = keeper.GetValidator(ctx, validators[4].OperatorAddress) require.Equal(t, 1, len(updates)) require.Equal(t, validators[4].ABCIValidatorUpdate(), updates[0]) } @@ -825,7 +825,7 @@ func TestApplyAndReturnValidatorSetUpdatesWithCliffValidator(t *testing.T) { keeper.SetValidator(ctx, validators[2]) keeper.SetValidatorByPowerIndex(ctx, validators[2]) updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddress) require.Equal(t, 2, len(updates), "%v", updates) require.Equal(t, validators[0].ABCIValidatorUpdateZero(), updates[1]) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) @@ -857,8 +857,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { pool := keeper.GetPool(ctx) delTokens1 := sdk.TokensFromTendermintPower(20) delTokens2 := sdk.TokensFromTendermintPower(30) - validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewDecFromInt(delTokens1)) - validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewDecFromInt(delTokens2)) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, delTokens1.ToDec()) + validators[1], pool, _ = validators[1].RemoveDelShares(pool, delTokens2.ToDec()) keeper.SetPool(ctx, pool) validators[0] = TestingUpdateValidator(keeper, ctx, validators[0], false) validators[1] = TestingUpdateValidator(keeper, ctx, validators[1], false) @@ -902,8 +902,8 @@ func TestApplyAndReturnValidatorSetUpdatesNewValidator(t *testing.T) { // verify initial Tendermint updates are correct updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, len(validators), len(updates)) - validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) - validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddress) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddress) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) @@ -934,7 +934,7 @@ func TestApplyAndReturnValidatorSetUpdatesNewValidator(t *testing.T) { keeper.SetPool(ctx, pool) keeper.SetValidator(ctx, validator) - validator, pool, _ = validator.RemoveDelShares(pool, sdk.NewDecFromInt(amt)) + validator, pool, _ = validator.RemoveDelShares(pool, amt.ToDec()) keeper.SetValidator(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator) @@ -951,9 +951,9 @@ func TestApplyAndReturnValidatorSetUpdatesNewValidator(t *testing.T) { // verify initial Tendermint updates are correct updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) - validator, _ = keeper.GetValidator(ctx, validator.OperatorAddr) - validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) - validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validator, _ = keeper.GetValidator(ctx, validator.OperatorAddress) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddress) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddress) require.Equal(t, len(validators)+1, len(updates)) require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) @@ -988,8 +988,8 @@ func TestApplyAndReturnValidatorSetUpdatesBondTransition(t *testing.T) { // verify initial Tendermint updates are correct updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) - validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) - validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddress) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddress) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) @@ -1000,7 +1000,7 @@ func TestApplyAndReturnValidatorSetUpdatesBondTransition(t *testing.T) { pool := keeper.GetPool(ctx) var found bool - validators[0], found = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[0], found = keeper.GetValidator(ctx, validators[0].OperatorAddress) require.True(t, found) keeper.DeleteValidatorByPowerIndex(ctx, validators[0]) @@ -1018,7 +1018,7 @@ func TestApplyAndReturnValidatorSetUpdatesBondTransition(t *testing.T) { ctx = ctx.WithBlockHeight(2) pool = keeper.GetPool(ctx) - validators[1], found = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validators[1], found = keeper.GetValidator(ctx, validators[1].OperatorAddress) require.True(t, found) keeper.DeleteValidatorByPowerIndex(ctx, validators[0]) @@ -1083,7 +1083,7 @@ func TestUpdateValidatorCommission(t *testing.T) { } else { tc.validator.Commission = commission keeper.SetValidator(ctx, tc.validator) - val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddr) + val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddress) require.True(t, found, "expected to find validator for test case #%d with rate: %s", i, tc.newRate, diff --git a/x/staking/querier/querier.go b/x/staking/querier/querier.go index e842c2197..4230dc6a4 100644 --- a/x/staking/querier/querier.go +++ b/x/staking/querier/querier.go @@ -1,6 +1,8 @@ package querier import ( + "fmt" + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" @@ -142,7 +144,7 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } validator, found := k.GetValidator(ctx, params.ValidatorAddr) @@ -162,7 +164,7 @@ func queryValidatorDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.Reque errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } delegations := k.GetValidatorDelegations(ctx, params.ValidatorAddr) @@ -179,7 +181,7 @@ func queryValidatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req a errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } unbonds := k.GetUnbondingDelegationsFromValidator(ctx, params.ValidatorAddr) @@ -196,7 +198,7 @@ func queryDelegatorDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.Reque errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } delegations := k.GetAllDelegatorDelegations(ctx, params.DelegatorAddr) @@ -213,7 +215,7 @@ func queryDelegatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req a errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } unbondingDelegations := k.GetAllUnbondingDelegations(ctx, params.DelegatorAddr) @@ -232,7 +234,7 @@ func queryDelegatorValidators(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } validators := k.GetDelegatorValidators(ctx, params.DelegatorAddr, stakingParams.MaxValidators) @@ -249,7 +251,7 @@ func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.Request errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } validator, err := k.GetDelegatorValidator(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -269,7 +271,7 @@ func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } delegation, found := k.GetDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -289,7 +291,7 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") + return []byte{}, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } unbond, found := k.GetUnbondingDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) diff --git a/x/staking/querier/querier_test.go b/x/staking/querier/querier_test.go index a2817335c..f4fe9592b 100644 --- a/x/staking/querier/querier_test.go +++ b/x/staking/querier/querier_test.go @@ -298,7 +298,7 @@ func TestQueryDelegation(t *testing.T) { // Query unbonging delegation unbondingTokens := sdk.TokensFromTendermintPower(10) - _, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDecFromInt(unbondingTokens)) + _, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddress, unbondingTokens.ToDec()) require.Nil(t, err) queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1) @@ -351,13 +351,13 @@ func TestQueryDelegation(t *testing.T) { // Query redelegation redelegationTokens := sdk.TokensFromTendermintPower(10) - _, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddr, - val2.OperatorAddr, sdk.NewDecFromInt(redelegationTokens)) + _, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddress, + val2.OperatorAddress, redelegationTokens.ToDec()) require.Nil(t, err) - redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr) + redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddress, val2.OperatorAddress) require.True(t, found) - bz, errRes = cdc.MarshalJSON(NewQueryRedelegationParams(addrAcc2, val1.OperatorAddr, val2.OperatorAddr)) + bz, errRes = cdc.MarshalJSON(NewQueryRedelegationParams(addrAcc2, val1.OperatorAddress, val2.OperatorAddress)) require.Nil(t, errRes) query = abci.RequestQuery{ @@ -390,10 +390,10 @@ func TestQueryRedelegations(t *testing.T) { _ = keeper.ApplyAndReturnValidatorSetUpdates(ctx) rdAmount := sdk.TokensFromTendermintPower(20) - keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), sdk.NewDecFromInt(rdAmount)) + keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), rdAmount.ToDec()) keeper.ApplyAndReturnValidatorSetUpdates(ctx) - redelegation, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr) + redelegation, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddress, val2.OperatorAddress) require.True(t, found) // delegator redelegations diff --git a/x/staking/simulation/invariants.go b/x/staking/simulation/invariants.go index 1368a61dc..db6249719 100644 --- a/x/staking/simulation/invariants.go +++ b/x/staking/simulation/invariants.go @@ -51,44 +51,43 @@ func SupplyInvariants(k staking.Keeper, loose := sdk.ZeroDec() bonded := sdk.ZeroDec() am.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(k.BondDenom(ctx)))) + loose = loose.Add(acc.GetCoins().AmountOf(k.BondDenom(ctx)).ToDec()) return false }) k.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) bool { for _, entry := range ubd.Entries { - loose = loose.Add(sdk.NewDecFromInt(entry.Balance)) + loose = loose.Add(entry.Balance.ToDec()) } return false }) k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { switch validator.GetStatus() { case sdk.Bonded: - bonded = bonded.Add(sdk.NewDecFromInt(validator.GetBondedTokens())) + bonded = bonded.Add(validator.GetBondedTokens().ToDec()) case sdk.Unbonding, sdk.Unbonded: - loose = loose.Add(sdk.NewDecFromInt(validator.GetTokens())) + loose = loose.Add(validator.GetTokens().ToDec()) } + // add yet-to-be-withdrawn + loose = loose.Add(d.GetValidatorOutstandingRewardsCoins(ctx, validator.GetOperator()).AmountOf(k.BondDenom(ctx))) return false }) // add outstanding fees - loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(k.BondDenom(ctx)))) + loose = loose.Add(f.GetCollectedFees(ctx).AmountOf(k.BondDenom(ctx)).ToDec()) // add community pool loose = loose.Add(d.GetFeePoolCommunityCoins(ctx).AmountOf(k.BondDenom(ctx))) - // add yet-to-be-withdrawn - loose = loose.Add(d.GetOutstandingRewardsCoins(ctx).AmountOf(k.BondDenom(ctx))) - // Not-bonded tokens should equal coin supply plus unbonding delegations // plus tokens on unbonded validators - if !sdk.NewDecFromInt(pool.NotBondedTokens).Equal(loose) { + if !pool.NotBondedTokens.ToDec().Equal(loose) { return fmt.Errorf("loose token invariance:\n"+ "\tpool.NotBondedTokens: %v\n"+ "\tsum of account tokens: %v", pool.NotBondedTokens, loose) } // Bonded tokens should equal sum of tokens with bonded validators - if !sdk.NewDecFromInt(pool.BondedTokens).Equal(bonded) { + if !pool.BondedTokens.ToDec().Equal(bonded) { return fmt.Errorf("bonded token invariance:\n"+ "\tpool.BondedTokens: %v\n"+ "\tsum of account tokens: %v", pool.BondedTokens, bonded) diff --git a/x/staking/simulation/msgs.go b/x/staking/simulation/msgs.go index 69be7d330..559b0ae96 100644 --- a/x/staking/simulation/msgs.go +++ b/x/staking/simulation/msgs.go @@ -82,6 +82,9 @@ func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation { Details: simulation.RandStringOfLength(r, 10), } + if len(k.GetAllValidators(ctx)) == 0 { + return noOperation, nil, nil + } val := keeper.RandomValidator(r, k, ctx) address := val.GetOperator() newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate) @@ -110,6 +113,9 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Oper action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom + if len(k.GetAllValidators(ctx)) == 0 { + return noOperation, nil, nil + } val := keeper.RandomValidator(r, k, ctx) validatorAddress := val.GetOperator() delegatorAcc := simulation.RandomAcc(r, accs) @@ -119,7 +125,7 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Oper amount = simulation.RandomAmount(r, amount) } if amount.Equal(sdk.ZeroInt()) { - return "no-operation", nil, nil + return noOperation, nil, nil } msg := staking.NewMsgDelegate( @@ -159,9 +165,9 @@ func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Op return noOperation, nil, nil } msg := staking.MsgUndelegate{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: delegation.ValidatorAddr, - SharesAmount: numShares, + DelegatorAddress: delegatorAddress, + ValidatorAddress: delegation.ValidatorAddress, + SharesAmount: numShares, } if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v", @@ -186,6 +192,9 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom + if len(k.GetAllValidators(ctx)) == 0 { + return noOperation, nil, nil + } srcVal := keeper.RandomValidator(r, k, ctx) srcValidatorAddress := srcVal.GetOperator() destVal := keeper.RandomValidator(r, k, ctx) @@ -201,10 +210,10 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati return noOperation, nil, nil } msg := staking.MsgBeginRedelegate{ - DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: srcValidatorAddress, - ValidatorDstAddr: destValidatorAddress, - SharesAmount: sdk.NewDecFromInt(amount), + DelegatorAddress: delegatorAddress, + ValidatorSrcAddress: srcValidatorAddress, + ValidatorDstAddress: destValidatorAddress, + SharesAmount: amount.ToDec(), } if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) @@ -215,7 +224,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati write() } event(fmt.Sprintf("staking/MsgBeginRedelegate/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgBeginRedelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil } } diff --git a/x/staking/test_common.go b/x/staking/test_common.go index 2ddc53447..634d924ac 100644 --- a/x/staking/test_common.go +++ b/x/staking/test_common.go @@ -2,7 +2,7 @@ package staking import ( "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -10,12 +10,12 @@ import ( ) var ( - priv1 = ed25519.GenPrivKey() + priv1 = secp256k1.GenPrivKey() addr1 = sdk.AccAddress(priv1.PubKey().Address()) - priv2 = ed25519.GenPrivKey() + priv2 = secp256k1.GenPrivKey() addr2 = sdk.AccAddress(priv2.PubKey().Address()) - addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - priv4 = ed25519.GenPrivKey() + addr3 = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + priv4 = secp256k1.GenPrivKey() addr4 = sdk.AccAddress(priv4.PubKey().Address()) coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} fee = auth.NewStdFee( @@ -54,10 +54,3 @@ func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk. amount := sdk.NewCoin(sdk.DefaultBondDenom, amt) return NewMsgDelegate(delAddr, valAddr, amount) } - -func NewTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, - valPubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator { - - amount := sdk.NewCoin(sdk.DefaultBondDenom, amt) - return NewMsgCreateValidatorOnBehalfOf(delAddr, valAddr, valPubKey, amount, Description{}, commissionMsg, sdk.OneInt()) -} diff --git a/x/staking/types/codec.go b/x/staking/types/codec.go index c290e0d14..9b43705f8 100644 --- a/x/staking/types/codec.go +++ b/x/staking/types/codec.go @@ -9,8 +9,8 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) - cdc.RegisterConcrete(MsgUndelegate{}, "cosmos-sdk/Undelegate", nil) - cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) + cdc.RegisterConcrete(MsgUndelegate{}, "cosmos-sdk/MsgUndelegate", nil) + cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/MsgBeginRedelegate", nil) } // generic sealed codec to be used throughout sdk diff --git a/x/staking/types/delegation.go b/x/staking/types/delegation.go index b12feb547..2d8647ba8 100644 --- a/x/staking/types/delegation.go +++ b/x/staking/types/delegation.go @@ -14,26 +14,26 @@ import ( // It is intended to be used as a marshalable pointer. For example, a DVPair can be used to construct the // key to getting an UnbondingDelegation from state. type DVPair struct { - DelegatorAddr sdk.AccAddress - ValidatorAddr sdk.ValAddress + DelegatorAddress sdk.AccAddress + ValidatorAddress sdk.ValAddress } // DVVTriplet is struct that just has a delegator-validator-validator triplet with no other data. // It is intended to be used as a marshalable pointer. For example, a DVVTriplet can be used to construct the // key to getting a Redelegation from state. type DVVTriplet struct { - DelegatorAddr sdk.AccAddress - ValidatorSrcAddr sdk.ValAddress - ValidatorDstAddr sdk.ValAddress + DelegatorAddress sdk.AccAddress + ValidatorSrcAddress sdk.ValAddress + ValidatorDstAddress sdk.ValAddress } // 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 // validator. type Delegation struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Shares sdk.Dec `json:"shares"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Shares sdk.Dec `json:"shares"` } // NewDelegation creates a new delegation object @@ -41,9 +41,9 @@ func NewDelegation(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress, shares sdk.Dec) Delegation { return Delegation{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Shares: shares, + DelegatorAddress: delegatorAddr, + ValidatorAddress: validatorAddr, + Shares: shares, } } @@ -69,8 +69,8 @@ func UnmarshalDelegation(cdc *codec.Codec, value []byte) (delegation Delegation, // nolint func (d Delegation) Equal(d2 Delegation) bool { - return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && - bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && + return bytes.Equal(d.DelegatorAddress, d2.DelegatorAddress) && + bytes.Equal(d.ValidatorAddress, d2.ValidatorAddress) && d.Shares.Equal(d2.Shares) } @@ -78,8 +78,8 @@ func (d Delegation) Equal(d2 Delegation) bool { var _ sdk.Delegation = Delegation{} // nolint - for sdk.Delegation -func (d Delegation) GetDelegatorAddr() sdk.AccAddress { return d.DelegatorAddr } -func (d Delegation) GetValidatorAddr() sdk.ValAddress { return d.ValidatorAddr } +func (d Delegation) GetDelegatorAddr() sdk.AccAddress { return d.DelegatorAddress } +func (d Delegation) GetValidatorAddr() sdk.ValAddress { return d.ValidatorAddress } func (d Delegation) GetShares() sdk.Dec { return d.Shares } // String returns a human readable string representation of a Delegation. @@ -87,8 +87,8 @@ func (d Delegation) String() string { return fmt.Sprintf(`Delegation: Delegator: %s Validator: %s - Shares: %s`, d.DelegatorAddr, - d.ValidatorAddr, d.Shares) + Shares: %s`, d.DelegatorAddress, + d.ValidatorAddress, d.Shares) } // Delegations is a collection of delegations @@ -104,9 +104,9 @@ func (d Delegations) String() (out string) { // UnbondingDelegation stores all of a single delegator's unbonding bonds // for a single validator in an time-ordered list type UnbondingDelegation struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // validator unbonding from operator addr - Entries []UnbondingDelegationEntry `json:"entries"` // unbonding delegation entries + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // delegator + ValidatorAddress sdk.ValAddress `json:"validator_address"` // validator unbonding from operator addr + Entries []UnbondingDelegationEntry `json:"entries"` // unbonding delegation entries } // UnbondingDelegationEntry - entry to an UnbondingDelegation @@ -129,9 +129,9 @@ func NewUnbondingDelegation(delegatorAddr sdk.AccAddress, entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance) return UnbondingDelegation{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Entries: []UnbondingDelegationEntry{entry}, + DelegatorAddress: delegatorAddr, + ValidatorAddress: validatorAddr, + Entries: []UnbondingDelegationEntry{entry}, } } @@ -193,7 +193,7 @@ func (d UnbondingDelegation) String() string { out := fmt.Sprintf(`Unbonding Delegations between: Delegator: %s Validator: %s - Entries:`, d.DelegatorAddr, d.ValidatorAddr) + Entries:`, d.DelegatorAddress, d.ValidatorAddress) for i, entry := range d.Entries { out += fmt.Sprintf(` Unbonding Delegation %d: Creation Height: %v @@ -218,10 +218,10 @@ func (ubds UnbondingDelegations) String() (out string) { // redelegating bonds from a particular source validator to a // particular destination validator type Redelegation struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator - ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // validator redelegation source operator addr - ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // validator redelegation destination operator addr - Entries []RedelegationEntry `json:"entries"` // redelegation entries + DelegatorAddress sdk.AccAddress `json:"delegator_address"` // delegator + ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` // validator redelegation source operator addr + ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` // validator redelegation destination operator addr + Entries []RedelegationEntry `json:"entries"` // redelegation entries } // RedelegationEntry - entry to a Redelegation @@ -242,10 +242,10 @@ func NewRedelegation(delegatorAddr sdk.AccAddress, validatorSrcAddr, minTime, balance, sharesDst) return Redelegation{ - DelegatorAddr: delegatorAddr, - ValidatorSrcAddr: validatorSrcAddr, - ValidatorDstAddr: validatorDstAddr, - Entries: []RedelegationEntry{entry}, + DelegatorAddress: delegatorAddr, + ValidatorSrcAddress: validatorSrcAddr, + ValidatorDstAddress: validatorDstAddr, + Entries: []RedelegationEntry{entry}, } } @@ -315,7 +315,7 @@ func (d Redelegation) String() string { Delegator: %s Source Validator: %s Destination Validator: %s - Entries:`, d.DelegatorAddr, d.ValidatorSrcAddr, d.ValidatorDstAddr) + Entries:`, d.DelegatorAddress, d.ValidatorSrcAddress, d.ValidatorDstAddress) for i, entry := range d.Entries { out += fmt.Sprintf(` Redelegation %d: Creation height: %v diff --git a/x/staking/types/delegation_test.go b/x/staking/types/delegation_test.go index 55a88b254..b3eec08fd 100644 --- a/x/staking/types/delegation_test.go +++ b/x/staking/types/delegation_test.go @@ -16,7 +16,7 @@ func TestDelegationEqual(t *testing.T) { ok := d1.Equal(d2) require.True(t, ok) - d2.ValidatorAddr = addr3 + d2.ValidatorAddress = addr3 d2.Shares = sdk.NewDec(200) ok = d1.Equal(d2) @@ -36,7 +36,7 @@ func TestUnbondingDelegationEqual(t *testing.T) { ok := ubd1.Equal(ubd2) require.True(t, ok) - ubd2.ValidatorAddr = addr3 + ubd2.ValidatorAddress = addr3 ubd2.Entries[0].CompletionTime = time.Unix(20*20*2, 0) ok = ubd1.Equal(ubd2) diff --git a/x/staking/types/expected_keepers.go b/x/staking/types/expected_keepers.go index affa22070..54c9bad62 100644 --- a/x/staking/types/expected_keepers.go +++ b/x/staking/types/expected_keepers.go @@ -5,7 +5,7 @@ import sdk "github.com/cosmos/cosmos-sdk/types" // expected coin keeper type DistributionKeeper interface { GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins - GetOutstandingRewardsCoins(ctx sdk.Context) sdk.DecCoins + GetValidatorOutstandingRewardsCoins(ctx sdk.Context, val sdk.ValAddress) sdk.DecCoins } // expected fee collection keeper diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index 3602382ad..f9c2eea77 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -25,8 +25,8 @@ type MsgCreateValidator struct { Description Description `json:"description"` Commission CommissionMsg `json:"commission"` MinSelfDelegation sdk.Int `json:"min_self_delegation"` - DelegatorAddr sdk.AccAddress `json:"delegator_address"` - ValidatorAddr sdk.ValAddress `json:"validator_address"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` PubKey crypto.PubKey `json:"pubkey"` Value sdk.Coin `json:"value"` } @@ -35,30 +35,24 @@ type msgCreateValidatorJSON struct { Description Description `json:"description"` Commission CommissionMsg `json:"commission"` MinSelfDelegation sdk.Int `json:"min_self_delegation"` - DelegatorAddr sdk.AccAddress `json:"delegator_address"` - ValidatorAddr sdk.ValAddress `json:"validator_address"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` PubKey string `json:"pubkey"` Value sdk.Coin `json:"value"` } // Default way to create validator. Delegator address and validator address are the same -func NewMsgCreateValidator(valAddr sdk.ValAddress, pubkey crypto.PubKey, - selfDelegation sdk.Coin, description Description, commission CommissionMsg, minSelfDelegation sdk.Int) MsgCreateValidator { +func NewMsgCreateValidator( + valAddr sdk.ValAddress, pubKey crypto.PubKey, selfDelegation sdk.Coin, + description Description, commission CommissionMsg, minSelfDelegation sdk.Int, +) MsgCreateValidator { - return NewMsgCreateValidatorOnBehalfOf( - sdk.AccAddress(valAddr), valAddr, pubkey, selfDelegation, description, commission, minSelfDelegation, - ) -} - -// Creates validator msg by delegator address on behalf of validator address -func NewMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, - pubkey crypto.PubKey, value sdk.Coin, description Description, commission CommissionMsg, minSelfDelegation sdk.Int) MsgCreateValidator { return MsgCreateValidator{ Description: description, - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - PubKey: pubkey, - Value: value, + DelegatorAddress: sdk.AccAddress(valAddr), + ValidatorAddress: valAddr, + PubKey: pubKey, + Value: selfDelegation, Commission: commission, MinSelfDelegation: minSelfDelegation, } @@ -71,12 +65,12 @@ func (msg MsgCreateValidator) Type() string { return "create_validator" } // Return address(es) that must sign over msg.GetSignBytes() func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { // delegator is first signer so delegator pays fees - addrs := []sdk.AccAddress{msg.DelegatorAddr} + addrs := []sdk.AccAddress{msg.DelegatorAddress} - if !bytes.Equal(msg.DelegatorAddr.Bytes(), msg.ValidatorAddr.Bytes()) { + if !bytes.Equal(msg.DelegatorAddress.Bytes(), msg.ValidatorAddress.Bytes()) { // if validator addr is not same as delegator addr, validator must sign // msg as well - addrs = append(addrs, sdk.AccAddress(msg.ValidatorAddr)) + addrs = append(addrs, sdk.AccAddress(msg.ValidatorAddress)) } return addrs } @@ -87,8 +81,8 @@ func (msg MsgCreateValidator) MarshalJSON() ([]byte, error) { return json.Marshal(msgCreateValidatorJSON{ Description: msg.Description, Commission: msg.Commission, - DelegatorAddr: msg.DelegatorAddr, - ValidatorAddr: msg.ValidatorAddr, + DelegatorAddress: msg.DelegatorAddress, + ValidatorAddress: msg.ValidatorAddress, PubKey: sdk.MustBech32ifyConsPub(msg.PubKey), Value: msg.Value, MinSelfDelegation: msg.MinSelfDelegation, @@ -105,9 +99,13 @@ func (msg *MsgCreateValidator) UnmarshalJSON(bz []byte) error { msg.Description = msgCreateValJSON.Description msg.Commission = msgCreateValJSON.Commission - msg.DelegatorAddr = msgCreateValJSON.DelegatorAddr - msg.ValidatorAddr = msgCreateValJSON.ValidatorAddr - msg.PubKey = sdk.MustGetConsPubKeyBech32(msgCreateValJSON.PubKey) + msg.DelegatorAddress = msgCreateValJSON.DelegatorAddress + msg.ValidatorAddress = msgCreateValJSON.ValidatorAddress + var err error + msg.PubKey, err = sdk.GetConsPubKeyBech32(msgCreateValJSON.PubKey) + if err != nil { + return err + } msg.Value = msgCreateValJSON.Value msg.MinSelfDelegation = msgCreateValJSON.MinSelfDelegation @@ -123,12 +121,15 @@ func (msg MsgCreateValidator) GetSignBytes() []byte { // quick validity check func (msg MsgCreateValidator) ValidateBasic() sdk.Error { // note that unmarshaling from bech32 ensures either empty or valid - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } + if !sdk.AccAddress(msg.ValidatorAddress).Equals(msg.DelegatorAddress) { + return ErrBadValidatorAddr(DefaultCodespace) + } if msg.Value.Amount.LTE(sdk.ZeroInt()) { return ErrBadDelegationAmount(DefaultCodespace) } @@ -151,7 +152,7 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { // MsgEditValidator - struct for editing a validator type MsgEditValidator struct { Description - ValidatorAddr sdk.ValAddress `json:"address"` + ValidatorAddress sdk.ValAddress `json:"address"` // We pass a reference to the new commission rate and min self delegation as it's not mandatory to // update. If not updated, the deserialized rate will be zero with no way to @@ -166,7 +167,7 @@ func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRat return MsgEditValidator{ Description: description, CommissionRate: newRate, - ValidatorAddr: valAddr, + ValidatorAddress: valAddr, MinSelfDelegation: newMinSelfDelegation, } } @@ -175,7 +176,7 @@ func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRat func (msg MsgEditValidator) Route() string { return RouterKey } func (msg MsgEditValidator) Type() string { return "edit_validator" } func (msg MsgEditValidator) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} + return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddress)} } // get the bytes for the message signer to sign on @@ -186,7 +187,7 @@ func (msg MsgEditValidator) GetSignBytes() []byte { // quick validity check func (msg MsgEditValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") } @@ -209,16 +210,16 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error { // MsgDelegate - struct for bonding transactions type MsgDelegate struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Value sdk.Coin `json:"value"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + Value sdk.Coin `json:"value"` } func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, value sdk.Coin) MsgDelegate { return MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Value: value, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + Value: value, } } @@ -226,7 +227,7 @@ func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, value sdk.Co func (msg MsgDelegate) Route() string { return RouterKey } func (msg MsgDelegate) Type() string { return "delegate" } func (msg MsgDelegate) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{msg.DelegatorAddr} + return []sdk.AccAddress{msg.DelegatorAddress} } // get the bytes for the message signer to sign on @@ -237,10 +238,10 @@ func (msg MsgDelegate) GetSignBytes() []byte { // quick validity check func (msg MsgDelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } if msg.Value.Amount.LTE(sdk.ZeroInt()) { @@ -253,20 +254,20 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { // MsgDelegate - struct for bonding transactions type MsgBeginRedelegate struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` - ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` - SharesAmount sdk.Dec `json:"shares_amount"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` + ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` + SharesAmount sdk.Dec `json:"shares_amount"` } func NewMsgBeginRedelegate(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgBeginRedelegate { return MsgBeginRedelegate{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - SharesAmount: sharesAmount, + DelegatorAddress: delAddr, + ValidatorSrcAddress: valSrcAddr, + ValidatorDstAddress: valDstAddr, + SharesAmount: sharesAmount, } } @@ -274,7 +275,7 @@ func NewMsgBeginRedelegate(delAddr sdk.AccAddress, valSrcAddr, func (msg MsgBeginRedelegate) Route() string { return RouterKey } func (msg MsgBeginRedelegate) Type() string { return "begin_redelegate" } func (msg MsgBeginRedelegate) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{msg.DelegatorAddr} + return []sdk.AccAddress{msg.DelegatorAddress} } // get the bytes for the message signer to sign on @@ -285,13 +286,13 @@ func (msg MsgBeginRedelegate) GetSignBytes() []byte { // quick validity check func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.ValidatorSrcAddr.Empty() { + if msg.ValidatorSrcAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } - if msg.ValidatorDstAddr.Empty() { + if msg.ValidatorDstAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } if msg.SharesAmount.LTE(sdk.ZeroDec()) { @@ -302,23 +303,23 @@ func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { // MsgUndelegate - struct for unbonding transactions type MsgUndelegate struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - SharesAmount sdk.Dec `json:"shares_amount"` + DelegatorAddress sdk.AccAddress `json:"delegator_address"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + SharesAmount sdk.Dec `json:"shares_amount"` } func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgUndelegate { return MsgUndelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - SharesAmount: sharesAmount, + DelegatorAddress: delAddr, + ValidatorAddress: valAddr, + SharesAmount: sharesAmount, } } //nolint func (msg MsgUndelegate) Route() string { return RouterKey } func (msg MsgUndelegate) Type() string { return "begin_unbonding" } -func (msg MsgUndelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } +func (msg MsgUndelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddress} } // get the bytes for the message signer to sign on func (msg MsgUndelegate) GetSignBytes() []byte { @@ -328,10 +329,10 @@ func (msg MsgUndelegate) GetSignBytes() []byte { // quick validity check func (msg MsgUndelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr.Empty() { + if msg.DelegatorAddress.Empty() { return ErrNilDelegatorAddr(DefaultCodespace) } - if msg.ValidatorAddr.Empty() { + if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } if msg.SharesAmount.LTE(sdk.ZeroDec()) { diff --git a/x/staking/types/msg_test.go b/x/staking/types/msg_test.go index 1e124e20e..a2a60a44e 100644 --- a/x/staking/types/msg_test.go +++ b/x/staking/types/msg_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,55 +77,6 @@ func TestMsgEditValidator(t *testing.T) { } } -// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf -func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { - commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) - commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) - - tests := []struct { - name, moniker, identity, website, details string - commissionMsg CommissionMsg - minSelfDelegation sdk.Int - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - validatorPubKey crypto.PubKey - bond sdk.Coin - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", commission2, sdk.OneInt(), sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"partial description", "", "", "c", "", commission2, sdk.OneInt(), sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"empty description", "", "", "", "", commission1, sdk.OneInt(), sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - {"empty delegator address", "a", "b", "c", "d", commission1, sdk.OneInt(), sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, - {"empty validator address", "a", "b", "c", "d", commission2, sdk.OneInt(), sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", commission1, sdk.OneInt(), sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", commission2, sdk.OneInt(), sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, - {"zero min self delegation", "a", "b", "c", "d", commission2, sdk.ZeroInt(), sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - {"negative min self delegation", "", "", "c", "", commission2, sdk.NewInt(-1), sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - {"delegation less than min self delegation", "a", "b", "c", "d", commission2, coinPos.Amount.Add(sdk.OneInt()), sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidatorOnBehalfOf( - tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description, tc.commissionMsg, tc.minSelfDelegation, - ) - - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } - - msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}, CommissionMsg{}, sdk.OneInt()) - addrs := msg.GetSigners() - require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr1)}, addrs, "Signers on default msg is wrong") - - msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}, CommissionMsg{}, sdk.OneInt()) - addrs = msg.GetSigners() - require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr2), sdk.AccAddress(addr1)}, addrs, "Signers for onbehalfof msg is wrong") -} - // test ValidateBasic for MsgDelegate func TestMsgDelegate(t *testing.T) { tests := []struct { diff --git a/x/staking/types/pool.go b/x/staking/types/pool.go index 6f108e8b3..d3905085a 100644 --- a/x/staking/types/pool.go +++ b/x/staking/types/pool.go @@ -39,8 +39,7 @@ func (p Pool) TokenSupply() sdk.Int { func (p Pool) BondedRatio() sdk.Dec { supply := p.TokenSupply() if supply.IsPositive() { - return sdk.NewDecFromInt(p.BondedTokens). - QuoInt(supply) + return p.BondedTokens.ToDec().QuoInt(supply) } return sdk.ZeroDec() } diff --git a/x/staking/types/validator.go b/x/staking/types/validator.go index 08c095cd4..730d43073 100644 --- a/x/staking/types/validator.go +++ b/x/staking/types/validator.go @@ -31,7 +31,7 @@ const ( // divided by the current exchange rate. Voting power can be calculated as total // bonded shares multiplied by exchange rate. type Validator struct { - OperatorAddr sdk.ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON + OperatorAddress sdk.ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) @@ -65,7 +65,7 @@ func (v Validators) ToSDKValidators() (validators []sdk.Validator) { // NewValidator - initialize a new validator func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { return Validator{ - OperatorAddr: operator, + OperatorAddress: operator, ConsPubKey: pubKey, Jailed: false, Status: sdk.Unbonded, @@ -116,7 +116,7 @@ func (v Validator) String() string { Unbonding Height: %d Unbonding Completion Time: %v Minimum Self Delegation: %v - Commission: %s`, v.OperatorAddr, bechConsPubKey, + Commission: %s`, v.OperatorAddress, bechConsPubKey, v.Jailed, sdk.BondStatusToString(v.Status), v.Tokens, v.DelegatorShares, v.Description, v.UnbondingHeight, v.UnbondingCompletionTime, v.MinSelfDelegation, v.Commission) @@ -124,7 +124,7 @@ func (v Validator) String() string { // this is a helper struct used for JSON de- and encoding only type bechValidator struct { - OperatorAddr sdk.ValAddress `json:"operator_address"` // the bech32 address of the validator's operator + OperatorAddress sdk.ValAddress `json:"operator_address"` // the bech32 address of the validator's operator ConsPubKey string `json:"consensus_pubkey"` // the bech32 consensus public key of the validator Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) @@ -145,7 +145,7 @@ func (v Validator) MarshalJSON() ([]byte, error) { } return codec.Cdc.MarshalJSON(bechValidator{ - OperatorAddr: v.OperatorAddr, + OperatorAddress: v.OperatorAddress, ConsPubKey: bechConsPubKey, Jailed: v.Jailed, Status: v.Status, @@ -170,7 +170,7 @@ func (v *Validator) UnmarshalJSON(data []byte) error { return err } *v = Validator{ - OperatorAddr: bv.OperatorAddr, + OperatorAddress: bv.OperatorAddress, ConsPubKey: consPubKey, Jailed: bv.Jailed, Tokens: bv.Tokens, @@ -188,7 +188,7 @@ func (v *Validator) UnmarshalJSON(data []byte) error { // only the vitals func (v Validator) TestEquivalent(v2 Validator) bool { return v.ConsPubKey.Equals(v2.ConsPubKey) && - bytes.Equal(v.OperatorAddr, v2.OperatorAddr) && + bytes.Equal(v.OperatorAddress, v2.OperatorAddress) && v.Status.Equal(v2.Status) && v.Tokens.Equal(v2.Tokens) && v.DelegatorShares.Equal(v2.DelegatorShares) && @@ -348,10 +348,13 @@ func (v Validator) SetInitialCommission(commission Commission) (Validator, sdk.E // CONTRACT: Tokens are assumed to have come from not-bonded pool. func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, sdk.Dec) { - // bondedShare/delegatedShare - exRate := v.DelegatorShareExRate() - if exRate.IsZero() { - panic("zero exRate should not happen") + // calculate the shares to issue + var issuedShares sdk.Dec + if v.DelegatorShares.IsZero() { + // the first delegation to a validator sets the exchange rate to one + issuedShares = amount.ToDec() + } else { + issuedShares = v.DelegatorShares.MulInt(amount).QuoInt(v.Tokens) } if v.Status == sdk.Bonded { @@ -359,7 +362,6 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, } v.Tokens = v.Tokens.Add(amount) - issuedShares := sdk.NewDecFromInt(amount).Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedShares) return v, pool, issuedShares @@ -382,7 +384,7 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo // leave excess tokens in the validator // however fully use all the delegator shares - issuedTokens = v.DelegatorShareExRate().Mul(delShares).TruncateInt() + issuedTokens = v.ShareTokens(delShares).TruncateInt() v.Tokens = v.Tokens.Sub(issuedTokens) if v.Tokens.IsNegative() { panic("attempting to remove more tokens than available in validator") @@ -397,14 +399,21 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo return v, pool, issuedTokens } -// DelegatorShareExRate gets the exchange rate of tokens over delegator shares. -// UNITS: tokens/delegator-shares -func (v Validator) DelegatorShareExRate() sdk.Dec { - if v.DelegatorShares.IsZero() { - // the first delegation to a validator sets the exchange rate to one - return sdk.OneDec() - } - return sdk.NewDecFromInt(v.Tokens).Quo(v.DelegatorShares) +// In some situations, the exchange rate becomes invalid, e.g. if +// Validator loses all tokens due to slashing. In this case, +// make all future delegations invalid. +func (v Validator) InvalidExRate() bool { + return v.Tokens.IsZero() && v.DelegatorShares.IsPositive() +} + +// calculate the token worth of provided shares +func (v Validator) ShareTokens(shares sdk.Dec) sdk.Dec { + return (shares.MulInt(v.Tokens)).Quo(v.DelegatorShares) +} + +// calculate the token worth of provided shares, truncated +func (v Validator) ShareTokensTruncated(shares sdk.Dec) sdk.Dec { + return (shares.MulInt(v.Tokens)).QuoTruncate(v.DelegatorShares) } // get the bonded tokens which the validator holds @@ -433,16 +442,15 @@ func (v Validator) PotentialTendermintPower() int64 { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator -func (v Validator) GetJailed() bool { return v.Jailed } -func (v Validator) GetMoniker() string { return v.Description.Moniker } -func (v Validator) GetStatus() sdk.BondStatus { return v.Status } -func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } -func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } -func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } -func (v Validator) GetTokens() sdk.Int { return v.Tokens } -func (v Validator) GetBondedTokens() sdk.Int { return v.BondedTokens() } -func (v Validator) GetTendermintPower() int64 { return v.TendermintPower() } -func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } -func (v Validator) GetMinSelfDelegation() sdk.Int { return v.MinSelfDelegation } -func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } -func (v Validator) GetDelegatorShareExRate() sdk.Dec { return v.DelegatorShareExRate() } +func (v Validator) GetJailed() bool { return v.Jailed } +func (v Validator) GetMoniker() string { return v.Description.Moniker } +func (v Validator) GetStatus() sdk.BondStatus { return v.Status } +func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddress } +func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } +func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } +func (v Validator) GetTokens() sdk.Int { return v.Tokens } +func (v Validator) GetBondedTokens() sdk.Int { return v.BondedTokens() } +func (v Validator) GetTendermintPower() int64 { return v.TendermintPower() } +func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } +func (v Validator) GetMinSelfDelegation() sdk.Int { return v.MinSelfDelegation } +func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } diff --git a/x/staking/types/validator_test.go b/x/staking/types/validator_test.go index 09641ee2a..b8b1fb5c7 100644 --- a/x/staking/types/validator_test.go +++ b/x/staking/types/validator_test.go @@ -1,7 +1,6 @@ package types import ( - "fmt" "testing" "github.com/cosmos/cosmos-sdk/codec" @@ -70,10 +69,25 @@ func TestABCIValidatorUpdateZero(t *testing.T) { require.Equal(t, int64(0), abciVal.Power) } +func TestShareTokens(t *testing.T) { + validator := Validator{ + OperatorAddress: addr1, + ConsPubKey: pk1, + Status: sdk.Bonded, + Tokens: sdk.NewInt(100), + DelegatorShares: sdk.NewDec(100), + } + assert.True(sdk.DecEq(t, sdk.NewDec(50), validator.ShareTokens(sdk.NewDec(50)))) + + validator.Tokens = sdk.NewInt(50) + assert.True(sdk.DecEq(t, sdk.NewDec(25), validator.ShareTokens(sdk.NewDec(50)))) + assert.True(sdk.DecEq(t, sdk.NewDec(5), validator.ShareTokens(sdk.NewDec(10)))) +} + func TestRemoveTokens(t *testing.T) { validator := Validator{ - OperatorAddr: addr1, + OperatorAddress: addr1, ConsPubKey: pk1, Status: sdk.Bonded, Tokens: sdk.NewInt(100), @@ -112,10 +126,9 @@ func TestAddTokensValidatorBonded(t *testing.T) { validator, pool = validator.UpdateStatus(pool, sdk.Bonded) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) - require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) - assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens())) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) } func TestAddTokensValidatorUnbonding(t *testing.T) { @@ -125,11 +138,10 @@ func TestAddTokensValidatorUnbonding(t *testing.T) { validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) - require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) - assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.Equal(t, sdk.Unbonding, validator.Status) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) } func TestAddTokensValidatorUnbonded(t *testing.T) { @@ -139,17 +151,16 @@ func TestAddTokensValidatorUnbonded(t *testing.T) { validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) - require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) - assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.Equal(t, sdk.Unbonded, validator.Status) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) } // TODO refactor to make simpler like the AddToken tests above func TestRemoveDelShares(t *testing.T) { valA := Validator{ - OperatorAddr: addr1, + OperatorAddress: addr1, ConsPubKey: pk1, Status: sdk.Bonded, Tokens: sdk.NewInt(100), @@ -158,7 +169,6 @@ func TestRemoveDelShares(t *testing.T) { poolA := InitialPool() poolA.NotBondedTokens = sdk.NewInt(10) poolA.BondedTokens = valA.BondedTokens() - require.Equal(t, valA.DelegatorShareExRate(), sdk.OneDec()) // Remove delegator shares valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewDec(10)) @@ -177,7 +187,7 @@ func TestRemoveDelShares(t *testing.T) { poolTokens := sdk.NewInt(5102) delShares := sdk.NewDec(115) validator := Validator{ - OperatorAddr: addr1, + OperatorAddress: addr1, ConsPubKey: pk1, Status: sdk.Bonded, Tokens: poolTokens, @@ -197,6 +207,26 @@ func TestRemoveDelShares(t *testing.T) { pool.NotBondedTokens.Add(pool.BondedTokens))) } +func TestAddTokensFromDel(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + pool := InitialPool() + pool.NotBondedTokens = sdk.NewInt(10) + + val, pool, shares := val.AddTokensFromDel(pool, sdk.NewInt(6)) + require.True(sdk.DecEq(t, sdk.NewDec(6), shares)) + require.True(sdk.DecEq(t, sdk.NewDec(6), val.DelegatorShares)) + require.True(sdk.IntEq(t, sdk.NewInt(6), val.Tokens)) + require.True(sdk.IntEq(t, sdk.NewInt(0), pool.BondedTokens)) + require.True(sdk.IntEq(t, sdk.NewInt(10), pool.NotBondedTokens)) + + val, pool, shares = val.AddTokensFromDel(pool, sdk.NewInt(3)) + require.True(sdk.DecEq(t, sdk.NewDec(3), shares)) + require.True(sdk.DecEq(t, sdk.NewDec(9), val.DelegatorShares)) + require.True(sdk.IntEq(t, sdk.NewInt(9), val.Tokens)) + require.True(sdk.IntEq(t, sdk.NewInt(0), pool.BondedTokens)) + require.True(sdk.IntEq(t, sdk.NewInt(10), pool.NotBondedTokens)) +} + func TestUpdateStatus(t *testing.T) { pool := InitialPool() pool.NotBondedTokens = sdk.NewInt(100) @@ -225,7 +255,7 @@ func TestPossibleOverflow(t *testing.T) { poolTokens := sdk.NewInt(2159) delShares := sdk.NewDec(391432570689183511).Quo(sdk.NewDec(40113011844664)) validator := Validator{ - OperatorAddr: addr1, + OperatorAddress: addr1, ConsPubKey: pk1, Status: sdk.Bonded, Tokens: poolTokens, @@ -236,13 +266,10 @@ func TestPossibleOverflow(t *testing.T) { BondedTokens: poolTokens, } tokens := int64(71) - msg := fmt.Sprintf("validator %#v", validator) newValidator, _, _ := validator.AddTokensFromDel(pool, sdk.NewInt(tokens)) - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroDec()), - "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", - msg, newValidator.DelegatorShareExRate()) + require.False(t, newValidator.DelegatorShares.IsNegative()) + require.False(t, newValidator.Tokens.IsNegative()) } func TestValidatorMarshalUnmarshalJSON(t *testing.T) {