diff --git a/.circleci/config.yml b/.circleci/config.yml index d866bd637e48..a8e718b109aa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,37 @@ version: 2 -defaults: &defaults +defaults: &linux_defaults working_directory: /go/src/github.com/cosmos/cosmos-sdk docker: - - image: circleci/golang:1.11.1 + - image: circleci/golang:1.11.4 environment: GOBIN: /tmp/workspace/bin + +############ +# +# Configure macos integration tests + +macos_config: &macos_defaults + macos: + xcode: "10.1.0" + working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk + environment: + GO_VERSION: "1.11.4" + +set_macos_env: &macos_env + run: + name: Set environment + command: | + echo 'export PATH=$PATH:$HOME/go/bin' >> $BASH_ENV + echo 'export GOPATH=$HOME/project' >> $BASH_ENV + echo 'export GOBIN=$GOPATH/bin' >> $BASH_ENV + echo 'export PATH=$PATH:$HOME/go/bin:$GOBIN' >> $BASH_ENV + +############ +# +# Configure docs deployment + docs_update: &docs_deploy working_directory: ~/repo docker: @@ -14,33 +39,31 @@ docs_update: &docs_deploy environment: AWS_REGION: us-east-1 -jobs: +deps: &dependencies + run: + name: dependencies + command: | + export PATH="$GOBIN:$PATH" + make vendor-deps +jobs: setup_dependencies: - <<: *defaults + <<: *linux_defaults steps: - run: mkdir -p /tmp/workspace/bin - run: mkdir -p /tmp/workspace/profiles - checkout - - restore_cache: - keys: - - v1-pkg-cache - run: name: tools command: | export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + make tools + - *dependencies - run: name: binaries command: | export PATH="$GOBIN:$PATH" make install - make install_examples - persist_to_workspace: root: /tmp/workspace paths: @@ -48,23 +71,18 @@ jobs: - profiles lint: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Get metalinter command: | export PATH="$GOBIN:$PATH" - make get_tools - make get_dev_tools + make devtools - run: name: Lint source command: | @@ -72,36 +90,27 @@ jobs: make test_lint integration_tests: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Test cli command: | export PATH="$GOBIN:$PATH" make test_cli - make test_examples test_sim_gaia_nondeterminism: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Test individual module simulations command: | @@ -109,17 +118,13 @@ jobs: make test_sim_gaia_nondeterminism test_sim_gaia_fast: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Test full Gaia simulation command: | @@ -127,17 +132,13 @@ jobs: make test_sim_gaia_fast test_sim_gaia_import_export: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Test Gaia import/export simulation command: | @@ -145,53 +146,55 @@ jobs: make test_sim_gaia_import_export test_sim_gaia_simulation_after_import: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: Test Gaia import/export simulation command: | export PATH="$GOBIN:$PATH" make test_sim_gaia_simulation_after_import - test_sim_gaia_multi_seed: - <<: *defaults + test_sim_gaia_multi_seed_long: + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout + - *dependencies - run: - name: dependencies + name: Test multi-seed Gaia simulation long command: | export PATH="$GOBIN:$PATH" - make get_vendor_deps + scripts/multisim.sh 800 50 TestFullGaiaSimulation + + test_sim_gaia_multi_seed: + <<: *linux_defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - *dependencies - run: - name: Test multi-seed Gaia simulation + name: Test multi-seed Gaia simulation short command: | export PATH="$GOBIN:$PATH" - scripts/multisim.sh 25 TestFullGaiaSimulation + scripts/multisim.sh 50 10 TestFullGaiaSimulation test_cover: - <<: *defaults + <<: *linux_defaults parallelism: 4 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: mkdir -p /tmp/logs - run: name: Run tests @@ -211,17 +214,13 @@ jobs: path: /tmp/logs upload_coverage: - <<: *defaults + <<: *linux_defaults parallelism: 1 steps: - attach_workspace: at: /tmp/workspace - checkout - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps + - *dependencies - run: name: gather command: | @@ -244,6 +243,7 @@ jobs: GOPATH: /home/circleci/.go_workspace/ GOOS: linux GOARCH: amd64 + GO_VERSION: "1.11.4" parallelism: 1 steps: - checkout @@ -251,17 +251,18 @@ jobs: name: run localnet and exit on failure command: | pushd /tmp - wget https://dl.google.com/go/go1.11.linux-amd64.tar.gz - sudo tar -xvf go1.11.linux-amd64.tar.gz + wget https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz + sudo tar -xvf go$GO_VERSION.linux-amd64.tar.gz sudo rm -rf /usr/local/go sudo mv go /usr/local popd set -x - make get_tools - make get_vendor_deps + make tools + make vendor-deps make build-linux make localnet-start ./scripts/localnet-blocks-test.sh 40 5 10 localhost + deploy_docs: <<: *docs_deploy steps: @@ -271,10 +272,75 @@ jobs: command: | chamber exec cosmos-sdk -- start_website_build + macos_ci: + <<: *macos_defaults + steps: + - *macos_env + - run: + name: Install go + command: | + source $BASH_ENV + curl -L -O https://dl.google.com/go/go$GO_VERSION.darwin-amd64.tar.gz + tar -C $HOME -xzf go$GO_VERSION.darwin-amd64.tar.gz + rm go$GO_VERSION.darwin-amd64.tar.gz + go version + - checkout + - run: + name: Install SDK + command: | + source $BASH_ENV + make tools + make vendor-deps + make install + - run: + name: Integration tests + command: + source $BASH_ENV + make test_cli + - run: + name: Test full gaia simulation + command: | + source $BASH_ENV + make test_sim_gaia_fast + + docker_image: + <<: *linux_defaults + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: | + if [ "${CIRCLE_BRANCH}" == "master" ]; then + 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 + workflows: version: 2 test-suite: jobs: + - docker_image: + filters: + branches: + only: + - master + - develop + requires: + - setup_dependencies + - macos_ci: + filters: + branches: + only: + - master + - develop - deploy_docs: filters: branches: @@ -303,6 +369,14 @@ workflows: - test_sim_gaia_multi_seed: requires: - setup_dependencies + - test_sim_gaia_multi_seed_long: + requires: + - setup_dependencies + filters: + branches: + only: + - master + - develop - test_cover: requires: - setup_dependencies diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..66cf55801745 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +client/lcd/swagger-ui/* linguist-vendored diff --git a/.gitignore b/.gitignore index cbc5e5e22cde..9f480652448d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,17 @@ # Build vendor +vendor-deps .vendor-new build tools/bin/* examples/build/* docs/_build +docs/tutorial +dist +devtools-stamp # Data - ideally these don't exist -examples/basecoin/app/data baseapp/data/* client/lcd/keys/* client/lcd/statik/statik.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3662b7b19233..fb28675939da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,153 @@ # Changelog +## 0.30.0 + +BREAKING CHANGES + +* Gaia REST API (`gaiacli advanced rest-server`) + * [gaia-lite] [\#2182] Renamed and merged all redelegations endpoints into `/staking/redelegations` + * [\#3176](https://github.com/cosmos/cosmos-sdk/issues/3176) `tx/sign` endpoint now expects `BaseReq` fields as nested object. + * [\#2222] all endpoints renamed from `/stake` -> `/staking` + * [\#1268] `LooseTokens` -> `NotBondedTokens` + * [\#3289] misc renames: + * `Validator.UnbondingMinTime` -> `Validator.UnbondingCompletionTime` + * `Delegation` -> `Value` in `MsgCreateValidator` and `MsgDelegate` + * `MsgBeginUnbonding` -> `MsgUndelegate` + +* Gaia CLI (`gaiacli`) + * [\#810](https://github.com/cosmos/cosmos-sdk/issues/810) Don't fallback to any default values for chain ID. + * Users need to supply chain ID either via config file or the `--chain-id` flag. + * Change `chain_id` and `trust_node` in `gaiacli` configuration to `chain-id` and `trust-node` respectively. + * [\#3069](https://github.com/cosmos/cosmos-sdk/pull/3069) `--fee` flag renamed to `--fees` to support multiple coins + * [\#3156](https://github.com/cosmos/cosmos-sdk/pull/3156) Remove unimplemented `gaiacli init` command + * [\#2222] `gaiacli tx stake` -> `gaiacli tx staking`, `gaiacli query stake` -> `gaiacli query staking` + * [\#1894](https://github.com/cosmos/cosmos-sdk/issues/1894) `version` command now shows latest commit, vendor dir hash, and build machine info. + * [\#3320](https://github.com/cosmos/cosmos-sdk/pull/3320) Ensure all `gaiacli query` commands respect the `--output` and `--indent` flags + +* Gaia + * https://github.com/cosmos/cosmos-sdk/issues/2838 - Move store keys to constants + * [\#3162](https://github.com/cosmos/cosmos-sdk/issues/3162) The `--gas` flag now takes `auto` instead of `simulate` + in order to trigger a simulation of the tx before the actual execution. + * [\#3285](https://github.com/cosmos/cosmos-sdk/pull/3285) New `gaiad tendermint version` to print libs versions + * [\#1894](https://github.com/cosmos/cosmos-sdk/pull/1894) `version` command now shows latest commit, vendor dir hash, and build machine info. + * [\#3249\(https://github.com/cosmos/cosmos-sdk/issues/3249) `tendermint`'s `show-validator` and `show-address` `--json` flags removed in favor of `--output-format=json`. + +* SDK + * [distribution] [\#3359](https://github.com/cosmos/cosmos-sdk/issues/3359) Always round down when calculating rewards-to-be-withdrawn in F1 fee distribution + * [#3336](https://github.com/cosmos/cosmos-sdk/issues/3336) Ensure all SDK + messages have their signature bytes contain canonical fields `value` and `type`. + * [\#3333](https://github.com/cosmos/cosmos-sdk/issues/3333) - F1 storage efficiency improvements - automatic withdrawals when unbonded, historical reward reference counting + * [staking] [\#2513](https://github.com/cosmos/cosmos-sdk/issues/2513) Validator power type from Dec -> Int + * [staking] [\#3233](https://github.com/cosmos/cosmos-sdk/issues/3233) key and value now contain duplicate fields to simplify code + * [\#3064](https://github.com/cosmos/cosmos-sdk/issues/3064) Sanitize `sdk.Coin` denom. Coins denoms are now case insensitive, i.e. 100fooToken equals to 100FOOTOKEN. + * [\#3195](https://github.com/cosmos/cosmos-sdk/issues/3195) Allows custom configuration for syncable strategy + * [\#3242](https://github.com/cosmos/cosmos-sdk/issues/3242) Fix infinite gas + meter utilization during aborted ante handler executions. + * [x/distribution] [\#3292](https://github.com/cosmos/cosmos-sdk/issues/3292) Enable or disable withdraw addresses with a parameter in the param store + * [staking] [\#2222](https://github.com/cosmos/cosmos-sdk/issues/2222) `/stake` -> `/staking` module rename + * [staking] [\#1268](https://github.com/cosmos/cosmos-sdk/issues/1268) `LooseTokens` -> `NotBondedTokens` + * [staking] [\#1402](https://github.com/cosmos/cosmos-sdk/issues/1402) Redelegation and unbonding-delegation structs changed to include multiple an array of entries + * [staking] [\#3289](https://github.com/cosmos/cosmos-sdk/issues/3289) misc renames: + * `Validator.UnbondingMinTime` -> `Validator.UnbondingCompletionTime` + * `Delegation` -> `Value` in `MsgCreateValidator` and `MsgDelegate` + * `MsgBeginUnbonding` -> `MsgUndelegate` + * [\#3315] Increase decimal precision to 18 + * [\#3323](https://github.com/cosmos/cosmos-sdk/issues/3323) Update to Tendermint 0.29.0 + * [\#3328](https://github.com/cosmos/cosmos-sdk/issues/3328) [x/gov] Remove redundant action tag + +* Tendermint + * [\#3298](https://github.com/cosmos/cosmos-sdk/issues/3298) Upgrade to Tendermint 0.28.0 + +FEATURES + +* Gaia REST API (`gaiacli advanced rest-server`) + * [\#3067](https://github.com/cosmos/cosmos-sdk/issues/3067) Add support for fees on transactions + * [\#3069](https://github.com/cosmos/cosmos-sdk/pull/3069) Add a custom memo on transactions + * [\#3027](https://github.com/cosmos/cosmos-sdk/issues/3027) Implement + `/gov/proposals/{proposalID}/proposer` to query for a proposal's proposer. + +* Gaia CLI (`gaiacli`) + * [\#2399](https://github.com/cosmos/cosmos-sdk/issues/2399) Implement `params` command to query slashing parameters. + * [\#2730](https://github.com/cosmos/cosmos-sdk/issues/2730) Add tx search pagination parameter + * [\#3027](https://github.com/cosmos/cosmos-sdk/issues/3027) Implement + `query gov proposer [proposal-id]` to query for a proposal's proposer. + * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) New `keys add --multisig` flag to store multisig keys locally. + * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) New `multisign` command to generate multisig signatures. + * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) New `sign --multisig` flag to enable multisig mode. + * [\#2715](https://github.com/cosmos/cosmos-sdk/issues/2715) Reintroduce gaia server's insecure mode. + * [\#3334](https://github.com/cosmos/cosmos-sdk/pull/3334) New `gaiad completion` and `gaiacli completion` to generate Bash/Zsh completion scripts. + * [\#2607](https://github.com/cosmos/cosmos-sdk/issues/2607) Make `gaiacli config` handle the boolean `indent` flag to beautify commands JSON output. + +* Gaia + * [\#2182] [x/staking] Added querier for querying a single redelegation + * [\#3305](https://github.com/cosmos/cosmos-sdk/issues/3305) Add support for + vesting accounts at genesis. + * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) [x/auth] Add multisig transactions support + * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) `add-genesis-account` can take both account addresses and key names + +* SDK + - [\#3099](https://github.com/cosmos/cosmos-sdk/issues/3099) Implement F1 fee distribution + - [\#2926](https://github.com/cosmos/cosmos-sdk/issues/2926) Add TxEncoder to client TxBuilder. + * [\#2694](https://github.com/cosmos/cosmos-sdk/issues/2694) Vesting account implementation. + * [\#2996](https://github.com/cosmos/cosmos-sdk/issues/2996) Update the `AccountKeeper` to contain params used in the context of + the ante handler. + * [\#3179](https://github.com/cosmos/cosmos-sdk/pull/3179) New CodeNoSignatures error code. + * [\#3319](https://github.com/cosmos/cosmos-sdk/issues/3319) [x/distribution] Queriers for all distribution state worth querying; distribution query commands + * [\#3356](https://github.com/cosmos/cosmos-sdk/issues/3356) [x/auth] bech32-ify accounts address in error message. + +IMPROVEMENTS + +* Gaia REST API + * [\#3176](https://github.com/cosmos/cosmos-sdk/issues/3176) Validate tx/sign endpoint POST body. + * [\#2948](https://github.com/cosmos/cosmos-sdk/issues/2948) Swagger UI now makes requests to light client node + +* Gaia CLI (`gaiacli`) + * [\#3224](https://github.com/cosmos/cosmos-sdk/pull/3224) Support adding offline public keys to the keystore + +* Gaia + * [\#2186](https://github.com/cosmos/cosmos-sdk/issues/2186) Add Address Interface + * [\#3158](https://github.com/cosmos/cosmos-sdk/pull/3158) Validate slashing genesis + * [\#3172](https://github.com/cosmos/cosmos-sdk/pull/3172) Support minimum fees in a local testnet. + * [\#3250](https://github.com/cosmos/cosmos-sdk/pull/3250) Refactor integration tests and increase coverage + * [\#3248](https://github.com/cosmos/cosmos-sdk/issues/3248) Refactor tx fee + model: + * Validators specify minimum gas prices instead of minimum fees + * Clients may provide either fees or gas prices directly + * The gas prices of a tx must meet a validator's minimum + * `gaiad start` and `gaia.toml` take --minimum-gas-prices flag and minimum-gas-price config key respectively. + * [\#2859](https://github.com/cosmos/cosmos-sdk/issues/2859) Rename `TallyResult` in gov proposals to `FinalTallyResult` + * [\#3286](https://github.com/cosmos/cosmos-sdk/pull/3286) Fix `gaiad gentx` printout of account's addresses, i.e. user bech32 instead of hex. + * [\#3249\(https://github.com/cosmos/cosmos-sdk/issues/3249) `--json` flag removed, users should use `--output=json` instead. + +* SDK + * [\#3137](https://github.com/cosmos/cosmos-sdk/pull/3137) Add tag documentation + for each module along with cleaning up a few existing tags in the governance, + slashing, and staking modules. + * [\#3093](https://github.com/cosmos/cosmos-sdk/issues/3093) Ante handler does no longer read all accounts in one go when processing signatures as signature + verification may fail before last signature is checked. + * [staking] [\#1402](https://github.com/cosmos/cosmos-sdk/issues/1402) Add for multiple simultaneous redelegations or unbonding-delegations within an unbonding period + * [staking] [\#1268](https://github.com/cosmos/cosmos-sdk/issues/1268) staking spec rewrite + +* CI + * [\#2498](https://github.com/cosmos/cosmos-sdk/issues/2498) Added macos CI job to CircleCI + * [#142](https://github.com/tendermint/devops/issues/142) Increased the number of blocks to be tested during multi-sim + * [#147](https://github.com/tendermint/devops/issues/142) Added docker image build to CI + +BUG FIXES + +* Gaia CLI (`gaiacli`) + * [\#3141](https://github.com/cosmos/cosmos-sdk/issues/3141) Fix the bug in GetAccount when `len(res) == 0` and `err == nil` + * [\#810](https://github.com/cosmos/cosmos-sdk/pull/3316) Fix regression in gaiacli config file handling + +* Gaia + * [\#3148](https://github.com/cosmos/cosmos-sdk/issues/3148) Fix `gaiad export` by adding a boolean to `NewGaiaApp` determining whether or not to load the latest version + * [\#3181](https://github.com/cosmos/cosmos-sdk/issues/3181) Correctly reset total accum update height and jailed-validator bond height / unbonding height on export-for-zero-height + * [\#3172](https://github.com/cosmos/cosmos-sdk/pull/3172) Fix parsing `gaiad.toml` + when it already exists. + * [\#3223](https://github.com/cosmos/cosmos-sdk/issues/3223) Fix unset governance proposal queues when importing state from old chain + * [#3187](https://github.com/cosmos/cosmos-sdk/issues/3187) Fix `gaiad export` + by resetting each validator's slashing period. + ## 0.29.1 BUG FIXES @@ -134,7 +282,7 @@ BREAKING CHANGES * [\#2019](https://github.com/cosmos/cosmos-sdk/issues/2019) Cap total number of signatures. Current per-transaction limit is 7, and if that is exceeded transaction is rejected. * [\#2801](https://github.com/cosmos/cosmos-sdk/pull/2801) Remove AppInit structure. * [\#2798](https://github.com/cosmos/cosmos-sdk/issues/2798) Governance API has miss-spelled English word in JSON response ('depositer' -> 'depositor') - * [\#2943](https://github.com/cosmos/cosmos-sdk/pull/2943) Transaction action tags equal the message type. Stake EndBlocker tags are included. + * [\#2943](https://github.com/cosmos/cosmos-sdk/pull/2943) Transaction action tags equal the message type. Staking EndBlocker tags are included. * Tendermint * Update to Tendermint 0.27.0 @@ -1062,7 +1210,7 @@ BREAKING CHANGES * All module keepers now require a codespace, see basecoin or democoin for usage * Many changes to names throughout * Type as a prefix naming convention applied (ex. BondMsg -> MsgBond) - * Removed redundancy in names (ex. stake.StakeKeeper -> stake.Keeper) + * Removed redundancy in names (ex. stake.StakingKeeper -> stake.Keeper) * Removed SealedAccountMapper * gaiad init now requires use of `--name` flag * Removed Get from Msg interface diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96804e28c8e8..bd54c5f02549 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ If you open a PR on the Cosmos SDK, it is mandatory to update the relevant docum * If your change relates to the core SDK (baseapp, store, ...), please update the docs/gaia folder, the docs/examples folder and possibly the docs/spec folder. * If your changes relate specifically to the gaia application (not including modules), please modify the docs/gaia folder. -* If your changes relate to a module, please update the module's spec in docs/spec. If the module is used by gaia and/or basecoin, you might also need to modify docs/gaia and/or docs/examples. +* If your changes relate to a module, please update the module's spec in docs/spec. If the module is used by gaia, you might also need to modify docs/gaia and/or docs/examples. * If your changes relate to the core of the CLI or Light-client (not specifically to module's CLI/Rest), please modify the docs/clients folder. ## Forking @@ -85,7 +85,7 @@ For instance, to create a fork and work on a branch of it, I would: - Create the fork on github, using the fork button. - Go to the original repo checked out locally (i.e. `$GOPATH/src/github.com/cosmos/cosmos-sdk`) - `git remote rename origin upstream` - - `git remote add origin git@github.com:ebuchman/basecoin.git` + - `git remote add origin git@github.com:ebuchman/cosmos-sdk.git` Now `origin` refers to my fork and `upstream` refers to the Cosmos-SDK version. So I can `git push -u origin master` to update my fork, and make pull requests to Cosmos-SDK from there. diff --git a/Dockerfile b/Dockerfile index 5dfd625deb00..aea2ecc9d54f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,8 @@ COPY . . # Install minimum necessary dependencies, build Cosmos SDK, remove packages RUN apk add --no-cache $PACKAGES && \ - make get_tools && \ - make get_vendor_deps && \ + make tools && \ + make vendor-deps && \ make build && \ make install diff --git a/Gopkg.lock b/Gopkg.lock index c6090bf2799d..fa9eec6a673f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -429,11 +429,12 @@ revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] - digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" + digest = "1:83f5e189eea2baad419a6a410984514266ff690075759c87e9ede596809bd0b8" name = "github.com/tendermint/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" + revision = "80daadac05d1cd29571fccf27002d79667a88b58" + version = "v0.1.1" [[projects]] digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" @@ -452,11 +453,12 @@ version = "v0.12.0" [[projects]] - digest = "1:f94f468c7cd1fc3693ebc16011f26ec26538761cdcd182b3a28faa649a7d55e2" + digest = "1:22a0fe58c626dd09549eb9451688fab5a2c8bef04d478c907f747d6151d431fd" name = "github.com/tendermint/tendermint" packages = [ "abci/client", "abci/example/code", + "abci/example/counter", "abci/example/kvstore", "abci/server", "abci/types", @@ -517,7 +519,7 @@ "version", ] pruneopts = "UT" - revision = "v0.27.3" + revision = "v0.29.0" [[projects]] digest = "1:a7485b2a69f996923f9d3406a9a853fd8eb31818515e985a830d71f88f6a925b" @@ -695,7 +697,6 @@ "github.com/tendermint/tendermint/crypto/secp256k1", "github.com/tendermint/tendermint/crypto/tmhash", "github.com/tendermint/tendermint/crypto/xsalsa20symmetric", - "github.com/tendermint/tendermint/libs/autofile", "github.com/tendermint/tendermint/libs/bech32", "github.com/tendermint/tendermint/libs/cli", "github.com/tendermint/tendermint/libs/cli/flags", diff --git a/Gopkg.toml b/Gopkg.toml index d730f62a1405..54a64d419a75 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,7 +40,7 @@ [[override]] name = "github.com/tendermint/tendermint" - revision = "v0.27.3" + revision = "v0.29.0" [[constraint]] name = "github.com/zondax/ledger-cosmos-go" diff --git a/Makefile b/Makefile index cfd9cbd61c18..2f8086fd7ae4 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,29 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') VERSION := $(subst v,,$(shell git describe --tags --long)) +COMMIT := $(shell git log -1 --format='%H') BUILD_TAGS = netgo -BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.Version=${VERSION}" +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)" LEDGER_ENABLED ?= true GOTOOLS = \ github.com/golang/dep/cmd/dep \ github.com/alecthomas/gometalinter \ github.com/rakyll/statik GOBIN ?= $(GOPATH)/bin -all: get_tools get_vendor_deps install install_examples install_cosmos-sdk-cli test_lint test -# The below include contains the get_tools target. +all: devtools get_vendor_deps install test_lint test + +# The below include contains the tools target. include scripts/Makefile ######################################## ### CI -ci: get_tools get_vendor_deps install test_cover test_lint test +ci: devtools vendor-deps install test_cover test_lint test ######################################## ### Build/Install @@ -49,56 +55,24 @@ build: ifeq ($(OS),Windows_NT) go build $(BUILD_FLAGS) -o build/gaiad.exe ./cmd/gaia/cmd/gaiad go build $(BUILD_FLAGS) -o build/gaiacli.exe ./cmd/gaia/cmd/gaiacli - go build $(BUILD_FLAGS) -o build/logjack ./cmd/logjack else go build $(BUILD_FLAGS) -o build/gaiad ./cmd/gaia/cmd/gaiad go build $(BUILD_FLAGS) -o build/gaiacli ./cmd/gaia/cmd/gaiacli go build $(BUILD_FLAGS) -o build/gaiareplay ./cmd/gaia/cmd/gaiareplay go build $(BUILD_FLAGS) -o build/gaiakeyutil ./cmd/gaia/cmd/gaiakeyutil - go build $(BUILD_FLAGS) -o build/logjack ./cmd/logjack endif -build-linux: +build-linux: vendor-deps LEDGER_ENABLED=false GOOS=linux GOARCH=amd64 $(MAKE) build update_gaia_lite_docs: @statik -src=client/lcd/swagger-ui -dest=client/lcd -f -build_cosmos-sdk-cli: -ifeq ($(OS),Windows_NT) - go build $(BUILD_FLAGS) -o build/cosmos-sdk-cli.exe ./cmd/cosmos-sdk-cli -else - go build $(BUILD_FLAGS) -o build/cosmos-sdk-cli ./cmd/cosmos-sdk-cli -endif - -build_examples: -ifeq ($(OS),Windows_NT) - go build $(BUILD_FLAGS) -o build/basecoind.exe ./docs/examples/basecoin/cmd/basecoind - go build $(BUILD_FLAGS) -o build/basecli.exe ./docs/examples/basecoin/cmd/basecli - go build $(BUILD_FLAGS) -o build/democoind.exe ./docs/examples/democoin/cmd/democoind - go build $(BUILD_FLAGS) -o build/democli.exe ./docs/examples/democoin/cmd/democli -else - go build $(BUILD_FLAGS) -o build/basecoind ./docs/examples/basecoin/cmd/basecoind - go build $(BUILD_FLAGS) -o build/basecli ./docs/examples/basecoin/cmd/basecli - go build $(BUILD_FLAGS) -o build/democoind ./docs/examples/democoin/cmd/democoind - go build $(BUILD_FLAGS) -o build/democli ./docs/examples/democoin/cmd/democli -endif - -install: check-ledger update_gaia_lite_docs +install: vendor-deps check-ledger update_gaia_lite_docs go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiad go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiacli go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiareplay go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiakeyutil - go install $(BUILD_FLAGS) ./cmd/logjack - -install_examples: - go install $(BUILD_FLAGS) ./docs/examples/basecoin/cmd/basecoind - go install $(BUILD_FLAGS) ./docs/examples/basecoin/cmd/basecli - go install $(BUILD_FLAGS) ./docs/examples/democoin/cmd/democoind - go install $(BUILD_FLAGS) ./docs/examples/democoin/cmd/democli - -install_cosmos-sdk-cli: - go install $(BUILD_FLAGS) ./cmd/cosmos-sdk-cli install_debug: go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiadebug @@ -117,33 +91,38 @@ check_tools: update_tools: @echo "--> Updating tools to correct version" - $(MAKE) --always-make get_tools + $(MAKE) --always-make tools update_dev_tools: @echo "--> Downloading linters (this may take awhile)" $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) go get -u github.com/tendermint/lint/golint -get_dev_tools: get_tools +devtools: devtools-stamp +devtools-stamp: tools @echo "--> Downloading linters (this may take awhile)" $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) go get github.com/tendermint/lint/golint + touch $@ -get_vendor_deps: get_tools +vendor-deps: tools @echo "--> Generating vendor directory via dep ensure" @rm -rf .vendor-new @dep ensure -v -vendor-only + tar -c vendor/ | sha1sum | cut -d' ' -f1 > $@ -update_vendor_deps: get_tools +update_vendor_deps: tools @echo "--> Running dep ensure" @rm -rf .vendor-new @dep ensure -v -draw_deps: get_tools +draw_deps: tools @# requires brew install graphviz or apt-get install graphviz go get github.com/RobotsAndPencils/goviz @goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png +clean: + rm -f devtools-stamp vendor-deps ######################################## ### Documentation @@ -161,10 +140,6 @@ test: test_unit test_cli: @go test -p 4 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` -tags=cli_test -test_examples: - @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/docs/examples/basecoin/cli_test` -tags=cli_test - @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/docs/examples/democoin/cli_test` -tags=cli_test - test_unit: @VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION) @@ -181,15 +156,15 @@ test_sim_gaia_fast: test_sim_gaia_import_export: @echo "Running Gaia import/export simulation. This may take several minutes..." - @bash scripts/multisim.sh 50 TestGaiaImportExport + @bash scripts/multisim.sh 50 5 TestGaiaImportExport test_sim_gaia_simulation_after_import: @echo "Running Gaia simulation-after-import. This may take several minutes..." - @bash scripts/multisim.sh 50 TestGaiaSimulationAfterImport + @bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" - @bash scripts/multisim.sh 400 TestFullGaiaSimulation + @bash scripts/multisim.sh 400 5 TestFullGaiaSimulation SIM_NUM_BLOCKS ?= 500 SIM_BLOCK_SIZE ?= 200 @@ -263,8 +238,8 @@ localnet-stop: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build build_cosmos-sdk-cli build_examples install install_examples install_cosmos-sdk-cli install_debug dist \ -check_tools check_dev_tools get_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ +.PHONY: build install install_debug dist \ +check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \ build-linux build-docker-gaiadnode localnet-start localnet-stop \ format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \ diff --git a/README.md b/README.md index a5aab6dddb0d..2ab5cf4e2b36 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ It is being used to build `Gaia`, the first implementation of the Cosmos Hub. **WARNING**: The SDK has mostly stabilized, but we are still making some breaking changes. -**Note**: Requires [Go 1.11+](https://golang.org/dl/) +**Note**: Requires [Go 1.11.4+](https://golang.org/dl/) ## Cosmos Hub Public Testnet diff --git a/Vagrantfile b/Vagrantfile index 84681e2b7653..6dc32dc974a1 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -46,6 +46,6 @@ Vagrant.configure("2") do |config| chown vagrant:vagrant /home/vagrant/.bash_profile su - vagrant -c 'source /home/vagrant/.bash_profile' - su - vagrant -c 'cd /home/vagrant/go/src/github.com/cosmos/cosmos-sdk && make get_tools' + su - vagrant -c 'cd /home/vagrant/go/src/github.com/cosmos/cosmos-sdk && make tools' SHELL end diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c3042b58833d..cfeb97214eeb 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -33,6 +33,9 @@ const ( runTxModeSimulate runTxMode = iota // Deliver a transaction runTxModeDeliver runTxMode = iota + + // MainStoreKey is the string representation of the main store + MainStoreKey = "main" ) // BaseApp reflects the ABCI application implementation. @@ -71,8 +74,9 @@ type BaseApp struct { // TODO move this in the future to baseapp param store on main store. consensusParams *abci.ConsensusParams - // spam prevention - minimumFees sdk.Coins + // The minimum gas prices a validator is willing to accept for processing a + // transaction. This is mainly used for DoS and spam prevention. + minGasPrices sdk.DecCoins // flag for sealing sealed bool @@ -210,14 +214,17 @@ func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error { return nil } -// SetMinimumFees sets the minimum fees. -func (app *BaseApp) SetMinimumFees(fees sdk.Coins) { app.minimumFees = fees } +func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) { + app.minGasPrices = gasPrices +} // NewContext returns a new Context with the correct store, the given header, and nil txBytes. func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { if isCheckTx { - return sdk.NewContext(app.checkState.ms, header, true, app.Logger).WithMinimumFees(app.minimumFees) + return sdk.NewContext(app.checkState.ms, header, true, app.Logger). + WithMinGasPrices(app.minGasPrices) } + return sdk.NewContext(app.deliverState.ms, header, false, app.Logger) } @@ -238,7 +245,7 @@ func (app *BaseApp) setCheckState(header abci.Header) { ms := app.cms.CacheMultiStore() app.checkState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinimumFees(app.minimumFees), + ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinGasPrices(app.minGasPrices), } } @@ -453,8 +460,9 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res } // Cache wrap the commit-multistore for safety. - ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger). - WithMinimumFees(app.minimumFees) + ctx := sdk.NewContext( + app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger, + ).WithMinGasPrices(app.minGasPrices) // Passes the rest of the path as an argument to the querier. // For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path @@ -501,6 +509,7 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg } else { gasMeter = sdk.NewInfiniteGasMeter() } + app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter) if app.beginBlocker != nil { @@ -730,7 +739,10 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk defer func() { if mode == runTxModeDeliver { ctx.BlockGasMeter().ConsumeGas( - ctx.GasMeter().GasConsumedToLimit(), "block gas meter") + ctx.GasMeter().GasConsumedToLimit(), + "block gas meter", + ) + if ctx.BlockGasMeter().GasConsumed() < startingGas { panic(sdk.ErrorGasOverflow{"tx gas summation"}) } @@ -749,23 +761,29 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk // Cache wrap context before anteHandler call in case it aborts. // This is required for both CheckTx and DeliverTx. - // https://github.com/cosmos/cosmos-sdk/issues/2772 + // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 + // // NOTE: Alternatively, we could require that anteHandler ensures that // writes do not happen if aborted/failed. This may have some // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) newCtx, result, abort := app.anteHandler(anteCtx, tx, (mode == runTxModeSimulate)) - if abort { - return result - } if !newCtx.IsZero() { - // At this point, newCtx.MultiStore() is cache wrapped, - // or something else replaced by anteHandler. - // We want the original ms, not one which was cache-wrapped - // for the ante handler. + // At this point, newCtx.MultiStore() is cache-wrapped, or something else + // replaced by the ante handler. We want the original multistore, not one + // which was cache-wrapped for the ante handler. + // + // Also, in the case of the tx aborting, we need to track gas consumed via + // the instantiated gas meter in the ante handler, so we update the context + // prior to returning. ctx = newCtx.WithMultiStore(ms) } + + if abort { + return result + } + msCache.Write() gasWanted = result.GasWanted } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 4678655d0e13..acccd1fbf6fe 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -7,6 +7,8 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/store" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -85,12 +87,13 @@ func TestMountStores(t *testing.T) { // Test that LoadLatestVersion actually does. func TestLoadVersion(t *testing.T) { logger := defaultLogger() + pruningOpt := SetPruning(store.PruneSyncable) db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, logger, db, nil) + app := NewBaseApp(name, logger, db, nil, pruningOpt) // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") + capKey := sdk.NewKVStoreKey(MainStoreKey) app.MountStores(capKey) err := app.LoadLatestVersion(capKey) // needed to make stores non-nil require.Nil(t, err) @@ -116,7 +119,7 @@ func TestLoadVersion(t *testing.T) { commitID2 := sdk.CommitID{2, res.Data} // reload with LoadLatestVersion - app = NewBaseApp(name, logger, db, nil) + app = NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores(capKey) err = app.LoadLatestVersion(capKey) require.Nil(t, err) @@ -124,7 +127,7 @@ func TestLoadVersion(t *testing.T) { // reload with LoadVersion, see if you can commit the same block and get // the same result - app = NewBaseApp(name, logger, db, nil) + app = NewBaseApp(name, logger, db, nil, pruningOpt) app.MountStores(capKey) err = app.LoadVersion(1, capKey) require.Nil(t, err) @@ -160,7 +163,7 @@ func testChangeNameHelper(name string) func(*BaseApp) { app := newBaseApp(t.Name()) // make a cap key and mount the store - capKey := sdk.NewKVStoreKey("main") + capKey := sdk.NewKVStoreKey(MainStoreKey) app.MountStores(capKey) err := app.LoadLatestVersion(capKey) // needed to make stores non-nil require.Nil(t, err) @@ -217,7 +220,7 @@ func TestInitChainer(t *testing.T) { db := dbm.NewMemDB() logger := defaultLogger() app := NewBaseApp(name, logger, db, nil) - capKey := sdk.NewKVStoreKey("main") + capKey := sdk.NewKVStoreKey(MainStoreKey) capKey2 := sdk.NewKVStoreKey("key2") app.MountStores(capKey, capKey2) diff --git a/baseapp/doc.go b/baseapp/doc.go index 631a01dc5043..d708d08bcd1f 100644 --- a/baseapp/doc.go +++ b/baseapp/doc.go @@ -4,7 +4,5 @@ functionality and act as a bridge between the ABCI interface and the SDK abstractions. BaseApp has no state except the CommitMultiStore you provide upon init. - -See examples/basecoin/app/* for usage. */ package baseapp diff --git a/baseapp/helpers.go b/baseapp/helpers.go index b6a0a36122e0..0ac99887a3bb 100644 --- a/baseapp/helpers.go +++ b/baseapp/helpers.go @@ -23,7 +23,7 @@ func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) { return app.runTx(runTxModeDeliver, nil, tx) } -// RunForever - BasecoinApp execution and cleanup +// RunForever BasecoinApp execution and cleanup func RunForever(app abci.Application) { // Start the ABCI server diff --git a/baseapp/options.go b/baseapp/options.go index 6e4104e506b8..8e6687335a3f 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -14,30 +14,18 @@ import ( // for options that need access to non-exported fields of the BaseApp // SetPruning sets a pruning option on the multistore associated with the app -func SetPruning(pruning string) func(*BaseApp) { - var pruningEnum sdk.PruningStrategy - switch pruning { - case "nothing": - pruningEnum = sdk.PruneNothing - case "everything": - pruningEnum = sdk.PruneEverything - case "syncable": - pruningEnum = sdk.PruneSyncable - default: - panic(fmt.Sprintf("invalid pruning strategy: %s", pruning)) - } - return func(bap *BaseApp) { - bap.cms.SetPruning(pruningEnum) - } +func SetPruning(opts sdk.PruningOptions) func(*BaseApp) { + return func(bap *BaseApp) { bap.cms.SetPruning(opts) } } -// SetMinimumFees returns an option that sets the minimum fees on the app. -func SetMinimumFees(minFees string) func(*BaseApp) { - fees, err := sdk.ParseCoins(minFees) +// SetMinimumGasPrices returns an option that sets the minimum gas prices on the app. +func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { + gasPrices, err := sdk.ParseDecCoins(gasPricesStr) if err != nil { - panic(fmt.Sprintf("invalid minimum fees: %v", err)) + panic(fmt.Sprintf("invalid minimum gas prices: %v", err)) } - return func(bap *BaseApp) { bap.SetMinimumFees(fees) } + + return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) } } func (app *BaseApp) SetName(name string) { diff --git a/client/config.go b/client/config.go index ab72ee78d44e..675a35296a62 100644 --- a/client/config.go +++ b/client/config.go @@ -24,7 +24,7 @@ var configDefaults map[string]string func init() { configDefaults = map[string]string{ - "chain_id": "", + "chain-id": "", "output": "text", "node": "tcp://localhost:26657", } @@ -78,7 +78,7 @@ func runConfigCmd(cmd *cobra.Command, args []string) error { // Get value action if getAction { switch key { - case "trace", "trust_node": + case "trace", "trust-node", "indent": fmt.Println(tree.GetDefault(key, false).(bool)) default: if defaultValue, ok := configDefaults[key]; ok { @@ -91,11 +91,14 @@ func runConfigCmd(cmd *cobra.Command, args []string) error { } // Set value action + if len(args) != 2 { + return fmt.Errorf("wrong number of arguments") + } value := args[1] switch key { - case "chain_id", "output", "node": + case "chain-id", "output", "node": tree.Set(key, value) - case "trace", "trust_node": + case "trace", "trust-node", "indent": boolVal, err := strconv.ParseBool(value) if err != nil { return err diff --git a/client/context/broadcast.go b/client/context/broadcast.go index 9f88ce7b9892..c844e519d120 100644 --- a/client/context/broadcast.go +++ b/client/context/broadcast.go @@ -104,7 +104,7 @@ func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastT } if ctx.Output != nil { - if ctx.JSON { + if ctx.OutputFormat == "json" { type toJSON struct { TxHash string } @@ -131,7 +131,7 @@ func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcast return res, err } - if ctx.JSON { + if ctx.OutputFormat == "json" { // Since JSON is intended for automated scripts, always include response in // JSON mode. type toJSON struct { diff --git a/client/context/context.go b/client/context/context.go index 9fac7c27a872..6950122c432e 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -24,8 +24,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" ) -const ctxAccStoreName = "acc" - var ( verifier tmlite.Verifier ) @@ -37,6 +35,7 @@ type CLIContext struct { AccDecoder auth.AccountDecoder Client rpcclient.Client Output io.Writer + OutputFormat string Height int64 NodeURI string From string @@ -44,7 +43,6 @@ type CLIContext struct { TrustNode bool UseLedger bool Async bool - JSON bool PrintResponse bool Verifier tmlite.Verifier Simulate bool @@ -76,13 +74,13 @@ func NewCLIContext() CLIContext { Client: rpc, Output: os.Stdout, NodeURI: nodeURI, - AccountStore: ctxAccStoreName, + AccountStore: auth.StoreKey, From: viper.GetString(client.FlagFrom), + OutputFormat: viper.GetString(cli.OutputFlag), Height: viper.GetInt64(client.FlagHeight), TrustNode: viper.GetBool(client.FlagTrustNode), UseLedger: viper.GetBool(client.FlagUseLedger), Async: viper.GetBool(client.FlagAsync), - JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), Verifier: verifier, Simulate: viper.GetBool(client.FlagDryRun), @@ -256,3 +254,28 @@ func (ctx CLIContext) WithSimulation(simulate bool) CLIContext { ctx.Simulate = simulate return ctx } + +// PrintOutput prints output while respecting output and indent flags +// NOTE: pass in marshalled structs that have been unmarshaled +// because this function will panic on marshaling errors +func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) { + var out []byte + + switch ctx.OutputFormat { + case "text": + out = []byte(toPrint.String()) + case "json": + if ctx.Indent { + out, err = ctx.Codec.MarshalJSONIndent(toPrint, "", " ") + } else { + out, err = ctx.Codec.MarshalJSON(toPrint) + } + } + + if err != nil { + return + } + + fmt.Println(string(out)) + return +} diff --git a/client/context/query.go b/client/context/query.go index 0eff95d30bab..aba8df19095e 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -70,7 +70,7 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { if err != nil { return nil, err } else if len(res) == 0 { - return nil, err + return nil, ErrInvalidAccount(address) } account, err := ctx.AccDecoder(res) diff --git a/client/flags.go b/client/flags.go index cf7be359e437..9ee121c2096c 100644 --- a/client/flags.go +++ b/client/flags.go @@ -2,6 +2,7 @@ package client import ( "fmt" + "os" "strconv" "github.com/spf13/cobra" @@ -15,7 +16,7 @@ const ( // occur between the tx simulation and the actual run. DefaultGasAdjustment = 1.0 DefaultGasLimit = 200000 - GasFlagSimulate = "simulate" + GasFlagAuto = "auto" FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -29,9 +30,9 @@ const ( FlagAccountNumber = "account-number" FlagSequence = "sequence" FlagMemo = "memo" - FlagFee = "fee" + FlagFees = "fees" + FlagGasPrices = "gas-prices" FlagAsync = "async" - FlagJson = "json" FlagPrintResponse = "print-response" FlagDryRun = "dry-run" FlagGenerateOnly = "generate-only" @@ -59,13 +60,13 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response") c.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") - c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) - viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID)) viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) + + c.MarkFlagRequired(FlagChainID) } return cmds } @@ -78,24 +79,24 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Uint64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Uint64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagMemo, "", "Memo to send along with transaction") - c.Flags().String(FlagFee, "", "Fee to pay along with transaction") - c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") + c.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 10stake,1atom") + c.Flags().String(FlagGasPrices, "", "Gas prices to determine the transaction fee (e.g. 0.00001stake)") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ") c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") - c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") 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") // --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)", GasFlagSimulate, 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(FlagChainID, c.Flags().Lookup(FlagChainID)) viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) + + c.MarkFlagRequired(FlagChainID) } return cmds } @@ -133,30 +134,62 @@ func (v *GasSetting) Type() string { return "string" } // Set parses and sets the value of the --gas flag. func (v *GasSetting) Set(s string) (err error) { - v.Simulate, v.Gas, err = ReadGasFlag(s) + v.Simulate, v.Gas, err = ParseGas(s) return } func (v *GasSetting) String() string { if v.Simulate { - return GasFlagSimulate + return GasFlagAuto } return strconv.FormatUint(v.Gas, 10) } -// ParseGasFlag parses the value of the --gas flag. -func ReadGasFlag(s string) (simulate bool, gas uint64, err error) { - switch s { +// ParseGas parses the value of the gas option. +func ParseGas(gasStr string) (simulateAndExecute bool, gas uint64, err error) { + switch gasStr { case "": gas = DefaultGasLimit - case GasFlagSimulate: - simulate = true + case GasFlagAuto: + simulateAndExecute = true default: - gas, err = strconv.ParseUint(s, 10, 64) + gas, err = strconv.ParseUint(gasStr, 10, 64) if err != nil { - err = fmt.Errorf("gas must be either integer or %q", GasFlagSimulate) + err = fmt.Errorf("gas must be either integer or %q", GasFlagAuto) return } } return } + +// NewCompletionCmd builds a cobra.Command that generate bash completion +// scripts for the given root command. If hidden is true, the command +// will not show up in the root command's list of available commands. +func NewCompletionCmd(rootCmd *cobra.Command, hidden bool) *cobra.Command { + flagZsh := "zsh" + cmd := &cobra.Command{ + Use: "completion", + Short: "Generate Bash/Zsh completion script to STDOUT", + Long: `To load completion script run + +. <(completion_script) + +To configure your bash shell to load completions for each session add to your bashrc + +# ~/.bashrc or ~/.profile +. <(completion_script) +`, + RunE: func(_ *cobra.Command, _ []string) error { + if viper.GetBool(flagZsh) { + return rootCmd.GenZshCompletion(os.Stdout) + } + return rootCmd.GenBashCompletion(os.Stdout) + }, + Hidden: hidden, + Args: cobra.NoArgs, + } + + cmd.Flags().Bool(flagZsh, false, "Generate Zsh completion script") + + return cmd +} diff --git a/client/keys/add.go b/client/keys/add.go index 8c2f666d833e..b3af588dc8b4 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -1,23 +1,29 @@ package keys import ( + "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "os" + "sort" + + "github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/go-bip39" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" ccrypto "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -28,6 +34,8 @@ const ( flagDryRun = "dry-run" flagAccount = "account" flagIndex = "index" + flagMultisig = "multisig" + flagNoSort = "nosort" ) func addKeyCommand() *cobra.Command { @@ -36,16 +44,28 @@ func addKeyCommand() *cobra.Command { Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk", Long: `Derive a new private key and encrypt to disk. Optionally specify a BIP39 mnemonic, a BIP39 passphrase to further secure the mnemonic, -and a bip32 HD path to derive a specific account. The key will be stored under the given name +and a bip32 HD path to derive a specific account. The key will be stored under the given name and encrypted with the given password. The only input that is required is the encryption password. If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase. The flag --recover allows one to recover a key from a seed passphrase. -If run with --dry-run, a key would be generated (or recovered) but not stored to the local keystore. +If run with --dry-run, a key would be generated (or recovered) but not stored to the +local keystore. +Use the --pubkey flag to add arbitrary public keys to the keystore for constructing +multisig transactions. + +You can add a multisig key by passing the list of key names you want the public +key to be composed of to the --multisig flag and the minimum number of signatures +required through --multisig-threshold. The keys are sorted by address, unless +the flag --nosort is set. `, Args: cobra.ExactArgs(1), RunE: runAddCmd, } + cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)") + cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") + cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") + cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk") cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic") cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key") @@ -73,6 +93,9 @@ func runAddCmd(cmd *cobra.Command, args []string) error { buf := client.BufferStdin() name := args[0] + + interactive := viper.GetBool(flagInteractive) + if viper.GetBool(flagDryRun) { // we throw this away, so don't enforce args, // we want to get a new random seed phrase quickly @@ -93,8 +116,40 @@ func runAddCmd(cmd *cobra.Command, args []string) error { } } + multisigKeys := viper.GetStringSlice(flagMultisig) + if len(multisigKeys) != 0 { + var pks []crypto.PubKey + + multisigThreshold := viper.GetInt(flagMultiSigThreshold) + if err := validateMultisigThreshold(multisigThreshold, len(multisigKeys)); err != nil { + return err + } + + for _, keyname := range multisigKeys { + k, err := kb.Get(keyname) + if err != nil { + return err + } + pks = append(pks, k.GetPubKey()) + } + + // Handle --nosort + if !viper.GetBool(flagNoSort) { + sort.Slice(pks, func(i, j int) bool { + return bytes.Compare(pks[i].Address(), pks[j].Address()) < 0 + }) + } + + pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) + if _, err := kb.CreateOffline(name, pk); err != nil { + return err + } + fmt.Fprintf(os.Stderr, "Key %q saved to disk.", name) + return nil + } + // ask for a password when generating a local key - if !viper.GetBool(client.FlagUseLedger) { + if viper.GetString(FlagPublicKey) == "" && !viper.GetBool(client.FlagUseLedger) { encryptPassword, err = client.GetCheckPassword( "Enter a passphrase to encrypt your key to disk:", "Repeat the passphrase:", buf) @@ -104,10 +159,16 @@ func runAddCmd(cmd *cobra.Command, args []string) error { } } - interactive := viper.GetBool(flagInteractive) - flags := cmd.Flags() - bipFlag := flags.Lookup(flagBIP44Path) + if viper.GetString(FlagPublicKey) != "" { + pk, err := sdk.GetAccPubKeyBech32(viper.GetString(FlagPublicKey)) + if err != nil { + return err + } + kb.CreateOffline(name, pk) + return nil + } + bipFlag := cmd.Flags().Lookup(flagBIP44Path) bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || !interactive) if err != nil { return err diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 9c1d8db4490b..0d680ada44ba 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -17,6 +17,7 @@ import ( client "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" @@ -27,18 +28,21 @@ import ( authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ( name1 = "test1" name2 = "test2" name3 = "test3" + memo = "LCD test tx" pw = app.DefaultKeyPass altPw = "12345678901" ) +var fees = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)} + func init() { mintkey.BcryptSecurityParameter = 1 version.Version = os.Getenv("VERSION") @@ -160,7 +164,7 @@ func TestCoinSend(t *testing.T) { initialBalance := acc.GetCoins() // create TX - receiveAddr, resultTx := doTransfer(t, port, seed, name1, pw, addr) + receiveAddr, resultTx := doTransfer(t, port, seed, name1, memo, pw, addr, fees) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -170,44 +174,65 @@ func TestCoinSend(t *testing.T) { // query sender acc = getAccount(t, port, addr) coins := acc.GetCoins() - mycoins := coins[0] + expectedBalance := initialBalance[0].Minus(fees[0]) - require.Equal(t, stakeTypes.DefaultBondDenom, mycoins.Denom) - require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) + require.Equal(t, stakingTypes.DefaultBondDenom, coins[0].Denom) + require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) + expectedBalance = coins[0] // query receiver - acc = getAccount(t, port, receiveAddr) - coins = acc.GetCoins() - mycoins = coins[0] - - require.Equal(t, stakeTypes.DefaultBondDenom, mycoins.Denom) - require.Equal(t, int64(1), mycoins.Amount.Int64()) + acc2 := getAccount(t, port, receiveAddr) + coins2 := acc2.GetCoins() + require.Equal(t, stakingTypes.DefaultBondDenom, coins2[0].Denom) + require.Equal(t, int64(1), coins2[0].Amount.Int64()) // test failure with too little gas - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, "100", 0, false, false) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "100", 0, false, false, fees) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + require.Nil(t, err) // test failure with negative gas - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, "-200", 0, false, false) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "-200", 0, false, false, fees) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + + // test failure with negative adjustment + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "10000", -0.1, true, false, fees) require.Equal(t, http.StatusBadRequest, res.StatusCode, body) // test failure with 0 gas - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, "0", 0, false, false) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "0", 0, false, false, fees) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // test failure with wrong adjustment - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, "simulate", 0.1, false, false) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, client.GasFlagAuto, 0.1, false, false, fees) + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // run simulation and test success with estimated gas - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, "", 0, true, false) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "10000", 1.0, true, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) var responseBody struct { GasEstimate int64 `json:"gas_estimate"` } require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) - res, body, _ = doTransferWithGas(t, port, seed, name1, pw, addr, fmt.Sprintf("%v", responseBody.GasEstimate), 0, false, false) + + acc = getAccount(t, port, addr) + require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) + + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, + fmt.Sprintf("%d", responseBody.GasEstimate), 1.0, false, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &resultTx) + require.Nil(t, err) + + tests.WaitForHeight(resultTx.Height+1, port) + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(1), acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) } func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { @@ -217,7 +242,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { acc := getAccount(t, port, addr) // generate TX - res, body, _ := doTransferWithGas(t, port, seed, name1, pw, addr, "simulate", 0, false, true) + res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, client.GasFlagAuto, 1, false, true, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) var msg auth.StdTx require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) @@ -225,7 +250,9 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { 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)) - gasEstimate := msg.Fee.Gas + require.Equal(t, memo, msg.Memo) + + gasEstimate := int64(msg.Fee.Gas) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() @@ -233,12 +260,11 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { var signedMsg auth.StdTx payload := authrest.SignBody{ - Tx: msg, - LocalAccountName: name1, - Password: pw, - ChainID: viper.GetString(client.FlagChainID), - AccountNumber: accnum, - Sequence: sequence, + Tx: msg, + BaseReq: utils.NewBaseReq( + name1, pw, "", viper.GetString(client.FlagChainID), "", "", + accnum, sequence, nil, nil, false, false, + ), } json, err := cdc.MarshalJSON(payload) require.Nil(t, err) @@ -265,8 +291,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { require.Nil(t, cdc.UnmarshalJSON([]byte(body), &resultTx)) require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) - require.Equal(t, gasEstimate, uint64(resultTx.DeliverTx.GasWanted)) - require.Equal(t, gasEstimate, uint64(resultTx.DeliverTx.GasUsed)) + require.Equal(t, gasEstimate, resultTx.DeliverTx.GasWanted) } func TestTxs(t *testing.T) { @@ -290,7 +315,7 @@ func TestTxs(t *testing.T) { require.Equal(t, emptyTxs, txs) // create tx - receiveAddr, resultTx := doTransfer(t, port, seed, name1, pw, addr) + receiveAddr, resultTx := doTransfer(t, port, seed, name1, memo, pw, addr, fees) tests.WaitForHeight(resultTx.Height+1, port) // check if tx is queryable @@ -301,6 +326,7 @@ func TestTxs(t *testing.T) { txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) require.Len(t, txs, 1) require.Equal(t, resultTx.Height, txs[0].Height) + fmt.Println(txs[0]) // query recipient txs = getTransactions(t, port, fmt.Sprintf("recipient=%s", receiveAddr.String())) @@ -313,20 +339,20 @@ func TestPoolParamsQuery(t *testing.T) { cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() - defaultParams := stake.DefaultParams() + defaultParams := staking.DefaultParams() - params := getStakeParams(t, port) + params := getStakingParams(t, port) require.True(t, defaultParams.Equal(params)) - pool := getStakePool(t, port) + pool := getStakingPool(t, port) - initialPool := stake.InitialPool() - initialPool.LooseTokens = initialPool.LooseTokens.Add(sdk.NewDec(100)) - initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState - initialPool.LooseTokens = initialPool.LooseTokens.Add(sdk.NewDec(int64(50))) // freeFermionsAcc = 50 on GaiaAppGenState + initialPool := staking.InitialPool() + initialPool.NotBondedTokens = initialPool.NotBondedTokens.Add(sdk.NewInt(100)) + initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewInt(100)) // Delegate tx on GaiaAppGenState + initialPool.NotBondedTokens = initialPool.NotBondedTokens.Add(sdk.NewInt(50)) // freeFermionsAcc = 50 on GaiaAppGenState require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) - require.Equal(t, initialPool.LooseTokens, pool.LooseTokens) + require.Equal(t, initialPool.NotBondedTokens, pool.NotBondedTokens) } func TestValidatorsQuery(t *testing.T) { @@ -371,8 +397,11 @@ func TestBonding(t *testing.T) { amt := sdk.NewDec(60) validator := getValidator(t, port, operAddrs[0]) + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + // create bond TX - resultTx := doDelegate(t, port, name1, pw, addr, operAddrs[0], 60) + resultTx := doDelegate(t, port, name1, pw, addr, operAddrs[0], 60, fees) tests.WaitForHeight(resultTx.Height+1, port) require.Equal(t, uint32(0), resultTx.CheckTx.Code) @@ -386,10 +415,12 @@ func TestBonding(t *testing.T) { require.Len(t, txs, 1) require.Equal(t, resultTx.Height, txs[0].Height) - acc := getAccount(t, port, addr) + // verify balance + acc = getAccount(t, port, addr) coins := acc.GetCoins() - - require.Equal(t, int64(40), coins.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + expectedBalance := initialBalance[0].Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(60), coins.AmountOf(stakingTypes.DefaultBondDenom)) + expectedBalance = coins[0] // query delegation bond := getDelegation(t, port, addr, operAddrs[0]) @@ -412,7 +443,7 @@ func TestBonding(t *testing.T) { require.Equal(t, operAddrs[0], bondedValidator.OperatorAddr) // testing unbonding - resultTx = doBeginUnbonding(t, port, name1, pw, addr, operAddrs[0], 30) + resultTx = doUndelegate(t, port, name1, pw, addr, operAddrs[0], 30, fees) tests.WaitForHeight(resultTx.Height+1, port) require.Equal(t, uint32(0), resultTx.CheckTx.Code) @@ -421,7 +452,13 @@ 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() - require.Equal(t, int64(40), coins.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + expectedBalance = expectedBalance.Minus(fees[0]) + require.True(t, + expectedBalance.Amount.LT(coins.AmountOf(stakingTypes.DefaultBondDenom)) || + expectedBalance.Amount.Equal(coins.AmountOf(stakingTypes.DefaultBondDenom)), + "should get tokens back from automatic withdrawal after an unbonding delegation", + ) + expectedBalance = coins[0] // query tx txs = getTransactions(t, port, @@ -431,16 +468,26 @@ func TestBonding(t *testing.T) { require.Len(t, txs, 1) require.Equal(t, resultTx.Height, txs[0].Height) - unbonding := getUndelegation(t, port, addr, operAddrs[0]) - require.Equal(t, "30", unbonding.Balance.Amount.String()) + ubd := getUnbondingDelegation(t, port, addr, operAddrs[0]) + require.Len(t, ubd.Entries, 1) + require.Equal(t, int64(30), ubd.Entries[0].Balance.Amount.Int64()) // test redelegation - resultTx = doBeginRedelegation(t, port, name1, pw, addr, operAddrs[0], operAddrs[1], 30) + resultTx = doBeginRedelegation(t, port, name1, pw, addr, operAddrs[0], operAddrs[1], 30, fees) tests.WaitForHeight(resultTx.Height+1, port) require.Equal(t, uint32(0), resultTx.CheckTx.Code) require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // verify balance after paying fees + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Minus(fees[0]) + require.True(t, + expectedBalance.Amount.LT(coins.AmountOf(stakingTypes.DefaultBondDenom)) || + expectedBalance.Amount.Equal(coins.AmountOf(stakingTypes.DefaultBondDenom)), + "should get tokens back from automatic withdrawal after an unbonding delegation", + ) + // query tx txs = getTransactions(t, port, fmt.Sprintf("action=begin_redelegate&delegator=%s", addr), @@ -453,23 +500,32 @@ func TestBonding(t *testing.T) { // query delegations, unbondings and redelegations from validator and delegator delegatorDels = getDelegatorDelegations(t, port, addr) require.Len(t, delegatorDels, 1) - require.Equal(t, "30.0000000000", delegatorDels[0].GetShares().String()) + require.Equal(t, "30.000000000000000000", delegatorDels[0].GetShares().String()) + + redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1]) + require.Len(t, redelegation, 1) + require.Len(t, redelegation[0].Entries, 1) + require.Equal(t, "30", redelegation[0].Entries[0].Balance.Amount.String()) delegatorUbds := getDelegatorUnbondingDelegations(t, port, addr) require.Len(t, delegatorUbds, 1) - require.Equal(t, "30", delegatorUbds[0].Balance.Amount.String()) + require.Len(t, delegatorUbds[0].Entries, 1) + require.Equal(t, "30", delegatorUbds[0].Entries[0].Balance.Amount.String()) - delegatorReds := getDelegatorRedelegations(t, port, addr) + delegatorReds := getRedelegations(t, port, addr, nil, nil) require.Len(t, delegatorReds, 1) - require.Equal(t, "30", delegatorReds[0].Balance.Amount.String()) + require.Len(t, delegatorReds[0].Entries, 1) + require.Equal(t, "30", delegatorReds[0].Entries[0].Balance.Amount.String()) validatorUbds := getValidatorUnbondingDelegations(t, port, operAddrs[0]) require.Len(t, validatorUbds, 1) - require.Equal(t, "30", validatorUbds[0].Balance.Amount.String()) + require.Len(t, validatorUbds[0].Entries, 1) + require.Equal(t, "30", validatorUbds[0].Entries[0].Balance.Amount.String()) - validatorReds := getValidatorRedelegations(t, port, operAddrs[0]) + validatorReds := getRedelegations(t, port, nil, operAddrs[0], nil) require.Len(t, validatorReds, 1) - require.Equal(t, "30", validatorReds[0].Balance.Amount.String()) + require.Len(t, validatorReds[0].Entries, 1) + require.Equal(t, "30", validatorReds[0].Entries[0].Balance.Amount.String()) // TODO Undonding status not currently implemented // require.Equal(t, sdk.Unbonding, bondedValidators[0].Status) @@ -493,8 +549,11 @@ func TestSubmitProposal(t *testing.T) { cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 5) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 5, fees) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -504,14 +563,18 @@ func TestSubmitProposal(t *testing.T) { var proposalID uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) + // verify balance + acc = getAccount(t, port, addr) + expectedBalance := initialBalance[0].Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(5), acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) + // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) - // query tx - txs := getTransactions(t, port, fmt.Sprintf("action=submit_proposal&proposer=%s", addr)) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + proposer := getProposer(t, port, proposalID) + require.Equal(t, addr.String(), proposer.Proposer) + require.Equal(t, proposalID, proposer.ProposalID) } func TestDeposit(t *testing.T) { @@ -519,8 +582,11 @@ func TestDeposit(t *testing.T) { cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 5) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 5, fees) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -530,14 +596,26 @@ func TestDeposit(t *testing.T) { var proposalID uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) + // verify balance + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(5), coins.AmountOf(stakingTypes.DefaultBondDenom)) + expectedBalance = coins[0] + // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name1, pw, addr, proposalID, 5) + resultTx = doDeposit(t, port, seed, name1, pw, addr, proposalID, 5, fees) tests.WaitForHeight(resultTx.Height+1, port) + // verify balance after deposit and fee + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(5), acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) + // query tx txs := getTransactions(t, port, fmt.Sprintf("action=deposit&depositor=%s", addr)) require.Len(t, txs, 1) @@ -545,11 +623,11 @@ func TestDeposit(t *testing.T) { // query proposal proposal = getProposal(t, port, proposalID) - require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)})) + require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)})) // query deposit deposit := getDeposit(t, port, proposalID, addr) - require.True(t, deposit.Amount.IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)})) + require.True(t, deposit.Amount.IsEqual(sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)})) } func TestVote(t *testing.T) { @@ -557,8 +635,11 @@ func TestVote(t *testing.T) { cleanup, _, operAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 5) + resultTx := doSubmitProposal(t, port, seed, name1, pw, addr, 10, fees) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -568,22 +649,29 @@ func TestVote(t *testing.T) { var proposalID uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID) + // verify balance + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(10), coins.AmountOf(stakingTypes.DefaultBondDenom)) + expectedBalance = coins[0] + // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) - - // deposit - resultTx = doDeposit(t, port, seed, name1, pw, addr, proposalID, 5) - tests.WaitForHeight(resultTx.Height+1, port) - - // query proposal - proposal = getProposal(t, port, proposalID) require.Equal(t, gov.StatusVotingPeriod, proposal.GetStatus()) // vote - resultTx = doVote(t, port, seed, name1, pw, addr, proposalID) + resultTx = doVote(t, port, seed, name1, pw, addr, proposalID, "Yes", fees) tests.WaitForHeight(resultTx.Height+1, port) + // verify balance after vote and fee + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + expectedBalance = expectedBalance.Minus(fees[0]) + require.Equal(t, expectedBalance.Amount, coins.AmountOf(stakingTypes.DefaultBondDenom)) + expectedBalance = coins[0] + // query tx txs := getTransactions(t, port, fmt.Sprintf("action=vote&voter=%s", addr)) require.Len(t, txs, 1) @@ -597,15 +685,31 @@ func TestVote(t *testing.T) { require.Equal(t, sdk.ZeroDec(), tally.Yes, "tally should be 0 as the address is not bonded") // create bond TX - resultTx = doDelegate(t, port, name1, pw, addr, operAddrs[0], 60) + resultTx = doDelegate(t, port, name1, pw, addr, operAddrs[0], 60, fees) tests.WaitForHeight(resultTx.Height+1, port) - // vote - resultTx = doVote(t, port, seed, name1, pw, addr, proposalID) - tests.WaitForHeight(resultTx.Height+1, port) + // verify balance + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + expectedBalance = expectedBalance.Minus(fees[0]) + require.Equal(t, expectedBalance.Amount.SubRaw(60), coins.AmountOf(stakingTypes.DefaultBondDenom)) + expectedBalance = coins[0] tally = getTally(t, port, proposalID) require.Equal(t, sdk.NewDec(60), tally.Yes, "tally should be equal to the amount delegated") + + // change vote option + resultTx = doVote(t, port, seed, name1, pw, addr, proposalID, "No", fees) + tests.WaitForHeight(resultTx.Height+1, port) + + // verify balance + acc = getAccount(t, port, addr) + expectedBalance = expectedBalance.Minus(fees[0]) + require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) + + tally = getTally(t, port, proposalID) + require.Equal(t, sdk.ZeroDec(), tally.Yes, "tally should be 0 the user changed the option") + require.Equal(t, sdk.NewDec(60), tally.No, "tally should be equal to the amount delegated") } func TestUnjail(t *testing.T) { @@ -630,32 +734,32 @@ func TestProposalsQuery(t *testing.T) { defer cleanup() depositParam := getDepositParam(t, port) - halfMinDeposit := depositParam.MinDeposit.AmountOf(stakeTypes.DefaultBondDenom).Int64() / 2 + halfMinDeposit := depositParam.MinDeposit.AmountOf(stakingTypes.DefaultBondDenom).Int64() / 2 getVotingParam(t, port) getTallyingParam(t, port) // Addr1 proposes (and deposits) proposals #1 and #2 - resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit) + resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit, fees) var proposalID1 uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit) + resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit, fees) var proposalID2 uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 - resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], halfMinDeposit) + resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], halfMinDeposit, fees) var proposalID3 uint64 cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 - resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, halfMinDeposit) + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, halfMinDeposit, fees) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, halfMinDeposit) + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, halfMinDeposit, fees) tests.WaitForHeight(resultTx.Height+1, port) // check deposits match proposal and individual deposits @@ -677,7 +781,7 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, deposit, deposits[0]) // increasing the amount of the deposit should update the existing one - resultTx = doDeposit(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID1, 1) + resultTx = doDeposit(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID1, 1, fees) tests.WaitForHeight(resultTx.Height+1, port) deposits = getDeposits(t, port, proposalID1) @@ -695,13 +799,13 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, proposals[1].GetProposalID()) // Addr1 votes on proposals #2 & #3 - resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2, "Yes", fees) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3, "Yes", fees) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 votes on proposal #3 - resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3) + resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, "Yes", fees) tests.WaitForHeight(resultTx.Height+1, port) // Test query all proposals diff --git a/client/lcd/root.go b/client/lcd/root.go index ad7ba2ff051d..07aade96b332 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -41,11 +41,6 @@ type RestServer struct { func NewRestServer(cdc *codec.Codec) *RestServer { r := mux.NewRouter() cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc) - - // Register version methods on the router - r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") - r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") return &RestServer{ @@ -82,68 +77,52 @@ func (rs *RestServer) Start(listenAddr string, sslHosts string, rs.log.Error("error closing listener", "err", err) }) - // TODO: re-enable insecure mode once #2715 has been addressed + rs.listener, err = rpcserver.Listen( + listenAddr, + rpcserver.Config{MaxOpenConnections: maxOpen}, + ) + if err != nil { + return + } + rs.log.Info("Starting Gaia Lite REST service...") + + // launch rest-server in insecure mode if insecure { - fmt.Println( - "Insecure mode is temporarily disabled, please locally generate an " + - "SSL certificate to test. Support will be re-enabled soon!", - ) - // listener, err = rpcserver.StartHTTPServer( - // listenAddr, handler, logger, - // rpcserver.Config{MaxOpenConnections: maxOpen}, - // ) - // if err != nil { - // return - // } - } else { - if certFile != "" { - // validateCertKeyFiles() is needed to work around tendermint/tendermint#2460 - err = validateCertKeyFiles(certFile, keyFile) - if err != nil { - return err - } - - // cert/key pair is provided, read the fingerprint - rs.fingerprint, err = fingerprintFromFile(certFile) - if err != nil { - return err - } - } else { - // if certificate is not supplied, generate a self-signed one - certFile, keyFile, rs.fingerprint, err = genCertKeyFilesAndReturnFingerprint(sslHosts) - if err != nil { - return err - } - - defer func() { - os.Remove(certFile) - os.Remove(keyFile) - }() + return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log) + } + + // handle certificates + if certFile != "" { + // validateCertKeyFiles() is needed to work around tendermint/tendermint#2460 + if err := validateCertKeyFiles(certFile, keyFile); err != nil { + return err } - rs.listener, err = rpcserver.Listen( - listenAddr, - rpcserver.Config{MaxOpenConnections: maxOpen}, - ) + // cert/key pair is provided, read the fingerprint + rs.fingerprint, err = fingerprintFromFile(certFile) if err != nil { - return + return err } - - rs.log.Info("Starting Gaia Lite REST service...") - rs.log.Info(rs.fingerprint) - - err := rpcserver.StartHTTPAndTLSServer( - rs.listener, - rs.Mux, - certFile, keyFile, - rs.log, - ) + } else { + // if certificate is not supplied, generate a self-signed one + certFile, keyFile, rs.fingerprint, err = genCertKeyFilesAndReturnFingerprint(sslHosts) if err != nil { return err } + + defer func() { + os.Remove(certFile) + os.Remove(keyFile) + }() } - return nil + rs.log.Info(rs.fingerprint) + return rpcserver.StartHTTPAndTLSServer( + rs.listener, + rs.Mux, + certFile, keyFile, + rs.log, + ) } // ServeCommand will start a Gaia Lite REST service as a blocking process. It diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 417b25b879b3..eed9d7e27691 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -18,11 +18,12 @@ tags: - name: ICS23 description: Slashing module APIs - name: ICS24 - description: Fee distribution module APIs + description: WIP - Fee distribution module APIs - name: version description: Query app version schemes: - https +host: fabo.interblock.io:1317 securityDefinitions: kms: type: basic @@ -286,20 +287,10 @@ paths: schema: type: object properties: + base_req: + $ref: "#/definitions/BaseReq" tx: $ref: "#/definitions/StdTx" - name: - type: string - password: - type: string - chain_id: - type: string - account_number: - type: string - example: "0" - sequence: - type: string - example: "0" append_sig: type: boolean example: true @@ -620,7 +611,7 @@ paths: description: No content about this account address 500: description: Server internel error - /stake/delegators/{delegatorAddr}/delegations: + /staking/delegators/{delegatorAddr}/delegations: parameters: - in: path name: delegatorAddr @@ -678,7 +669,7 @@ paths: description: Key password is wrong 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/delegations/{validatorAddr}: + /staking/delegators/{delegatorAddr}/delegations/{validatorAddr}: parameters: - in: path name: delegatorAddr @@ -705,7 +696,7 @@ paths: description: Invalid delegator address or validator address 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/unbonding_delegations: + /staking/delegators/{delegatorAddr}/unbonding_delegations: parameters: - in: path name: delegatorAddr @@ -774,7 +765,7 @@ paths: description: Key password is wrong 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: + /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: parameters: - in: path name: delegatorAddr @@ -803,15 +794,25 @@ paths: description: Invalid delegator address or validator address 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/redelegations: + /staking/redelegations: parameters: - - in: path - name: delegatorAddr + - in: query + name: delegator description: Bech32 AccAddress of Delegator - required: true + required: false + type: string + - in: query + name: validator_from + description: Bech32 ValAddress of SrcValidator + required: false + type: string + - in: query + name: validator_to + description: Bech32 ValAddress of DstValidator + required: false type: string get: - summary: Get all redelegations from a delegator + summary: Get all redelegations (filter by query params) tags: - ICS21 produces: @@ -823,10 +824,15 @@ paths: type: array items: $ref: "#/definitions/Redelegation" - 400: - description: Invalid delegator address 500: description: Internal Server Error + /staking/delegators/{delegatorAddr}/redelegations: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string post: summary: Submit a redelegation parameters: @@ -874,7 +880,7 @@ paths: description: Key password is wrong 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/validators: + /staking/delegators/{delegatorAddr}/validators: parameters: - in: path name: delegatorAddr @@ -898,7 +904,7 @@ paths: description: Invalid delegator address 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/validators/{validatorAddr}: + /staking/delegators/{delegatorAddr}/validators/{validatorAddr}: parameters: - in: path name: delegatorAddr @@ -925,7 +931,7 @@ paths: description: Invalid delegator address or validator address 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/txs: + /staking/delegators/{delegatorAddr}/txs: parameters: - in: path name: delegatorAddr @@ -951,7 +957,7 @@ paths: description: Invalid delegator address 500: description: Internal Server Error - /stake/validators: + /staking/validators: get: summary: Get all validator candidates tags: @@ -967,7 +973,7 @@ paths: $ref: "#/definitions/Validator" 500: description: Internal Server Error - /stake/validators/{validatorAddr}: + /staking/validators/{validatorAddr}: parameters: - in: path name: validatorAddr @@ -989,7 +995,7 @@ paths: description: Invalid validator address 500: description: Internal Server Error - /stake/validators/{validatorAddr}/delegations: + /staking/validators/{validatorAddr}/delegations: parameters: - in: path name: validatorAddr @@ -1013,7 +1019,7 @@ paths: description: Invalid validator address 500: description: Internal Server Error - /stake/validators/{validatorAddr}/unbonding_delegations: + /staking/validators/{validatorAddr}/unbonding_delegations: parameters: - in: path name: validatorAddr @@ -1037,31 +1043,7 @@ paths: description: Invalid validator address 500: description: Internal Server Error - /stake/validators/{validatorAddr}/redelegations: - parameters: - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string - get: - summary: Get all outgoing redelegations from a validator - tags: - - ICS21 - produces: - - application/json - responses: - 200: - description: OK - schema: - type: array - items: - $ref: "#/definitions/Redelegation" - 400: - description: Invalid validator address - 500: - description: Internal Server Error - /stake/pool: + /staking/pool: get: summary: Get the current state of the staking pool tags: @@ -1088,7 +1070,7 @@ paths: type: string 500: description: Internal Server Error - /stake/parameters: + /staking/parameters: get: summary: Get the current staking parameter values tags: @@ -1316,6 +1298,28 @@ paths: description: Invalid proposal id 500: description: Internal Server Error + /gov/proposals/{proposalId}/proposer: + get: + summary: Query proposer + description: Query for the proposer for a proposal + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Proposer" + 400: + description: Invalid proposal ID + 500: + description: Internal Server Error /gov/proposals/{proposalId}/deposits: get: summary: Query deposits @@ -2208,23 +2212,36 @@ definitions: properties: name: type: string + example: "my_name" password: type: string example: "12345678" + memo: + type: string + example: "Sent via Cosmos Voyager 🚀" chain_id: type: string + example: "Cosmos-Hub" account_number: type: string example: "0" sequence: type: string - example: "0" + example: "1" gas: type: string example: "200000" gas_adjustment: type: string example: "1.2" + fees: + type: array + items: + $ref: "#/definitions/Coin" + gas_prices: + type: array + items: + $ref: "#/definitions/DecCoin" generate_only: type: boolean example: false @@ -2258,7 +2275,7 @@ definitions: type: string proposal_status: type: string - tally_result: + final_tally_result: $ref: "#/definitions/TallyResult" submit_time: type: string @@ -2268,6 +2285,13 @@ definitions: $ref: "#/definitions/Coin" voting_start_time: type: string + Proposer: + type: object + properties: + proposal_id: + type: integer + proposer: + type: string Deposit: type: object properties: diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index e1272f173806..da41e196b276 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -15,13 +15,14 @@ import ( "strings" "testing" + "github.com/tendermint/tendermint/crypto/secp256k1" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/tendermint/tendermint/crypto/secp256k1" - ctypes "github.com/tendermint/tendermint/rpc/core/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" @@ -35,7 +36,8 @@ import ( "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/stake" + gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" + "github.com/cosmos/cosmos-sdk/x/staking" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -60,7 +62,7 @@ import ( bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest" govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" - stakeRest "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest" ) // makePathname creates a unique pathname for each test. It will panic if it @@ -225,8 +227,8 @@ func InitializeTestLCD( logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger = log.NewFilter(logger, log.AllowError()) - privValidatorFile := config.PrivValidatorFile() - privVal := pvm.LoadOrGenFilePV(privValidatorFile) + privVal := pvm.LoadOrGenFilePV(config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile()) privVal.Reset() db := dbm.NewMemDB() @@ -245,18 +247,18 @@ func InitializeTestLCD( for i := 0; i < nValidators; i++ { operPrivKey := secp256k1.GenPrivKey() operAddr := operPrivKey.PubKey().Address() - pubKey := privVal.PubKey + pubKey := privVal.GetPubKey() delegation := 100 if i > 0 { pubKey = ed25519.GenPrivKey().PubKey() delegation = 1 } - msg := stake.NewMsgCreateValidator( + msg := staking.NewMsgCreateValidator( sdk.ValAddress(operAddr), pubKey, - sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(int64(delegation))), - stake.Description{Moniker: fmt.Sprintf("validator-%d", i+1)}, - stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(int64(delegation))), + staking.Description{Moniker: fmt.Sprintf("validator-%d", i+1)}, + staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), ) stdSignMsg := txbuilder.StdSignMsg{ ChainID: genDoc.ChainID, @@ -272,7 +274,7 @@ func InitializeTestLCD( valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr)) - accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 150)} + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 150)} accs = append(accs, gapp.NewGenesisAccount(&accAuth)) } @@ -286,10 +288,10 @@ func InitializeTestLCD( // add some tokens to init accounts for _, addr := range initAddrs { accAuth := auth.NewBaseAccountWithAddress(addr) - accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 100)} + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 100)} acc := gapp.NewGenesisAccount(&accAuth) genesisState.Accounts = append(genesisState.Accounts, acc) - genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewDec(100)) + genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(sdk.NewInt(100)) } appState, err := codec.MarshalJSONIndent(cdc, genesisState) @@ -386,9 +388,9 @@ 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, "acc") + authRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey) bankRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) - stakeRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + stakingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) slashingRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) govRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) } @@ -643,12 +645,10 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { 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, - LocalAccountName: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, + Tx: msg, + BaseReq: utils.NewBaseReq( + name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false, + ), } json, err := cdc.MarshalJSON(payload) require.Nil(t, err) @@ -678,8 +678,8 @@ type broadcastReq struct { // 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, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { - res, body, receiveAddr := doTransferWithGas(t, port, seed, name, password, addr, "", 0, false, false) +func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, fees sdk.Coins) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { + 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) @@ -688,8 +688,8 @@ func doTransfer(t *testing.T, port, seed, name, password string, addr sdk.AccAdd return receiveAddr, resultTx } -func doTransferWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas string, - gasAdjustment float64, simulate, generateOnly bool) ( +func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, gas string, + gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins) ( res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address @@ -703,25 +703,15 @@ func doTransferWithGas(t *testing.T, port, seed, name, password string, addr sdk sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - sr := sendReq{ - Amount: sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1)}, - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - Simulate: simulate, - GenerateOnly: generateOnly, - }, - } - - if len(gas) != 0 { - sr.BaseReq.Gas = gas - } + baseReq := utils.NewBaseReq( + name, password, memo, chainID, gas, + fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, + generateOnly, simulate, + ) - if gasAdjustment > 0 { - sr.BaseReq.GasAdjustment = fmt.Sprintf("%f", gasAdjustment) + sr := sendReq{ + Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)}, + BaseReq: baseReq, } req, err := cdc.MarshalJSON(sr) @@ -740,34 +730,30 @@ type sendReq struct { // ICS 21 - Stake // ---------------------------------------------------------------------- -// POST /stake/delegators/{delegatorAddr}/delegations Submit delegation +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation func doDelegate(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { - + delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - ed := msgDelegationsInput{ - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + msg := msgDelegationsInput{ + BaseReq: baseReq, DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, amount), + Delegation: sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, amount), } - req, err := cdc.MarshalJSON(ed) + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr.String()), req) + res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/delegations", delAddr.String()), req) require.Equal(t, http.StatusOK, res.StatusCode, body) - var results ctypes.ResultBroadcastTxCommit - err = cdc.UnmarshalJSON([]byte(body), &results) + + var result ctypes.ResultBroadcastTxCommit + err = cdc.UnmarshalJSON([]byte(body), &result) require.Nil(t, err) - return results + + return result } type msgDelegationsInput struct { @@ -777,79 +763,70 @@ type msgDelegationsInput struct { Delegation sdk.Coin `json:"delegation"` } -// POST /stake/delegators/{delegatorAddr}/delegations Submit delegation -func doBeginUnbonding(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation +func doUndelegate(t *testing.T, port, name, password string, + delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - ed := msgBeginUnbondingInput{ - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + msg := msgUndelegateInput{ + BaseReq: baseReq, DelegatorAddr: delAddr, ValidatorAddr: valAddr, SharesAmount: sdk.NewDec(amount), } - req, err := cdc.MarshalJSON(ed) + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations", delAddr), req) + res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delAddr), req) require.Equal(t, http.StatusOK, res.StatusCode, body) - var results ctypes.ResultBroadcastTxCommit - err = cdc.UnmarshalJSON([]byte(body), &results) + var result ctypes.ResultBroadcastTxCommit + err = cdc.UnmarshalJSON([]byte(body), &result) require.Nil(t, err) - return results + return result } -type msgBeginUnbondingInput struct { +type msgUndelegateInput struct { BaseReq utils.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"` } -// POST /stake/delegators/{delegatorAddr}/delegations Submit delegation +// POST /staking/delegators/{delegatorAddr}/delegations Submit delegation func doBeginRedelegation(t *testing.T, port, name, password string, - delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { + delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, delAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - ed := msgBeginRedelegateInput{ - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + + msg := msgBeginRedelegateInput{ + BaseReq: baseReq, DelegatorAddr: delAddr, ValidatorSrcAddr: valSrcAddr, ValidatorDstAddr: valDstAddr, SharesAmount: sdk.NewDec(amount), } - req, err := cdc.MarshalJSON(ed) + req, err := cdc.MarshalJSON(msg) require.NoError(t, err) - res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/redelegations", delAddr), req) + res, body := Request(t, port, "POST", fmt.Sprintf("/staking/delegators/%s/redelegations", delAddr), req) require.Equal(t, http.StatusOK, res.StatusCode, body) - var results ctypes.ResultBroadcastTxCommit - err = cdc.UnmarshalJSON([]byte(body), &results) + var result ctypes.ResultBroadcastTxCommit + err = cdc.UnmarshalJSON([]byte(body), &result) require.Nil(t, err) - return results + return result } type msgBeginRedelegateInput struct { @@ -860,12 +837,12 @@ type msgBeginRedelegateInput struct { SharesAmount sdk.Dec `json:"shares"` } -// GET /stake/delegators/{delegatorAddr}/delegations Get all delegations from a delegator -func getDelegatorDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.Delegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/delegations", delegatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/delegations Get all delegations from a delegator +func getDelegatorDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/delegations", delegatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var dels []stake.Delegation + var dels []staking.Delegation err := cdc.UnmarshalJSON([]byte(body), &dels) require.Nil(t, err) @@ -873,12 +850,12 @@ func getDelegatorDelegations(t *testing.T, port string, delegatorAddr sdk.AccAdd return dels } -// GET /stake/delegators/{delegatorAddr}/unbonding_delegations Get all unbonding delegations from a delegator -func getDelegatorUnbondingDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.UnbondingDelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations", delegatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/unbonding_delegations Get all unbonding delegations from a delegator +func getDelegatorUnbondingDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.UnbondingDelegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations", delegatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var ubds []stake.UnbondingDelegation + var ubds []staking.UnbondingDelegation err := cdc.UnmarshalJSON([]byte(body), &ubds) require.Nil(t, err) @@ -886,25 +863,34 @@ func getDelegatorUnbondingDelegations(t *testing.T, port string, delegatorAddr s return ubds } -// GET /stake/delegators/{delegatorAddr}/redelegations Get all redelegations from a delegator -func getDelegatorRedelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.Redelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/redelegations", delegatorAddr), nil) +// GET /staking/redelegations?delegator=0xdeadbeef&validator_from=0xdeadbeef&validator_to=0xdeadbeef& Get redelegations filters by params passed in +func getRedelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress, srcValidatorAddr sdk.ValAddress, dstValidatorAddr sdk.ValAddress) []staking.Redelegation { + var res *http.Response + var body string + endpoint := "/staking/redelegations?" + if !delegatorAddr.Empty() { + endpoint += fmt.Sprintf("delegator=%s&", delegatorAddr) + } + if !srcValidatorAddr.Empty() { + endpoint += fmt.Sprintf("validator_from=%s&", srcValidatorAddr) + } + if !dstValidatorAddr.Empty() { + endpoint += fmt.Sprintf("validator_to=%s&", dstValidatorAddr) + } + res, body = Request(t, port, "GET", endpoint, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - - var reds []stake.Redelegation - - err := cdc.UnmarshalJSON([]byte(body), &reds) + var redels []staking.Redelegation + err := cdc.UnmarshalJSON([]byte(body), &redels) require.Nil(t, err) - - return reds + return redels } -// GET /stake/delegators/{delegatorAddr}/validators Query all validators that a delegator is bonded to -func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.Validator { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/validators", delegatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/validators Query all validators that a delegator is bonded to +func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/validators", delegatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bondedValidators []stake.Validator + var bondedValidators []staking.Validator err := cdc.UnmarshalJSON([]byte(body), &bondedValidators) require.Nil(t, err) @@ -912,27 +898,27 @@ func getDelegatorValidators(t *testing.T, port string, delegatorAddr sdk.AccAddr return bondedValidators } -// GET /stake/delegators/{delegatorAddr}/validators/{validatorAddr} Query a validator that a delegator is bonded to -func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.Validator { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/validators/%s", delegatorAddr, validatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/validators/{validatorAddr} Query a validator that a delegator is bonded to +func getDelegatorValidator(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/validators/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bondedValidator stake.Validator + var bondedValidator staking.Validator err := cdc.UnmarshalJSON([]byte(body), &bondedValidator) require.Nil(t, err) return bondedValidator } -// GET /stake/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator +// GET /staking/delegators/{delegatorAddr}/txs Get all staking txs (i.e msgs) from a delegator func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info { var res *http.Response var body string if len(query) > 0 { - res, body = Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/txs?type=%s", delegatorAddr, query), nil) + res, body = Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/txs?type=%s", delegatorAddr, query), nil) } else { - res, body = Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/txs", delegatorAddr), nil) + res, body = Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/txs", delegatorAddr), nil) } require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -944,107 +930,100 @@ func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, quer return txs } -// GET /stake/delegators/{delegatorAddr}/delegations/{validatorAddr} Query the current delegation between a delegator and a validator -func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.Delegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/delegations/%s", delegatorAddr, validatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/delegations/{validatorAddr} Query the current delegation between a delegator and a validator +func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) staking.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/delegations/%s", delegatorAddr, validatorAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var bond stake.Delegation + var bond staking.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) require.Nil(t, err) return bond } -// GET /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} Query all unbonding delegations between a delegator and a validator -func getUndelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.UnbondingDelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations/%s", delegatorAddr, validatorAddr), nil) +// GET /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} Query all unbonding delegations between a delegator and a validator +func getUnbondingDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress) staking.UnbondingDelegation { + + res, body := Request(t, port, "GET", + fmt.Sprintf("/staking/delegators/%s/unbonding_delegations/%s", + delegatorAddr, validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) - var unbond stake.UnbondingDelegation + var unbond staking.UnbondingDelegation err := cdc.UnmarshalJSON([]byte(body), &unbond) require.Nil(t, err) return unbond } -// GET /stake/validators Get all validator candidates -func getValidators(t *testing.T, port string) []stake.Validator { - res, body := Request(t, port, "GET", "/stake/validators", nil) +// GET /staking/validators Get all validator candidates +func getValidators(t *testing.T, port string) []staking.Validator { + res, body := Request(t, port, "GET", "/staking/validators", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var validators []stake.Validator + var validators []staking.Validator err := cdc.UnmarshalJSON([]byte(body), &validators) require.Nil(t, err) return validators } -// GET /stake/validators/{validatorAddr} Query the information from a single validator -func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) stake.Validator { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", validatorAddr.String()), nil) +// GET /staking/validators/{validatorAddr} Query the information from a single validator +func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) staking.Validator { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var validator stake.Validator + var validator staking.Validator err := cdc.UnmarshalJSON([]byte(body), &validator) require.Nil(t, err) return validator } -// GET /stake/validators/{validatorAddr}/delegations Get all delegations from a validator -func getValidatorDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.Delegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/delegations", validatorAddr.String()), nil) +// GET /staking/validators/{validatorAddr}/delegations Get all delegations from a validator +func getValidatorDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []staking.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s/delegations", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var delegations []stake.Delegation + var delegations []staking.Delegation err := cdc.UnmarshalJSON([]byte(body), &delegations) require.Nil(t, err) return delegations } -// GET /stake/validators/{validatorAddr}/unbonding_delegations Get all unbonding delegations from a validator -func getValidatorUnbondingDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.UnbondingDelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/unbonding_delegations", validatorAddr.String()), nil) +// GET /staking/validators/{validatorAddr}/unbonding_delegations Get all unbonding delegations from a validator +func getValidatorUnbondingDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []staking.UnbondingDelegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/staking/validators/%s/unbonding_delegations", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var ubds []stake.UnbondingDelegation + var ubds []staking.UnbondingDelegation err := cdc.UnmarshalJSON([]byte(body), &ubds) require.Nil(t, err) return ubds } -// GET /stake/validators/{validatorAddr}/redelegations Get all outgoing redelegations from a validator -func getValidatorRedelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.Redelegation { - res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/redelegations", validatorAddr.String()), nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) - - var reds []stake.Redelegation - err := cdc.UnmarshalJSON([]byte(body), &reds) - require.Nil(t, err) - - return reds -} - -// GET /stake/pool Get the current state of the staking pool -func getStakePool(t *testing.T, port string) stake.Pool { - res, body := Request(t, port, "GET", "/stake/pool", nil) +// GET /staking/pool Get the current state of the staking pool +func getStakingPool(t *testing.T, port string) staking.Pool { + res, body := Request(t, port, "GET", "/staking/pool", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) require.NotNil(t, body) - var pool stake.Pool + var pool staking.Pool err := cdc.UnmarshalJSON([]byte(body), &pool) require.Nil(t, err) return pool } -// GET /stake/parameters Get the current staking parameter values -func getStakeParams(t *testing.T, port string) stake.Params { - res, body := Request(t, port, "GET", "/stake/parameters", nil) +// GET /staking/parameters Get the current staking parameter values +func getStakingParams(t *testing.T, port string) staking.Params { + res, body := Request(t, port, "GET", "/staking/parameters", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var params stake.Params + var params staking.Params err := cdc.UnmarshalJSON([]byte(body), ¶ms) require.Nil(t, err) return params @@ -1054,26 +1033,20 @@ func getStakeParams(t *testing.T, port string) stake.Params { // ICS 22 - Gov // ---------------------------------------------------------------------- // POST /gov/proposals Submit a proposal -func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { - +func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - chainID := viper.GetString(client.FlagChainID) + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + pr := postProposalReq{ Title: "Test", Description: "test", ProposalType: "Text", Proposer: proposerAddr, - InitialDeposit: sdk.Coins{sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(amount))}, - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + InitialDeposit: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}, + BaseReq: baseReq, } req, err := cdc.MarshalJSON(pr) @@ -1155,23 +1128,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 int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, amount int64, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - chainID := viper.GetString(client.FlagChainID) + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + dr := depositReq{ Depositor: proposerAddr, - Amount: sdk.Coins{sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(amount))}, - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + Amount: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}, + BaseReq: baseReq, } req, err := cdc.MarshalJSON(dr) @@ -1214,23 +1182,18 @@ 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) (resultTx ctypes.ResultBroadcastTxCommit) { +func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID uint64, option string, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() - chainID := viper.GetString(client.FlagChainID) + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + vr := voteReq{ - Voter: proposerAddr, - Option: "Yes", - BaseReq: utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: accnum, - Sequence: sequence, - }, + Voter: proposerAddr, + Option: option, + BaseReq: baseReq, } req, err := cdc.MarshalJSON(vr) @@ -1292,6 +1255,18 @@ func getVote(t *testing.T, port string, proposalID uint64, voterAddr sdk.AccAddr return vote } +// GET /gov/proposals/{proposalId}/proposer +func getProposer(t *testing.T, port string, proposalID uint64) gcutils.Proposer { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/proposer", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var proposer gcutils.Proposer + err := cdc.UnmarshalJSON([]byte(body), &proposer) + + require.Nil(t, err) + return proposer +} + // GET /gov/parameters/deposit Query governance deposit parameters func getDepositParam(t *testing.T, port string) gov.DepositParams { res, body := Request(t, port, "GET", "/gov/parameters/deposit", nil) @@ -1343,16 +1318,13 @@ 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) (resultTx ctypes.ResultBroadcastTxCommit) { + valAddr sdk.ValAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { chainID := viper.GetString(client.FlagChainID) + baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false) - ur := unjailReq{utils.BaseReq{ - Name: name, - Password: password, - ChainID: chainID, - AccountNumber: 1, - Sequence: 1, - }} + ur := 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) diff --git a/client/lcd/version.go b/client/lcd/version.go deleted file mode 100644 index 4f4ce611cee7..000000000000 --- a/client/lcd/version.go +++ /dev/null @@ -1,29 +0,0 @@ -package lcd - -import ( - "net/http" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" - "github.com/cosmos/cosmos-sdk/version" -) - -// cli version REST handler endpoint -func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { - v := version.GetVersion() - w.Write([]byte(v)) -} - -// connected node version REST handler endpoint -func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - version, err := cliCtx.Query("/app/version", nil) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(version) - } -} diff --git a/client/rpc/block.go b/client/rpc/block.go index ee77fb5c1680..95f5acb991dd 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -28,7 +28,6 @@ func BlockCommand() *cobra.Command { viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) - cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } diff --git a/client/rpc/root.go b/client/rpc/root.go index c8a98bc447c3..cb25b47f31e8 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -1,48 +1,20 @@ package rpc import ( - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/spf13/cobra" + "fmt" + "net/http" - "github.com/spf13/viper" + "github.com/gorilla/mux" - "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/version" ) -const ( - // one of the following should be provided to verify the connection - flagGenesis = "genesis" - flagCommit = "commit" - flagValHash = "validator-set" -) - -// XXX: remove this when not needed -func todoNotImplemented(_ *cobra.Command, _ []string) error { - return errors.New("todo: Command not yet implemented") -} - -// InitClientCommand initializes client commands -func InitClientCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "init", - Short: "Initialize light client", - RunE: todoNotImplemented, - } - cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to") - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity") - cmd.Flags().String(flagCommit, "", "File with trusted and signed header") - cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") - viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) - viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) - - return cmd -} - // Register REST endpoints func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") + r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(cliCtx)).Methods("GET") @@ -50,3 +22,23 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(cliCtx)).Methods("GET") } + +// cli version REST handler endpoint +func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.GetVersion()))) +} + +// connected node version REST handler endpoint +func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + version, err := cliCtx.Query("/app/version", nil) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(version) + } +} diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 9d401f69c365..0992f8504bc7 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -32,8 +32,6 @@ func ValidatorCommand() *cobra.Command { viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) - cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") - viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) return cmd } diff --git a/client/tx/query.go b/client/tx/query.go index 94570122459e..e169286e37e1 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -46,8 +46,6 @@ func QueryTxCmd(cdc *codec.Codec) *cobra.Command { cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) - cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") - viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) return cmd diff --git a/client/tx/search.go b/client/tx/search.go index 4f104008ad37..29537e00bd11 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "github.com/cosmos/cosmos-sdk/client" @@ -17,11 +18,16 @@ import ( "github.com/spf13/viper" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" ) const ( - flagTags = "tags" - flagAny = "any" + flagTags = "tags" + flagAny = "any" + flagPage = "page" + flagLimit = "limit" + defaultPage = 1 + defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19 ) // default client command to search through tagged transactions @@ -32,7 +38,7 @@ func SearchTxCmd(cdc *codec.Codec) *cobra.Command { Long: strings.TrimSpace(` Search for transactions that match exactly the given tags. For example: -$ gaiacli query txs --tags ':&:' +$ gaiacli query txs --tags ':&:' --page 1 --limit 30 `), RunE: func(cmd *cobra.Command, args []string) error { tagsStr := viper.GetString(flagTags) @@ -53,12 +59,18 @@ $ gaiacli query txs --tags ':&:' } keyValue := strings.Split(tag, ":") - tag = fmt.Sprintf("%s='%s'", keyValue[0], keyValue[1]) + if keyValue[0] == types.TxHeightKey { + tag = fmt.Sprintf("%s=%s", keyValue[0], keyValue[1]) + } else { + tag = fmt.Sprintf("%s='%s'", keyValue[0], keyValue[1]) + } tmTags = append(tmTags, tag) } + page := viper.GetInt(flagPage) + limit := viper.GetInt(flagLimit) cliCtx := context.NewCLIContext().WithCodec(cdc) - txs, err := SearchTxs(cliCtx, cdc, tmTags) + txs, err := SearchTxs(cliCtx, cdc, tmTags, page, limit) if err != nil { return err } @@ -81,22 +93,31 @@ $ gaiacli query txs --tags ':&:' cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) - cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") - viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().String(flagTags, "", "tag:value list of tags that must match") + cmd.Flags().Int32(flagPage, defaultPage, "Query a specific page of paginated results") + cmd.Flags().Int32(flagLimit, defaultLimit, "Query number of transactions results per page returned") + cmd.MarkFlagRequired(flagTags) return cmd } // SearchTxs performs a search for transactions for a given set of tags via // Tendermint RPC. It returns a slice of Info object containing txs and metadata. // An error is returned if the query fails. -func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]Info, error) { +func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) ([]Info, error) { if len(tags) == 0 { return nil, errors.New("must declare at least one tag to search") } + if page <= 0 { + return nil, errors.New("page must greater than 0") + } + + if limit <= 0 { + return nil, errors.New("limit must greater than 0") + } + // XXX: implement ANY query := strings.Join(tags, " AND ") @@ -108,10 +129,7 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]In prove := !cliCtx.TrustNode - // TODO: take these as args - page := 0 - perPage := 100 - res, err := node.TxSearch(query, prove, page, perPage) + res, err := node.TxSearch(query, prove, page, limit) if err != nil { return nil, err } @@ -153,6 +171,7 @@ func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) { func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tags []string + var page, limit int var txs []Info err := r.ParseForm() if err != nil { @@ -164,18 +183,14 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. return } - for key, values := range r.Form { - value, err := url.QueryUnescape(values[0]) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode query value", err.Error())) - return - } + tags, page, limit, err = parseHTTPArgs(r) - tag := fmt.Sprintf("%s='%s'", key, value) - tags = append(tags, tag) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } - txs, err = SearchTxs(cliCtx, cdc, tags) + txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -184,3 +199,51 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent) } } + +func parseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) { + tags = make([]string, 0, len(r.Form)) + for key, values := range r.Form { + if key == "page" || key == "limit" { + continue + } + var value string + value, err = url.QueryUnescape(values[0]) + if err != nil { + return tags, page, limit, err + } + + var tag string + if key == types.TxHeightKey { + tag = fmt.Sprintf("%s=%s", key, value) + } else { + tag = fmt.Sprintf("%s='%s'", key, value) + } + tags = append(tags, tag) + } + + pageStr := r.FormValue("page") + if pageStr == "" { + page = defaultPage + } else { + page, err = strconv.Atoi(pageStr) + if err != nil { + return tags, page, limit, err + } else if page <= 0 { + return tags, page, limit, errors.New("page must greater than 0") + } + } + + limitStr := r.FormValue("limit") + if limitStr == "" { + limit = defaultLimit + } else { + limit, err = strconv.Atoi(limitStr) + if err != nil { + return tags, page, limit, err + } else if limit <= 0 { + return tags, page, limit, errors.New("limit must greater than 0") + } + } + + return tags, page, limit, nil +} diff --git a/client/utils/rest.go b/client/utils/rest.go index cd614cdbfae7..f6bd18a4f583 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -101,30 +101,82 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, txBldr // BaseReq defines a structure that can be embedded in other request structures // that all share common "base" fields. type BaseReq struct { - Name string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber uint64 `json:"account_number"` - Sequence uint64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - GenerateOnly bool `json:"generate_only"` - Simulate bool `json:"simulate"` + Name string `json:"name"` + Password string `json:"password"` + Memo string `json:"memo"` + ChainID string `json:"chain_id"` + AccountNumber uint64 `json:"account_number"` + Sequence uint64 `json:"sequence"` + Fees sdk.Coins `json:"fees"` + 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( + name, password, memo, chainID string, gas, gasAdjustment string, + accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool, +) BaseReq { + + return BaseReq{ + Name: strings.TrimSpace(name), + Password: password, + Memo: strings.TrimSpace(memo), + ChainID: strings.TrimSpace(chainID), + Fees: fees, + GasPrices: gasPrices, + Gas: strings.TrimSpace(gas), + GasAdjustment: strings.TrimSpace(gasAdjustment), + AccountNumber: accNumber, + Sequence: seq, + GenerateOnly: genOnly, + Simulate: simulate, + } } // Sanitize performs basic sanitization on a BaseReq object. func (br BaseReq) Sanitize() BaseReq { - return BaseReq{ - Name: strings.TrimSpace(br.Name), - Password: strings.TrimSpace(br.Password), - ChainID: strings.TrimSpace(br.ChainID), - Gas: strings.TrimSpace(br.Gas), - GasAdjustment: strings.TrimSpace(br.GasAdjustment), - AccountNumber: br.AccountNumber, - Sequence: br.Sequence, - GenerateOnly: br.GenerateOnly, - Simulate: br.Simulate, + return NewBaseReq( + br.Name, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment, + br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate, + ) +} + +// ValidateBasic performs basic validation of a BaseReq. If custom validation +// 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 { + 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 + + case !br.Fees.IsZero() && !br.GasPrices.IsZero(): + // both fees and gas prices were provided + WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices") + return false + + case !br.Fees.IsValid() && !br.GasPrices.IsValid(): + // neither fees or gas prices were provided + WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided") + return false + } } + + if len(br.Name) == 0 { + WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") + return false + } + + return true } /* @@ -149,34 +201,13 @@ func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req i err = cdc.UnmarshalJSON(body, req) if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err)) return err } return nil } -// ValidateBasic performs basic validation of a BaseReq. If custom validation -// logic is needed, the implementing request handler should perform those -// checks manually. -func (br BaseReq) ValidateBasic(w http.ResponseWriter, cliCtx context.CLIContext) bool { - if !cliCtx.GenerateOnly && !cliCtx.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 - } - } - if len(br.Name) == 0 { - WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") - return false - } - return true -} - // CompleteAndBroadcastTxREST implements a utility function that facilitates // sending a series of messages in a signed transaction given a TxBuilder and a // QueryContext. It ensures that the account exists, has a proper number and @@ -185,41 +216,44 @@ func (br BaseReq) ValidateBasic(w http.ResponseWriter, cliCtx context.CLIContext // // NOTE: Also see CompleteAndBroadcastTxCli. // NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn. -func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec) { - simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } +func CompleteAndBroadcastTxREST( + w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, + baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec, +) { - adjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + gasAdjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } - txBldr := authtxb.TxBuilder{ - TxEncoder: GetTxEncoder(cdc), - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: baseReq.ChainID, - AccountNumber: baseReq.AccountNumber, - Sequence: baseReq.Sequence, + simulateAndExecute, gas, err := client.ParseGas(baseReq.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } - if baseReq.Simulate || txBldr.SimulateGas { - newBldr, err := EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) + txBldr := authtxb.NewTxBuilder( + GetTxEncoder(cdc), baseReq.AccountNumber, + baseReq.Sequence, gas, gasAdjustment, baseReq.Simulate, + baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices, + ) + + if baseReq.Simulate || simulateAndExecute { + if gasAdjustment < 0 { + WriteErrorResponse(w, http.StatusBadRequest, "gas adjustment must be a positive float") + return + } + + txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) if err != nil { WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if baseReq.Simulate { - WriteSimulationResponse(w, newBldr.Gas) + WriteSimulationResponse(w, txBldr.GetGas()) return } - - txBldr = newBldr } if baseReq.GenerateOnly { diff --git a/client/utils/utils.go b/client/utils/utils.go index 2d928ddcc947..92ce82b9937f 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -3,10 +3,11 @@ package utils import ( "bytes" "fmt" - "github.com/cosmos/cosmos-sdk/codec" "io" "os" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" @@ -35,12 +36,12 @@ func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIConte return err } - if txBldr.SimulateGas || cliCtx.Simulate { + if txBldr.GetSimulateAndExecute() || cliCtx.Simulate { txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return err } - fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas) + fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas()) } if cliCtx.Simulate { return nil @@ -56,6 +57,7 @@ func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIConte if err != nil { return err } + // broadcast to a Tendermint node _, err = cliCtx.BroadcastTx(txBytes) return err @@ -131,20 +133,40 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, "The generated transaction's intended signer does not match the given signer: %q", name) } - if !offline && txBldr.AccountNumber == 0 { - accNum, err := cliCtx.GetAccountNumber(addr) + if !offline { + txBldr, err = populateAccountFromState( + txBldr, cliCtx, sdk.AccAddress(addr)) if err != nil { return signedStdTx, err } - txBldr = txBldr.WithAccountNumber(accNum) } - if !offline && txBldr.Sequence == 0 { - accSeq, err := cliCtx.GetAccountSequence(addr) + passphrase, err := keys.GetPassphrase(name) + if err != nil { + return signedStdTx, err + } + + return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) +} + +// SignStdTxWithSignerAddress attaches a signature to a StdTx and returns a copy of a it. +// Don't perform online validation or lookups if offline is true, else +// populate account and sequence numbers from a foreign account. +func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, + addr sdk.AccAddress, name string, stdTx auth.StdTx, + offline bool) (signedStdTx auth.StdTx, err error) { + + // check whether the address is a signer + if !isTxSigner(addr, stdTx.GetSigners()) { + return signedStdTx, fmt.Errorf( + "The generated transaction's intended signer does not match the given signer: %q", name) + } + + if !offline { + txBldr, err = populateAccountFromState(txBldr, cliCtx, addr) if err != nil { return signedStdTx, err } - txBldr = txBldr.WithSequence(accSeq) } passphrase, err := keys.GetPassphrase(name) @@ -152,7 +174,28 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, return signedStdTx, err } - return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) + return txBldr.SignStdTx(name, passphrase, stdTx, false) +} + +func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, + addr sdk.AccAddress) (authtxb.TxBuilder, error) { + if txBldr.GetAccountNumber() == 0 { + accNum, err := cliCtx.GetAccountNumber(addr) + if err != nil { + return txBldr, err + } + txBldr = txBldr.WithAccountNumber(accNum) + } + + if txBldr.GetSequence() == 0 { + accSeq, err := cliCtx.GetAccountSequence(addr) + if err != nil { + return txBldr, err + } + txBldr = txBldr.WithSequence(accSeq) + } + + return txBldr, nil } // GetTxEncoder return tx encoder from global sdk configuration if ones is defined. @@ -172,7 +215,7 @@ func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name stri if err != nil { return } - estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment) + estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GetGasAdjustment()) return } @@ -200,7 +243,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // TODO: (ref #1903) Allow for user supplied account number without // automatically doing a manual lookup. - if txBldr.AccountNumber == 0 { + if txBldr.GetAccountNumber() == 0 { accNum, err := cliCtx.GetAccountNumber(from) if err != nil { return txBldr, err @@ -210,7 +253,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // TODO: (ref #1903) Allow for user supplied account sequence without // automatically doing a manual lookup. - if txBldr.Sequence == 0 { + if txBldr.GetSequence() == 0 { accSeq, err := cliCtx.GetAccountSequence(from) if err != nil { return txBldr, err @@ -231,7 +274,7 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg } func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - if txBldr.SimulateGas { + if txBldr.GetSimulateAndExecute() { var name string name, err = cliCtx.GetFromName() if err != nil { @@ -242,7 +285,7 @@ func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIConte if err != nil { return } - fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas) + fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas()) } stdSignMsg, err := txBldr.Build(msgs) if err != nil { diff --git a/client/utils/utils_test.go b/client/utils/utils_test.go index bdd57ded0d16..d5ed6205ebdb 100644 --- a/client/utils/utils_test.go +++ b/client/utils/utils_test.go @@ -3,11 +3,13 @@ package utils import ( "encoding/json" "errors" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/x/auth" + "testing" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" - "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/libs/common" diff --git a/cmd/cosmos-sdk-cli/cmd/init.go b/cmd/cosmos-sdk-cli/cmd/init.go deleted file mode 100644 index f8cf88b88661..000000000000 --- a/cmd/cosmos-sdk-cli/cmd/init.go +++ /dev/null @@ -1,162 +0,0 @@ -package cmd - -import ( - "fmt" - "go/build" - "io/ioutil" - "os" - "strings" - - "path/filepath" - - "github.com/spf13/cobra" - tmversion "github.com/tendermint/tendermint/version" - - "github.com/cosmos/cosmos-sdk/version" -) - -var remoteBasecoinPath = "github.com/cosmos/cosmos-sdk/docs/examples/basecoin" - -// Replacer to replace all instances of basecoin/basecli/BasecoinApp to project specific names -// Gets initialized when initCmd is executing after getting the project name from user -var replacer *strings.Replacer - -// Remote path for the project. -var remoteProjectPath string - -func init() { - initCmd.Flags().StringVarP(&remoteProjectPath, "project-path", "p", "", "Remote project path. eg: github.com/your_user_name/project_name") - rootCmd.AddCommand(initCmd) -} - -var initCmd = &cobra.Command{ - Use: "init [ProjectName]", - Short: "Initialize your new cosmos zone", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - fmt.Print("Thanks for choosing Cosmos-SDK to build your project.\n\n") - projectName := args[0] - capitalizedProjectName := strings.Title(projectName) - shortProjectName := strings.ToLower(projectName) - remoteProjectPath = strings.ToLower(strings.TrimSpace(remoteProjectPath)) - if remoteProjectPath == "" { - remoteProjectPath = strings.ToLower(shortProjectName) - } - replacer = strings.NewReplacer("basecli", shortProjectName+"cli", - "basecoind", shortProjectName+"d", - "BasecoinApp", capitalizedProjectName+"App", - remoteBasecoinPath, remoteProjectPath, - "basecoin", shortProjectName, - "Basecoin", capitalizedProjectName) - return setupBasecoinWorkspace(shortProjectName, remoteProjectPath) - }, -} - -func resolveProjectPath(remoteProjectPath string) string { - gopath := os.Getenv("GOPATH") - if gopath == "" { - gopath = build.Default.GOPATH - // Use $HOME/go - } - return gopath + string(os.PathSeparator) + "src" + string(os.PathSeparator) + remoteProjectPath -} - -// nolint: unparam, errcheck -func copyBasecoinTemplate(projectName string, projectPath string, remoteProjectPath string) { - basecoinProjectPath := resolveProjectPath(remoteBasecoinPath) - filepath.Walk(basecoinProjectPath, func(path string, f os.FileInfo, err error) error { - if !f.IsDir() { - data, err := ioutil.ReadFile(path) - if err != nil { - return err - } - contents := string(data) - // Extract relative file path eg: app/app.go instead of /Users/..../github.com/cosmos/...examples/basecoin/app/app.go - relativeFilePath := path[len(basecoinProjectPath)+1:] - // Evaluating the filepath in the new project folder - projectFilePath := projectPath + string(os.PathSeparator) + relativeFilePath - projectFilePath = replacer.Replace(projectFilePath) - lengthOfRootDir := strings.LastIndex(projectFilePath, string(os.PathSeparator)) - // Extracting the path of root directory from the filepath - rootDir := projectFilePath[0:lengthOfRootDir] - // Creating the required directory first - os.MkdirAll(rootDir, os.ModePerm) - fmt.Println("Creating " + projectFilePath) - // Writing the contents to a file in the project folder - contents = replacer.Replace(contents) - ioutil.WriteFile(projectFilePath, []byte(contents), os.ModePerm) - } - return nil - }) -} - -// nolint: errcheck -func createGopkg(projectPath string) { - // Create gopkg.toml file - dependencies := map[string]string{ - "github.com/cosmos/cosmos-sdk": "=" + version.Version, - "github.com/stretchr/testify": "=1.2.1", - "github.com/spf13/cobra": "=0.0.1", - "github.com/spf13/viper": "=1.0.0", - } - overrides := map[string]string{ - "github.com/golang/protobuf": "1.1.0", - "github.com/tendermint/tendermint": tmversion.Version, - } - contents := "" - for dependency, version := range dependencies { - contents += "[[constraint]]\n\tname = \"" + dependency + "\"\n\tversion = \"" + version + "\"\n\n" - } - for dependency, version := range overrides { - contents += "[[override]]\n\tname = \"" + dependency + "\"\n\tversion = \"=" + version + "\"\n\n" - } - contents += "[prune]\n\tgo-tests = true\n\tunused-packages = true" - ioutil.WriteFile(projectPath+"/Gopkg.toml", []byte(contents), os.ModePerm) -} - -// nolint: errcheck -func createMakefile(projectPath string) { - // Create makefile - // TODO: Should we use tools/ directory as in Cosmos-SDK to get tools for linting etc. - makefileContents := `PACKAGES=$(shell go list ./... | grep -v '/vendor/') - -all: get_tools get_vendor_deps build test - -get_tools: - go get github.com/golang/dep/cmd/dep - -build: - go build -o bin/basecli cmd/basecli/main.go && go build -o bin/basecoind cmd/basecoind/main.go - -get_vendor_deps: - @rm -rf vendor/ - @dep ensure - -test: - @go test $(PACKAGES) - -benchmark: - @go test -bench=. $(PACKAGES) - -.PHONY: all build test benchmark` - - // Replacing instances of base* to project specific names - makefileContents = replacer.Replace(makefileContents) - - ioutil.WriteFile(projectPath+"/Makefile", []byte(makefileContents), os.ModePerm) - -} - -func setupBasecoinWorkspace(projectName string, remoteProjectPath string) error { - projectPath := resolveProjectPath(remoteProjectPath) - fmt.Println("Configuring your project in " + projectPath) - // Check if the projectPath already exists or not - if _, err := os.Stat(projectPath); !os.IsNotExist(err) { - return fmt.Errorf("Unable to initialize the project. %s already exists", projectPath) - } - copyBasecoinTemplate(projectName, projectPath, remoteProjectPath) - createGopkg(projectPath) - createMakefile(projectPath) - fmt.Printf("Initialized a new project at %s.\nHappy hacking!\n", projectPath) - return nil -} diff --git a/cmd/cosmos-sdk-cli/cmd/root.go b/cmd/cosmos-sdk-cli/cmd/root.go deleted file mode 100644 index 2eddd79b5d42..000000000000 --- a/cmd/cosmos-sdk-cli/cmd/root.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "cosmos-sdk-cli", - Short: "Tools to develop on cosmos-sdk", -} - -// Execute the command -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/cosmos-sdk-cli/main.go b/cmd/cosmos-sdk-cli/main.go deleted file mode 100644 index ee5ac0215603..000000000000 --- a/cmd/cosmos-sdk-cli/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/cosmos/cosmos-sdk/cmd/cosmos-sdk-cli/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 4930f44ef5d0..8b5ce4e73380 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -21,7 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) const ( @@ -44,8 +44,8 @@ type GaiaApp struct { // keys to access the substores keyMain *sdk.KVStoreKey keyAccount *sdk.KVStoreKey - keyStake *sdk.KVStoreKey - tkeyStake *sdk.TransientStoreKey + keyStaking *sdk.KVStoreKey + tkeyStaking *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyMint *sdk.KVStoreKey keyDistr *sdk.KVStoreKey @@ -59,7 +59,7 @@ type GaiaApp struct { accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper - stakeKeeper stake.Keeper + stakingKeeper staking.Keeper slashingKeeper slashing.Keeper mintKeeper mint.Keeper distrKeeper distr.Keeper @@ -77,25 +77,28 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b var app = &GaiaApp{ BaseApp: bApp, cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyStake: sdk.NewKVStoreKey("stake"), - tkeyStake: sdk.NewTransientStoreKey("transient_stake"), - keyMint: sdk.NewKVStoreKey("mint"), - keyDistr: sdk.NewKVStoreKey("distr"), - tkeyDistr: sdk.NewTransientStoreKey("transient_distr"), - keySlashing: sdk.NewKVStoreKey("slashing"), - keyGov: sdk.NewKVStoreKey("gov"), - keyFeeCollection: sdk.NewKVStoreKey("fee"), - keyParams: sdk.NewKVStoreKey("params"), - tkeyParams: sdk.NewTransientStoreKey("transient_params"), + keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keyStaking: sdk.NewKVStoreKey(staking.StoreKey), + tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), + keyMint: sdk.NewKVStoreKey(mint.StoreKey), + keyDistr: sdk.NewKVStoreKey(distr.StoreKey), + tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey), + keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), + keyGov: sdk.NewKVStoreKey(gov.StoreKey), + keyFeeCollection: sdk.NewKVStoreKey(auth.FeeStoreKey), + keyParams: sdk.NewKVStoreKey(params.StoreKey), + tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), } + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) + // define the accountKeeper app.accountKeeper = auth.NewAccountKeeper( app.cdc, - app.keyAccount, // target store - auth.ProtoBaseAccount, // prototype + app.keyAccount, + app.paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, ) // add handlers @@ -104,67 +107,64 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b app.cdc, app.keyFeeCollection, ) - app.paramsKeeper = params.NewKeeper( - app.cdc, - app.keyParams, app.tkeyParams, - ) - stakeKeeper := stake.NewKeeper( + stakingKeeper := staking.NewKeeper( app.cdc, - app.keyStake, app.tkeyStake, - app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), - stake.DefaultCodespace, + app.keyStaking, app.tkeyStaking, + app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), + staking.DefaultCodespace, ) app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, app.paramsKeeper.Subspace(mint.DefaultParamspace), - &stakeKeeper, app.feeCollectionKeeper, + &stakingKeeper, app.feeCollectionKeeper, ) app.distrKeeper = distr.NewKeeper( app.cdc, app.keyDistr, app.paramsKeeper.Subspace(distr.DefaultParamspace), - app.bankKeeper, &stakeKeeper, app.feeCollectionKeeper, + app.bankKeeper, &stakingKeeper, app.feeCollectionKeeper, distr.DefaultCodespace, ) app.slashingKeeper = slashing.NewKeeper( app.cdc, app.keySlashing, - &stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), + &stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace, ) app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakeKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakingKeeper, gov.DefaultCodespace, ) // register the staking hooks - // NOTE: The stakeKeeper above is passed by reference, so that it can be + // NOTE: The stakingKeeper above is passed by reference, so that it can be // modified like below: - app.stakeKeeper = *stakeKeeper.SetHooks( + app.stakingKeeper = *stakingKeeper.SetHooks( NewStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()), ) // register message routes app.Router(). - AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("stake", stake.NewHandler(app.stakeKeeper)). - AddRoute("distr", distr.NewHandler(app.distrKeeper)). - AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). - AddRoute("gov", gov.NewHandler(app.govKeeper)) + AddRoute(bank.RouterKey, bank.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("gov", gov.NewQuerier(app.govKeeper)). + AddRoute(distr.QuerierRoute, distr.NewQuerier(app.distrKeeper)). + AddRoute(gov.QuerierRoute, gov.NewQuerier(app.govKeeper)). AddRoute(slashing.QuerierRoute, slashing.NewQuerier(app.slashingKeeper, app.cdc)). - AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc)) + AddRoute(staking.QuerierRoute, staking.NewQuerier(app.stakingKeeper, app.cdc)) // initialize BaseApp - app.MountStores(app.keyMain, app.keyAccount, app.keyStake, app.keyMint, app.keyDistr, + app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) - app.MountStoresTransient(app.tkeyParams, app.tkeyStake, app.tkeyDistr) + app.MountStoresTransient(app.tkeyParams, app.tkeyStaking, app.tkeyDistr) app.SetEndBlocker(app.EndBlocker) if loadLatest { @@ -181,7 +181,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b func MakeCodec() *codec.Codec { var cdc = codec.New() bank.RegisterCodec(cdc) - stake.RegisterCodec(cdc) + staking.RegisterCodec(cdc) distr.RegisterCodec(cdc) slashing.RegisterCodec(cdc) gov.RegisterCodec(cdc) @@ -193,7 +193,6 @@ func MakeCodec() *codec.Codec { // application updates every end block func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - // mint new tokens for the previous block mint.BeginBlocker(ctx, app.mintKeeper) @@ -215,9 +214,8 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // application updates every end block // nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - tags := gov.EndBlocker(ctx, app.govKeeper) - validatorUpdates, endBlockerTags := stake.EndBlocker(ctx, app.stakeKeeper) + validatorUpdates, endBlockerTags := staking.EndBlocker(ctx, app.stakingKeeper) tags = append(tags, endBlockerTags...) app.assertRuntimeInvariants() @@ -234,25 +232,28 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt sort.Slice(genesisState.Accounts, func(i, j int) bool { return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber }) + // load the accounts for _, gacc := range genesisState.Accounts { acc := gacc.ToAccount() - acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx) + acc = app.accountKeeper.NewAccount(ctx, acc) // set account number app.accountKeeper.SetAccount(ctx, acc) } - // load the initial stake information - validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + // initialize distribution (must happen before staking) + distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) + + // load the initial staking information + validators, err := staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData) if err != nil { panic(err) // TODO find a way to do this w/o panics } // initialize module-specific stores - auth.InitGenesis(ctx, app.feeCollectionKeeper, genesisState.AuthData) - slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) + auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData) + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) - distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) // validate genesis state err = GaiaValidateGenesisState(genesisState) @@ -274,7 +275,7 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt } } - validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) } return validators } @@ -337,39 +338,47 @@ func NewStakingHooks(dh distr.Hooks, sh slashing.Hooks) StakingHooks { } // nolint -func (h StakingHooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - h.dh.OnValidatorCreated(ctx, valAddr) - h.sh.OnValidatorCreated(ctx, valAddr) +func (h StakingHooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.AfterValidatorCreated(ctx, valAddr) + h.sh.AfterValidatorCreated(ctx, valAddr) +} +func (h StakingHooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.BeforeValidatorModified(ctx, valAddr) + h.sh.BeforeValidatorModified(ctx, valAddr) +} +func (h StakingHooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorRemoved(ctx, consAddr, valAddr) + h.sh.AfterValidatorRemoved(ctx, consAddr, valAddr) } -func (h StakingHooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { - h.dh.OnValidatorModified(ctx, valAddr) - h.sh.OnValidatorModified(ctx, valAddr) +func (h StakingHooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorBonded(ctx, consAddr, valAddr) + h.sh.AfterValidatorBonded(ctx, consAddr, valAddr) } -func (h StakingHooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.dh.OnValidatorRemoved(ctx, consAddr, valAddr) - h.sh.OnValidatorRemoved(ctx, consAddr, valAddr) +func (h StakingHooks) AfterValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr) + h.sh.AfterValidatorPowerDidChange(ctx, consAddr, valAddr) } -func (h StakingHooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.dh.OnValidatorBonded(ctx, consAddr, valAddr) - h.sh.OnValidatorBonded(ctx, consAddr, valAddr) +func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) + h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) } -func (h StakingHooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.dh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) - h.sh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) +func (h StakingHooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationCreated(ctx, delAddr, valAddr) + h.sh.BeforeDelegationCreated(ctx, delAddr, valAddr) } -func (h StakingHooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.dh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) - h.sh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) +func (h StakingHooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationSharesModified(ctx, delAddr, valAddr) + h.sh.BeforeDelegationSharesModified(ctx, delAddr, valAddr) } -func (h StakingHooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.dh.OnDelegationCreated(ctx, delAddr, valAddr) - h.sh.OnDelegationCreated(ctx, delAddr, valAddr) +func (h StakingHooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationRemoved(ctx, delAddr, valAddr) + h.sh.BeforeDelegationRemoved(ctx, delAddr, valAddr) } -func (h StakingHooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr) - h.sh.OnDelegationSharesModified(ctx, delAddr, valAddr) +func (h StakingHooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.AfterDelegationModified(ctx, delAddr, valAddr) + h.sh.AfterDelegationModified(ctx, delAddr, valAddr) } -func (h StakingHooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.dh.OnDelegationRemoved(ctx, delAddr, valAddr) - h.sh.OnDelegationRemoved(ctx, delAddr, valAddr) +func (h StakingHooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { + h.dh.BeforeValidatorSlashed(ctx, valAddr, fraction) + h.sh.BeforeValidatorSlashed(ctx, valAddr, fraction) } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 87b2ec2a9531..5f2b5b968772 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -14,7 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" abci "github.com/tendermint/tendermint/abci/types" ) @@ -28,7 +28,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := NewGenesisState( genaccs, auth.DefaultGenesisState(), - stake.DefaultGenesisState(), + staking.DefaultGenesisState(), mint.DefaultGenesisState(), distr.DefaultGenesisState(), gov.DefaultGenesisState(), diff --git a/cmd/gaia/app/benchmarks/txsize_test.go b/cmd/gaia/app/benchmarks/txsize_test.go index d9862f22c90e..2726dcd1bbe7 100644 --- a/cmd/gaia/app/benchmarks/txsize_test.go +++ b/cmd/gaia/app/benchmarks/txsize_test.go @@ -22,12 +22,12 @@ func ExampleTxSendSize() { addr1 := sdk.AccAddress(priv1.PubKey().Address()) priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) addr2 := sdk.AccAddress(priv2.PubKey().Address()) - coins := []sdk.Coin{sdk.NewCoin("denom", sdk.NewInt(10))} + coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))} msg1 := bank.MsgSend{ Inputs: []bank.Input{bank.NewInput(addr1, coins)}, Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, } - fee := auth.NewStdFee(gas, coins...) + fee := auth.NewStdFee(gas, coins) signBytes := auth.StdSignBytes("example-chain-ID", 1, 1, fee, []sdk.Msg{msg1}, "") sig, _ := priv1.Sign(signBytes) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 2b51c444be48..f535d1373355 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -2,7 +2,6 @@ package app import ( "encoding/json" - "fmt" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -14,7 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" - stake "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) // export the state of gaia for a genesis file @@ -39,8 +38,8 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( genState := NewGenesisState( accounts, - auth.ExportGenesis(ctx, app.feeCollectionKeeper), - stake.ExportGenesis(ctx, app.stakeKeeper), + auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper), + staking.ExportGenesis(ctx, app.stakingKeeper), mint.ExportGenesis(ctx, app.mintKeeper), distr.ExportGenesis(ctx, app.distrKeeper), gov.ExportGenesis(ctx, app.govKeeper), @@ -50,7 +49,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( if err != nil { return nil, nil, err } - validators = stake.WriteValidators(ctx, app.stakeKeeper) + validators = staking.WriteValidators(ctx, app.stakingKeeper) return appState, validators, nil } @@ -62,83 +61,95 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { /* Handle fee distribution state. */ - // withdraw all delegator & validator rewards - vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { - err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) - if err != nil { - panic(err) - } + // withdraw all validator commission + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) return false - } - app.distrKeeper.IterateValidatorDistInfos(ctx, vdiIter) + }) - ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { - err := app.distrKeeper.WithdrawDelegationReward( - ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) - if err != nil { - panic(err) - } - return false + // withdraw all delegator rewards + dels := app.stakingKeeper.GetAllDelegations(ctx) + for _, delegation := range dels { + _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) } - app.distrKeeper.IterateDelegationDistInfos(ctx, ddiIter) - app.assertRuntimeInvariantsOnContext(ctx) + // clear validator slash events + app.distrKeeper.DeleteAllValidatorSlashEvents(ctx) - // set distribution info withdrawal heights to 0 - app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, delInfo distr.DelegationDistInfo) (stop bool) { - delInfo.DelPoolWithdrawalHeight = 0 - app.distrKeeper.SetDelegationDistInfo(ctx, delInfo) - return false - }) - app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { - valInfo.FeePoolWithdrawalHeight = 0 - app.distrKeeper.SetValidatorDistInfo(ctx, valInfo) + // clear validator historical rewards + app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) return false }) - // assert that the fee pool is empty - feePool := app.distrKeeper.GetFeePool(ctx) - if !feePool.TotalValAccum.Accum.IsZero() { - panic("unexpected leftover validator accum") - } - bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom - if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", - feePool.ValPool.AmountOf(bondDenom).String())) + // reinitialize all delegations + for _, del := range dels { + app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddr, del.ValidatorAddr) } - // reset fee pool height, save fee pool - feePool.TotalValAccum = distr.NewTotalAccum(0) - app.distrKeeper.SetFeePool(ctx, feePool) + // reset context height + ctx = ctx.WithBlockHeight(height) - /* Handle stake state. */ + /* Handle staking state. */ - // iterate through validators by power descending, reset bond height, update bond intra-tx counter - store := ctx.KVStore(app.keyStake) - iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) + // iterate through redelegations, reset creation height + app.stakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + app.stakingKeeper.SetRedelegation(ctx, red) + return false + }) + + // iterate through unbonding delegations, reset creation height + app.stakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + app.stakingKeeper.SetUnbondingDelegation(ctx, ubd) + return false + }) + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keyStaking) + iter := sdk.KVStoreReversePrefixIterator(store, staking.ValidatorsKey) counter := int16(0) + + var valConsAddrs []sdk.ConsAddress for ; iter.Valid(); iter.Next() { - addr := sdk.ValAddress(iter.Value()) - validator, found := app.stakeKeeper.GetValidator(ctx, addr) + addr := sdk.ValAddress(iter.Key()[1:]) + validator, found := app.stakingKeeper.GetValidator(ctx, addr) if !found { panic("expected validator, not found") } + validator.BondHeight = 0 validator.UnbondingHeight = 0 - app.stakeKeeper.SetValidator(ctx, validator) + valConsAddrs = append(valConsAddrs, validator.ConsAddress()) + + app.stakingKeeper.SetValidator(ctx, validator) counter++ } + iter.Close() /* Handle slashing state. */ - // we have to clear the slashing periods, since they reference heights - app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) - // reset start height on signing infos - app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { - info.StartHeight = 0 - app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) - return false - }) + app.slashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }, + ) } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 9c92f60ba51a..cfda1c6c488d 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -19,22 +19,22 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( // bonded tokens given to genesis validators/accounts freeFermionVal = int64(100) freeFermionsAcc = sdk.NewInt(150) - bondDenom = stakeTypes.DefaultBondDenom + bondDenom = stakingTypes.DefaultBondDenom ) // State to Unmarshal type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` AuthData auth.GenesisState `json:"auth"` - StakeData stake.GenesisState `json:"stake"` + StakingData staking.GenesisState `json:"staking"` MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` @@ -43,14 +43,14 @@ type GenesisState struct { } func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, - stakeData stake.GenesisState, mintData mint.GenesisState, + stakingData staking.GenesisState, mintData mint.GenesisState, distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { return GenesisState{ Accounts: accounts, AuthData: authData, - StakeData: stakeData, + StakingData: stakingData, MintData: mintData, DistrData: distrData, GovData: govData, @@ -58,12 +58,19 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, } } -// nolint +// GenesisAccount defines an account initialized at genesis. type GenesisAccount struct { Address sdk.AccAddress `json:"address"` Coins sdk.Coins `json:"coins"` Sequence uint64 `json:"sequence_number"` AccountNumber uint64 `json:"account_number"` + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time"` // vesting start time + EndTime int64 `json:"end_time"` // vesting end time } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { @@ -76,22 +83,58 @@ func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { } func NewGenesisAccountI(acc auth.Account) GenesisAccount { - return GenesisAccount{ + gacc := GenesisAccount{ Address: acc.GetAddress(), Coins: acc.GetCoins(), AccountNumber: acc.GetAccountNumber(), Sequence: acc.GetSequence(), } + + vacc, ok := acc.(auth.VestingAccount) + if ok { + gacc.OriginalVesting = vacc.GetOriginalVesting() + gacc.DelegatedFree = vacc.GetDelegatedFree() + gacc.DelegatedVesting = vacc.GetDelegatedVesting() + gacc.StartTime = vacc.GetStartTime() + gacc.EndTime = vacc.GetEndTime() + } + + return gacc } // convert GenesisAccount to auth.BaseAccount -func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { - return &auth.BaseAccount{ +func (ga *GenesisAccount) ToAccount() auth.Account { + bacc := &auth.BaseAccount{ Address: ga.Address, Coins: ga.Coins.Sort(), AccountNumber: ga.AccountNumber, Sequence: ga.Sequence, } + + if !ga.OriginalVesting.IsZero() { + baseVestingAcc := &auth.BaseVestingAccount{ + BaseAccount: bacc, + OriginalVesting: ga.OriginalVesting, + DelegatedFree: ga.DelegatedFree, + DelegatedVesting: ga.DelegatedVesting, + EndTime: ga.EndTime, + } + + if ga.StartTime != 0 && ga.EndTime != 0 { + return &auth.ContinuousVestingAccount{ + BaseVestingAccount: baseVestingAcc, + StartTime: ga.StartTime, + } + } else if ga.EndTime != 0 { + return &auth.DelayedVestingAccount{ + BaseVestingAccount: baseVestingAcc, + } + } else { + panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga)) + } + } + + return bacc } // Create the core parameters for genesis initialization for gaia @@ -108,34 +151,37 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js return genesisState, errors.New("there must be at least one genesis tx") } - stakeData := genesisState.StakeData + stakingData := genesisState.StakingData for i, genTx := range appGenTxs { var tx auth.StdTx if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { return genesisState, err } + msgs := tx.GetMsgs() if len(msgs) != 1 { return genesisState, errors.New( "must provide genesis StdTx with exactly 1 CreateValidator message") } - if _, ok := msgs[0].(stake.MsgCreateValidator); !ok { + + if _, ok := msgs[0].(staking.MsgCreateValidator); !ok { return genesisState, fmt.Errorf( "Genesis transaction %v does not contain a MsgCreateValidator", i) } } for _, acc := range genesisState.Accounts { - // create the genesis account, give'm few steaks and a buncha token with there name for _, coin := range acc.Coins { if coin.Denom == bondDenom { - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens. - Add(sdk.NewDecFromInt(coin.Amount)) // increase the supply + stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens. + Add(coin.Amount) // increase the supply } } } - genesisState.StakeData = stakeData + + genesisState.StakingData = stakingData genesisState.GenTxs = appGenTxs + return genesisState, nil } @@ -143,7 +189,8 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js func NewDefaultGenesisState() GenesisState { return GenesisState{ Accounts: nil, - StakeData: stake.DefaultGenesisState(), + AuthData: auth.DefaultGenesisState(), + StakingData: staking.DefaultGenesisState(), MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), @@ -157,37 +204,32 @@ func NewDefaultGenesisState() GenesisState { // TODO: Error if there is a duplicate validator (#1708) // TODO: Ensure all state machine parameters are in genesis (#1704) func GaiaValidateGenesisState(genesisState GenesisState) error { - err := validateGenesisStateAccounts(genesisState.Accounts) - if err != nil { + if err := validateGenesisStateAccounts(genesisState.Accounts); err != nil { return err } - // skip stakeData validation as genesis is created from txs + + // skip stakingData validation as genesis is created from txs if len(genesisState.GenTxs) > 0 { return nil } - err = stake.ValidateGenesis(genesisState.StakeData) - if err != nil { + if err := auth.ValidateGenesis(genesisState.AuthData); err != nil { return err } - err = mint.ValidateGenesis(genesisState.MintData) - if err != nil { + if err := staking.ValidateGenesis(genesisState.StakingData); err != nil { return err } - err = distr.ValidateGenesis(genesisState.DistrData) - if err != nil { + if err := mint.ValidateGenesis(genesisState.MintData); err != nil { return err } - err = gov.ValidateGenesis(genesisState.GovData) - if err != nil { + if err := distr.ValidateGenesis(genesisState.DistrData); err != nil { return err } - err = slashing.ValidateGenesis(genesisState.SlashingData) - if err != nil { + if err := gov.ValidateGenesis(genesisState.GovData); err != nil { return err } - return nil + return slashing.ValidateGenesis(genesisState.SlashingData) } // Ensures that there are no duplicate accounts in the genesis state, @@ -276,7 +318,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm "each genesis transaction must provide a single genesis message") } - msg := msgs[0].(stake.MsgCreateValidator) + 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() @@ -296,10 +338,10 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm "account(s) %v not in genesis.json: %+v", strings.Join(accsNotInGenesis, " "), addrMap) } - if delAcc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) { + if delAcc.Coins.AmountOf(msg.Value.Denom).LT(msg.Value.Amount) { return appGenTxs, persistentPeers, fmt.Errorf( "insufficient fund for delegation %v: %v < %v", - delAcc.Address, delAcc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount, + delAcc.Address, delAcc.Coins.AmountOf(msg.Value.Denom), msg.Value.Amount, ) } @@ -318,7 +360,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { accAuth := auth.NewBaseAccountWithAddress(addr) coins := sdk.Coins{ - sdk.NewCoin("fooToken", sdk.NewInt(1000)), + sdk.NewCoin("footoken", sdk.NewInt(1000)), sdk.NewCoin(bondDenom, freeFermionsAcc), } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 425962fa3794..1383794a3b8a 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -3,6 +3,7 @@ package app import ( "encoding/json" "testing" + "time" "github.com/tendermint/tendermint/crypto/secp256k1" tmtypes "github.com/tendermint/tendermint/types" @@ -13,8 +14,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -32,18 +33,18 @@ var ( func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { // start with the default staking genesis state appState := NewDefaultGenesisState() - stakeData := appState.StakeData + stakingData := appState.StakingData genAccs := make([]GenesisAccount, len(genTxs)) for i, genTx := range genTxs { msgs := genTx.GetMsgs() require.Equal(t, 1, len(msgs)) - msg := msgs[0].(stake.MsgCreateValidator) + msg := msgs[0].(staking.MsgCreateValidator) acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr)) acc.Coins = sdk.Coins{sdk.NewInt64Coin(bondDenom, 150)} genAccs[i] = NewGenesisAccount(&acc) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(150)) // increase the supply + stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens.Add(sdk.NewInt(150)) // increase the supply } // create the final app state @@ -55,8 +56,19 @@ func TestToAccount(t *testing.T) { priv := ed25519.GenPrivKey() addr := sdk.AccAddress(priv.PubKey().Address()) authAcc := auth.NewBaseAccountWithAddress(addr) + authAcc.SetCoins(sdk.Coins{sdk.NewInt64Coin(bondDenom, 150)}) genAcc := NewGenesisAccount(&authAcc) - require.Equal(t, authAcc, *genAcc.ToAccount()) + acc := genAcc.ToAccount() + require.IsType(t, &auth.BaseAccount{}, acc) + require.Equal(t, &authAcc, acc.(*auth.BaseAccount)) + + vacc := auth.NewContinuousVestingAccount( + &authAcc, time.Now().Unix(), time.Now().Add(24*time.Hour).Unix(), + ) + genAcc = NewGenesisAccountI(vacc) + acc = genAcc.ToAccount() + require.IsType(t, &auth.ContinuousVestingAccount{}, acc) + require.Equal(t, vacc, acc.(*auth.ContinuousVestingAccount)) } func TestGaiaAppGenTx(t *testing.T) { @@ -91,9 +103,9 @@ func TestGaiaAppGenState(t *testing.T) { } func makeMsg(name string, pk crypto.PubKey) auth.StdTx { - desc := stake.NewDescription(name, "", "", "") - comm := stakeTypes.CommissionMsg{} - msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom, + desc := staking.NewDescription(name, "", "", "") + comm := stakingTypes.CommissionMsg{} + msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom, 50), desc, comm) return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") } @@ -108,18 +120,18 @@ func TestGaiaGenesisValidation(t *testing.T) { require.NotNil(t, err) // Test bonded + jailed validator fails genesisState = makeGenesisState(t, genTxs) - val1 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #2"}) + val1 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #2"}) val1.Jailed = true val1.Status = sdk.Bonded - genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1) + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) err = GaiaValidateGenesisState(genesisState) require.NotNil(t, err) // Test duplicate validator fails val1.Jailed = false genesisState = makeGenesisState(t, genTxs) - val2 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #3"}) - genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1) - genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val2) + val2 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #3"}) + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2) err = GaiaValidateGenesisState(genesisState) require.NotNil(t, err) } @@ -127,6 +139,6 @@ func TestGaiaGenesisValidation(t *testing.T) { func TestNewDefaultGenesisAccount(t *testing.T) { addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address() acc := NewDefaultGenesisAccount(sdk.AccAddress(addr)) - require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("fooToken")) + require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken")) require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom)) } diff --git a/cmd/gaia/app/invariants.go b/cmd/gaia/app/invariants.go index 4582457fcb24..cb53f1f9cfeb 100644 --- a/cmd/gaia/app/invariants.go +++ b/cmd/gaia/app/invariants.go @@ -10,16 +10,16 @@ import ( banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" + stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" ) func (app *GaiaApp) runtimeInvariants() []simulation.Invariant { return []simulation.Invariant{ banksim.NonnegativeBalanceInvariant(app.accountKeeper), - distrsim.ValAccumInvariants(app.distrKeeper, app.stakeKeeper), - stakesim.SupplyInvariants(app.bankKeeper, app.stakeKeeper, + distrsim.NonNegativeOutstandingInvariant(app.distrKeeper), + stakingsim.SupplyInvariants(app.bankKeeper, app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), - stakesim.NonNegativePowerInvariant(app.stakeKeeper), + stakingsim.NonNegativePowerInvariant(app.stakingKeeper), } } diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 67fa05ac5820..b88e809eba6e 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" distr "github.com/cosmos/cosmos-sdk/x/distribution" @@ -28,9 +29,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/slashing" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" - stake "github.com/cosmos/cosmos-sdk/x/stake" - stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -53,7 +54,7 @@ func init() { flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions") } -func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { +func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage { var genesisAccounts []GenesisAccount amount := int64(r.Intn(1e6)) @@ -66,21 +67,63 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { "\t{amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded) - // Randomly generate some genesis accounts - for _, acc := range accs { - coins := sdk.Coins{sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(amount))} - genesisAccounts = append(genesisAccounts, GenesisAccount{ - Address: acc.Address, - Coins: coins, - }) + // randomly generate some genesis accounts + for i, acc := range accs { + coins := sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))} + bacc := auth.NewBaseAccountWithAddress(acc.Address) + bacc.SetCoins(coins) + + var gacc GenesisAccount + + // Only consider making a vesting account once the initial bonded validator + // set is exhausted due to needing to track DelegatedVesting. + if int64(i) > numInitiallyBonded && r.Intn(100) < 50 { + var ( + vacc auth.VestingAccount + endTime int + ) + + startTime := genesisTimestamp.Unix() + + // Allow for some vesting accounts to vest very quickly while others very + // slowly. + if r.Intn(100) < 50 { + endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*24*30))) + } else { + endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*12))) + } + + if r.Intn(100) < 50 { + vacc = auth.NewContinuousVestingAccount(&bacc, startTime, int64(endTime)) + } else { + vacc = auth.NewDelayedVestingAccount(&bacc, int64(endTime)) + } + + gacc = NewGenesisAccountI(vacc) + } else { + gacc = NewGenesisAccount(&bacc) + } + + genesisAccounts = append(genesisAccounts, gacc) + } + + authGenesis := auth.GenesisState{ + Params: auth.Params{ + MemoCostPerByte: uint64(r.Intn(10) + 1), + MaxMemoCharacters: uint64(r.Intn(200-100) + 100), + TxSigLimit: uint64(r.Intn(7) + 1), + SigVerifyCostED25519: uint64(r.Intn(1000-500) + 500), + SigVerifyCostSecp256k1: uint64(r.Intn(1000-500) + 500), + }, } + fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis) // Random genesis states vp := time.Duration(r.Intn(2*172800)) * time.Second govGenesis := gov.GenesisState{ StartingProposalID: uint64(r.Intn(100)), DepositParams: gov.DepositParams{ - MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, int64(r.Intn(1e3)))}, + MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, int64(r.Intn(1e3)))}, MaxDepositPeriod: vp, }, VotingParams: gov.VotingParams{ @@ -94,25 +137,24 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { } fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis) - stakeGenesis := stake.GenesisState{ - Pool: stake.InitialPool(), - Params: stake.Params{ - UnbondingTime: time.Duration(r.Intn(60*60*24*3*2)) * time.Second, + stakingGenesis := staking.GenesisState{ + Pool: staking.InitialPool(), + Params: staking.Params{ + UnbondingTime: time.Duration(randIntBetween(r, 60, 60*60*24*3*2)) * time.Second, MaxValidators: uint16(r.Intn(250)), - BondDenom: stakeTypes.DefaultBondDenom, + BondDenom: stakingTypes.DefaultBondDenom, }, } - fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakeGenesis) + fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis) slashingGenesis := slashing.GenesisState{ Params: slashing.Params{ - MaxEvidenceAge: stakeGenesis.Params.UnbondingTime, - DoubleSignUnbondDuration: time.Duration(r.Intn(60*60*24)) * time.Second, - SignedBlocksWindow: int64(r.Intn(1000)), - DowntimeUnbondDuration: time.Duration(r.Intn(86400)) * time.Second, - MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1), - SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))), - SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))), + MaxEvidenceAge: stakingGenesis.Params.UnbondingTime, + SignedBlocksWindow: int64(randIntBetween(r, 10, 1000)), + MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1), + DowntimeJailDuration: time.Duration(randIntBetween(r, 60, 60*60*24)) * time.Second, + SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))), + SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))), }, } fmt.Printf("Selected randomly generated slashing parameters:\n\t%+v\n", slashingGenesis) @@ -121,7 +163,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { Minter: mint.InitialMinter( sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), Params: mint.NewParams( - stakeTypes.DefaultBondDenom, + stakingTypes.DefaultBondDenom, sdk.NewDecWithPrec(int64(r.Intn(99)), 2), sdk.NewDecWithPrec(20, 2), sdk.NewDecWithPrec(7, 2), @@ -130,30 +172,40 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { } fmt.Printf("Selected randomly generated minting parameters:\n\t%+v\n", mintGenesis) - var validators []stake.Validator - var delegations []stake.Delegation + var validators []staking.Validator + var delegations []staking.Delegation valAddrs := make([]sdk.ValAddress, numInitiallyBonded) for i := 0; i < int(numInitiallyBonded); i++ { valAddr := sdk.ValAddress(accs[i].Address) valAddrs[i] = valAddr - validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) - validator.Tokens = sdk.NewDec(amount) + validator := staking.NewValidator(valAddr, accs[i].PubKey, staking.Description{}) + validator.Tokens = sdk.NewInt(amount) validator.DelegatorShares = sdk.NewDec(amount) - delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount)} + delegation := staking.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount)} validators = append(validators, validator) delegations = append(delegations, delegation) } - stakeGenesis.Pool.LooseTokens = sdk.NewDec((amount * numAccs) + (numInitiallyBonded * amount)) - stakeGenesis.Validators = validators - stakeGenesis.Bonds = delegations + + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt((amount * numAccs) + (numInitiallyBonded * amount)) + stakingGenesis.Validators = validators + stakingGenesis.Bonds = delegations + + distrGenesis := distr.GenesisState{ + FeePool: distr.InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BaseProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BonusProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + } + fmt.Printf("Selected randomly generated distribution parameters:\n\t%+v\n", distrGenesis) genesis := GenesisState{ Accounts: genesisAccounts, - StakeData: stakeGenesis, + AuthData: authGenesis, + StakingData: stakingGenesis, MintData: mintGenesis, - DistrData: distr.DefaultGenesisWithValidators(valAddrs), + DistrData: distrGenesis, SlashingData: slashingGenesis, GovData: govGenesis, } @@ -167,21 +219,24 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { return appState } +func randIntBetween(r *rand.Rand, min, max int) int { + return r.Intn(max-min) + min +} + func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { return []simulation.WeightedOperation{ {5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)}, {100, banksim.SingleInputSendMsg(app.accountKeeper, app.bankKeeper)}, {50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)}, - {50, distrsim.SimulateMsgWithdrawDelegatorRewardsAll(app.accountKeeper, app.distrKeeper)}, {50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)}, - {50, distrsim.SimulateMsgWithdrawValidatorRewardsAll(app.accountKeeper, app.distrKeeper)}, - {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper)}, + {50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakingKeeper)}, {100, govsim.SimulateMsgDeposit(app.govKeeper)}, - {100, stakesim.SimulateMsgCreateValidator(app.accountKeeper, app.stakeKeeper)}, - {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, - {100, stakesim.SimulateMsgDelegate(app.accountKeeper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgBeginUnbonding(app.accountKeeper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakeKeeper)}, + {100, stakingsim.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper)}, + {5, stakingsim.SimulateMsgEditValidator(app.stakingKeeper)}, + {100, stakingsim.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper)}, {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, } } @@ -190,8 +245,8 @@ func invariants(app *GaiaApp) []simulation.Invariant { return []simulation.Invariant{ simulation.PeriodicInvariant(banksim.NonnegativeBalanceInvariant(app.accountKeeper), period, 0), simulation.PeriodicInvariant(govsim.AllInvariants(), period, 0), - simulation.PeriodicInvariant(distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper), period, 0), - simulation.PeriodicInvariant(stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, + simulation.PeriodicInvariant(distrsim.AllInvariants(app.distrKeeper, app.stakingKeeper), period, 0), + simulation.PeriodicInvariant(stakingsim.AllInvariants(app.bankKeeper, app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), period, 0), simulation.PeriodicInvariant(slashingsim.AllInvariants(), period, 0), } @@ -354,7 +409,7 @@ func TestGaiaImportExport(t *testing.T) { storeKeysPrefixes := []StoreKeysPrefixes{ {app.keyMain, newApp.keyMain, [][]byte{}}, {app.keyAccount, newApp.keyAccount, [][]byte{}}, - {app.keyStake, newApp.keyStake, [][]byte{stake.UnbondingQueueKey, stake.RedelegationQueueKey, stake.ValidatorQueueKey}}, // ordering may change but it doesn't matter + {app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey, staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter {app.keySlashing, newApp.keySlashing, [][]byte{}}, {app.keyMint, newApp.keyMint, [][]byte{}}, {app.keyDistr, newApp.keyDistr, [][]byte{}}, @@ -370,8 +425,10 @@ func TestGaiaImportExport(t *testing.T) { storeB := ctxB.KVStore(storeKeyB) kvA, kvB, count, equal := sdk.DiffKVStores(storeA, storeB, prefixes) fmt.Printf("Compared %d key/value pairs between %s and %s\n", count, storeKeyA, storeKeyB) - require.True(t, equal, "unequal stores: %s / %s:\nstore A %s (%X) => %s (%X)\nstore B %s (%X) => %s (%X)", - storeKeyA, storeKeyB, kvA.Key, kvA.Key, kvA.Value, kvA.Value, kvB.Key, kvB.Key, kvB.Value, kvB.Value) + require.True(t, equal, + "unequal stores: %s / %s:\nstore A %X => %X\nstore B %X => %X", + storeKeyA, storeKeyB, kvA.Key, kvA.Value, kvB.Key, kvB.Value, + ) } } diff --git a/cmd/gaia/cli_test/README.md b/cmd/gaia/cli_test/README.md new file mode 100644 index 000000000000..37fd41ce7eff --- /dev/null +++ b/cmd/gaia/cli_test/README.md @@ -0,0 +1,51 @@ +# Gaia CLI Integration tests + +The gaia cli integration tests live in this folder. You can run the full suite by running: + +```bash +$ go test -v -p 4 ./cmd/gaia/cli_test/... +# OR! +$ make test_cli +``` +> NOTE: While the full suite runs in parallel, some of the tests can take up to a minute to complete + +### Test Structure + +This integration suite [uses a thin wrapper](https://godoc.org/github.com/cosmos/cosmos-sdk/tests) over the [`os/exec`](https://golang.org/pkg/os/exec/) package. This allows the integration test to run against built binaries (both `gaiad` and `gaiacli` are used) while being written in golang. This allows tests to take advantage of the various golang code we have for operations like marshal/unmarshal, crypto, etc... + +> NOTE: The tests will use whatever `gaiad` or `gaiacli` binaries are available in your `$PATH`. You can check which binary will be run by the suite by running `which gaiad` or `which gaiacli`. If you have your `$GOPATH` properly setup they should be in `$GOPATH/bin/gaia*`. This will ensure that your test uses the latest binary you have built + +Tests generally follow this structure: + +```go +func TestMyNewCommand(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Your test code goes here... + + f.Cleanup() +} +``` + +This boilerplate above: +- Ensures the tests run in parallel. Because the tests are calling out to `os/exec` for many operations these tests can take a long time to run. +- Creates `.gaiad` and `.gaiacli` folders in a new temp folder. +- Uses `gaiacli` to create 2 accounts for use in testing: `foo` and `bar` +- Creates a genesis file with coins (`1000footoken,1000feetoken,150stake`) controlled by the `foo` key +- Generates an initial bonding transaction (`gentx`) to make the `foo` key a validator at genesis +- Starts `gaiad` and stops it once the test exits +- Cleans up test state on a successful run + +### Notes when adding/running tests + +- Because the tests run against a built binary, you should make sure you build every time the code changes and you want to test again, otherwise you will be testing against an older version. If you are adding new tests this can easily lead to confusing test results. +- The [`test_helpers.go`](./test_helpers.go) file is organized according to the format of `gaiacli` and `gaiad` commands. There are comments with section headers describing the different areas. Helper functions to call CLI functionality are generally named after the command (e.g. `gaiacli query staking validator` would be `QueryStakingValidator`). Try to keep functions grouped by their position in the command tree. +- Test state that is needed by `tx` and `query` commands (`home`, `chain_id`, etc...) is stored on the `Fixtures` object. This makes constructing your new tests almost trivial. +- Sometimes if you exit a test early there can be still running `gaiad` and `gaiacli` processes that will interrupt subsequent runs. Still running `gaiacli` processes will block access to the keybase while still running `gaiad` processes will block ports and prevent new tests from spinning up. You can ensure new tests spin up clean by running `pkill -9 gaiad && pkill -9 gaiacli` before each test run. +- Most `query` and `tx` commands take a variadic `flags` argument. This pattern allows for the creation of a general function which is easily modified by adding flags. See the `TxSend` function and its use for a good example. +- `Tx*` functions follow a general pattern and return `(success bool, stdout string, stderr string)`. This allows for easy testing of multiple different flag configurations. See `TestGaiaCLICreateValidator` or `TestGaiaCLISubmitProposal` for a good example of the pattern. diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index a297356e0e14..2d6864ec99e2 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -1,344 +1,393 @@ package clitest import ( - "encoding/json" + "errors" "fmt" "io/ioutil" "os" "path" "path/filepath" "testing" - - "github.com/cosmos/cosmos-sdk/x/slashing" + "time" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) +func TestGaiaCLIKeysAddMultisig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // key names order does not matter + f.KeysAdd("msig1", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz)) + f.KeysAdd("msig2", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar)) + require.Equal(t, f.KeysShow("msig1").Address, f.KeysShow("msig2").Address) + + f.KeysAdd("msig3", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz), + "--nosort") + f.KeysAdd("msig4", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar), + "--nosort") + require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address) +} + func TestGaiaCLIMinimumFees(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server with minimum fees - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v --minimum_fees=2feeToken", gaiadHome, servAddr, p2pAddr)) - + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + fees := fmt.Sprintf( + "--minimum-gas-prices=%s,%s", + sdk.NewDecCoinFromDec(feeDenom, minGasPrice), + sdk.NewDecCoinFromDec(fee2Denom, minGasPrice), + ) + proc := f.GDStart(fees) defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + barAddr := f.KeyAddress(keyBar) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Send a transaction that will get rejected + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10)) + require.False(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) - success := executeWrite(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + // Ensure tx w/ correct fees pass + txFees := fmt.Sprintf("--fees=%s,%s", sdk.NewInt64Coin(feeDenom, 2), sdk.NewInt64Coin(fee2Denom, 2)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees) + require.True(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure tx w/ improper fees fails + txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 5)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees) + require.False(f.T, success) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIGasPrices(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + + // insufficient gas prices (tx fails) + badGasPrice, _ := sdk.NewDecFromStr("0.000003") + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice))) require.False(t, success) - cleanupDirs(gaiadHome, gaiacliHome) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + // sufficient gas prices (tx passes) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + require.True(t, success) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + f.Cleanup() } func TestGaiaCLIFeesDeduction(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server with minimum fees - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v --minimum_fees=1fooToken", gaiadHome, servAddr, p2pAddr)) - + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + fooAcc := f.QueryAccount(fooAddr) + fooAmt := fooAcc.GetCoins().AmountOf(fooDenom) // test simulation - success := executeWrite(t, fmt.Sprintf( - "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass) + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 1000), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "--dry-run") require.True(t, success) - tests.WaitForNextNBlocksTM(1, port) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + // ensure state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) - // insufficient funds (coins + fees) - success = executeWrite(t, fmt.Sprintf( - "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass) + // insufficient funds (coins + fees) tx fails + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10000000), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))) require.False(t, success) - tests.WaitForNextNBlocksTM(1, port) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + // ensure state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) // test success (transfer = coins + fees) - success = executeWrite(t, fmt.Sprintf( - "gaiacli tx send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 500), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2))) require.True(t, success) - cleanupDirs(gaiadHome, gaiacliHome) + + f.Cleanup() } func TestGaiaCLISend(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr)) + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAcc := f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + // Send some tokens from one account to the other + f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) + tests.WaitForNextNBlocksTM(1, f.Port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) // Test --dry-run - success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo --dry-run", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--dry-run") require.True(t, success) + // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) + tests.WaitForNextNBlocksTM(1, f.Port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(20), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, int64(20), barAcc.GetCoins().AmountOf(denom).Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf(denom).Int64()) // test memo - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo --memo 'testmemo'", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) - - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(30), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - cleanupDirs(gaiadHome, gaiacliHome) + f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--memo='testmemo'") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, int64(30), barAcc.GetCoins().AmountOf(denom).Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf(denom).Int64()) + + f.Cleanup() } func TestGaiaCLIGasAuto(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr)) - + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAcc := f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) // Test failure with auto gas disabled and very little gas set by hand - success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=10 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=10") require.False(t, success) - tests.WaitForNextNBlocksTM(1, port) + // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) // Test failure with negative gas - success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=-100 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=-100") require.False(t, success) + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) + // Test failure with 0 gas - success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=0 --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=0") require.False(t, success) + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) + // Enable auto gas - success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx send %v --json --gas=simulate --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto") + require.NotEmpty(t, stderr) require.True(t, success) - // check that gas wanted == gas used cdc := app.MakeCodec() - jsonOutput := struct { + sendResp := struct { Height int64 TxHash string Response abci.ResponseDeliverTx }{} - require.Nil(t, cdc.UnmarshalJSON([]byte(stdout), &jsonOutput)) - require.Equal(t, jsonOutput.Response.GasWanted, jsonOutput.Response.GasUsed) - tests.WaitForNextNBlocksTM(1, port) + err := cdc.UnmarshalJSON([]byte(stdout), &sendResp) + require.Nil(t, err) + require.Equal(t, sendResp.Response.GasWanted, sendResp.Response.GasUsed) + tests.WaitForNextNBlocksTM(1, f.Port) + // Check state has changed accordingly - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - cleanupDirs(gaiadHome, gaiacliHome) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) + + f.Cleanup() } func TestGaiaCLICreateValidator(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --chain-id=%v --node=%s", gaiacliHome, chainID, servAddr) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr)) - + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) - consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + barVal := sdk.ValAddress(barAddr) - executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10%s --to=%s --from=foo", flags, stakeTypes.DefaultBondDenom, barAddr), app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10)) + tests.WaitForNextNBlocksTM(1, f.Port) - defaultParams := stake.DefaultParams() - initialPool := stake.InitialPool() - initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState + barAcc := f.QueryAccount(barAddr) + require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) + fooAcc := f.QueryAccount(fooAddr) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) - // create validator - cvStr := fmt.Sprintf("gaiacli tx stake create-validator %v", flags) - cvStr += fmt.Sprintf(" --from=%s", "bar") - cvStr += fmt.Sprintf(" --pubkey=%s", consPubKey) - cvStr += fmt.Sprintf(" --amount=%v", fmt.Sprintf("2%s", stakeTypes.DefaultBondDenom)) - cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally") - cvStr += fmt.Sprintf(" --commission-rate=%v", "0.05") - cvStr += fmt.Sprintf(" --commission-max-rate=%v", "0.20") - cvStr += fmt.Sprintf(" --commission-max-change-rate=%v", "0.10") + defaultParams := staking.DefaultParams() + initialPool := staking.InitialPool() + initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewInt(101)) // Delegate tx on GaiaAppGenState - initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) + // Generate a create validator transaction and ensure correctness + success, stdout, stderr := f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only") - // Test --generate-only - success, stdout, stderr := executeWriteRetStdStreams(t, cvStr+" --generate-only", app.DefaultKeyPass) - require.True(t, success) - require.True(t, success) - require.Empty(t, stderr) - msg := unmarshalStdTx(t, stdout) + require.True(f.T, success) + require.Empty(f.T, stderr) + msg := unmarshalStdTx(f.T, stdout) require.NotZero(t, msg.Fee.Gas) require.Equal(t, len(msg.Msgs), 1) require.Equal(t, 0, len(msg.GetSignatures())) // Test --dry-run - success = executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass) + success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2), "--dry-run") require.True(t, success) - executeWrite(t, cvStr, app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + // Create the validator + f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewInt64Coin(denom, 2)) + tests.WaitForNextNBlocksTM(1, f.Port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(8), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64(), "%v", barAcc) + // Ensure funds were deducted properly + barAcc = f.QueryAccount(barAddr) + require.Equal(t, int64(8), barAcc.GetCoins().AmountOf(denom).Int64()) - validator := executeGetValidator(t, fmt.Sprintf("gaiacli query stake validator %s %v", sdk.ValAddress(barAddr), flags)) - require.Equal(t, validator.OperatorAddr, sdk.ValAddress(barAddr)) - require.True(sdk.DecEq(t, sdk.NewDec(2), validator.Tokens)) + // Ensure that validator state is as expected + validator := f.QueryStakingValidator(barVal) + require.Equal(t, validator.OperatorAddr, barVal) + require.True(sdk.IntEq(t, sdk.NewInt(2), validator.Tokens)) - validatorDelegations := executeGetValidatorDelegations(t, fmt.Sprintf("gaiacli query stake delegations-to %s %v", sdk.ValAddress(barAddr), flags)) + // Query delegations to the validator + validatorDelegations := f.QueryStakingDelegationsTo(barVal) require.Len(t, validatorDelegations, 1) require.NotZero(t, validatorDelegations[0].Shares) // unbond a single share - unbondStr := fmt.Sprintf("gaiacli tx stake unbond %v", flags) - unbondStr += fmt.Sprintf(" --from=%s", "bar") - unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr)) - unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") - - success = executeWrite(t, unbondStr, app.DefaultKeyPass) + success = f.TxStakingUnbond(keyBar, "1", barVal) require.True(t, success) - tests.WaitForNextNBlocksTM(1, port) + tests.WaitForNextNBlocksTM(1, f.Port) - /* // this won't be what we expect because we've only started unbonding, haven't completed - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %v %v", barCech, flags)) - require.Equal(t, int64(9), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64(), "%v", barAcc) - */ - validator = executeGetValidator(t, fmt.Sprintf("gaiacli query stake validator %s %v", sdk.ValAddress(barAddr), flags)) - require.Equal(t, "1.0000000000", validator.Tokens.String()) + // Ensure bonded staking is correct + validator = f.QueryStakingValidator(barVal) + require.Equal(t, "1", validator.Tokens.String()) - validatorUbds := executeGetValidatorUnbondingDelegations(t, - fmt.Sprintf("gaiacli query stake unbonding-delegations-from %s %v", sdk.ValAddress(barAddr), flags)) + // Get unbonding delegations from the validator + validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal) require.Len(t, validatorUbds, 1) - require.Equal(t, "1", validatorUbds[0].Balance.Amount.String()) + require.Len(t, validatorUbds[0].Entries, 1) + require.Equal(t, "1", validatorUbds[0].Entries[0].Balance.Amount.String()) - params := executeGetParams(t, fmt.Sprintf("gaiacli query stake parameters %v", flags)) + // Query staking parameters + params := f.QueryStakingParameters() require.True(t, defaultParams.Equal(params)) - pool := executeGetPool(t, fmt.Sprintf("gaiacli query stake pool %v", flags)) + // Query staking pool + pool := f.QueryStakingPool() require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) - cleanupDirs(gaiadHome, gaiacliHome) + + f.Cleanup() } func TestGaiaCLISubmitProposal(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr)) - + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - - executeGetDepositParam(t, fmt.Sprintf("gaiacli query gov param deposit %v", flags)) - executeGetVotingParam(t, fmt.Sprintf("gaiacli query gov param voting %v", flags)) - executeGetTallyingParam(t, fmt.Sprintf("gaiacli query gov param tallying %v", flags)) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) + f.QueryGovParamDeposit() + f.QueryGovParamVoting() + f.QueryGovParamTallying() - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + fooAddr := f.KeyAddress(keyFoo) - proposalsQuery, _ := tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals %v", flags), "") - require.Equal(t, "No matching proposals found", proposalsQuery) + fooAcc := f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom).Int64()) - // submit a test proposal - spStr := fmt.Sprintf("gaiacli tx gov submit-proposal %v", flags) - spStr += fmt.Sprintf(" --from=%s", "foo") - spStr += fmt.Sprintf(" --deposit=%s", fmt.Sprintf("5%s", stakeTypes.DefaultBondDenom)) - spStr += fmt.Sprintf(" --type=%s", "Text") - spStr += fmt.Sprintf(" --title=%s", "Test") - spStr += fmt.Sprintf(" --description=%s", "test") + proposalsQuery := f.QueryGovProposals() + require.Empty(t, proposalsQuery) - // Test generate only - success, stdout, stderr := executeWriteRetStdStreams(t, spStr+" --generate-only", app.DefaultKeyPass) - require.True(t, success) + // Test submit generate only for submit proposal + success, stdout, stderr := f.TxGovSubmitProposal( + keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--generate-only") require.True(t, success) require.Empty(t, stderr) msg := unmarshalStdTx(t, stdout) @@ -347,35 +396,36 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Test --dry-run - success = executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass) + success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5), "--dry-run") require.True(t, success) - executeWrite(t, spStr, app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + // Create the proposal + f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewInt64Coin(denom, 5)) + tests.WaitForNextNBlocksTM(1, f.Port) - txs := executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags='action:submit_proposal&proposer:%s' %v", fooAddr, flags)) + // Ensure transaction tags can be queried + txs := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("proposer:%s", fooAddr)) require.Len(t, txs, 1) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure deposit was deducted + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(denom).Int64()) - proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal 1 %v", flags)) + // Ensure propsal is directly queryable + proposal1 := f.QueryGovProposal(1) require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) - proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals %v", flags), "") - require.Equal(t, " 1 - Test", proposalsQuery) - - deposit := executeGetDeposit(t, - fmt.Sprintf("gaiacli query gov deposit 1 %s %v", fooAddr, flags)) - require.Equal(t, int64(5), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure query proposals returns properly + proposalsQuery = f.QueryGovProposals() + require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) - depositStr := fmt.Sprintf("gaiacli tx gov deposit 1 %s %v", fmt.Sprintf("10%s", stakeTypes.DefaultBondDenom), flags) - depositStr += fmt.Sprintf(" --from=%s", "foo") + // Query the deposits on the proposal + deposit := f.QueryGovDeposit(1, fooAddr) + require.Equal(t, int64(5), deposit.Amount.AmountOf(denom).Int64()) - // Test generate only - success, stdout, stderr = executeWriteRetStdStreams(t, depositStr+" --generate-only", app.DefaultKeyPass) - require.True(t, success) + // Test deposit generate only + success, stdout, stderr = f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10), "--generate-only") require.True(t, success) require.Empty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -383,34 +433,34 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, len(msg.Msgs), 1) require.Equal(t, 0, len(msg.GetSignatures())) - executeWrite(t, depositStr, app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + // Run the deposit transaction + f.TxGovDeposit(1, keyFoo, sdk.NewInt64Coin(denom, 10)) + tests.WaitForNextNBlocksTM(1, f.Port) // test query deposit - deposits := executeGetDeposits(t, fmt.Sprintf("gaiacli query gov deposits 1 %v", flags)) + deposits := f.QueryGovDeposits(1) require.Len(t, deposits, 1) - require.Equal(t, int64(15), deposits[0].Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + require.Equal(t, int64(15), deposits[0].Amount.AmountOf(denom).Int64()) - deposit = executeGetDeposit(t, - fmt.Sprintf("gaiacli query gov deposit 1 %s %v", fooAddr, flags)) - require.Equal(t, int64(15), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure querying the deposit returns the proper amount + deposit = f.QueryGovDeposit(1, fooAddr) + require.Equal(t, int64(15), deposit.Amount.AmountOf(denom).Int64()) - txs = executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags=action:deposit&depositor:%s %v", fooAddr, flags)) + // Ensure tags are set on the transaction + txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr)) require.Len(t, txs, 1) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) + // Ensure account has expected amount of funds + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(denom).Int64()) - require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal 1 %v", flags)) + // Fetch the proposal and ensure it is now in the voting period + proposal1 = f.QueryGovProposal(1) require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) - voteStr := fmt.Sprintf("gaiacli tx gov vote 1 Yes %v", flags) - voteStr += fmt.Sprintf(" --from=%s", "foo") - - // Test generate only - success, stdout, stderr = executeWriteRetStdStreams(t, voteStr+" --generate-only", app.DefaultKeyPass) - require.True(t, success) + // Test vote generate only + success, stdout, stderr = f.TxGovVote(1, gov.OptionYes, keyFoo, "--generate-only") require.True(t, success) require.Empty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -418,70 +468,105 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, len(msg.Msgs), 1) require.Equal(t, 0, len(msg.GetSignatures())) - executeWrite(t, voteStr, app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) + // Vote on the proposal + f.TxGovVote(1, gov.OptionYes, keyFoo) + tests.WaitForNextNBlocksTM(1, f.Port) - vote := executeGetVote(t, fmt.Sprintf("gaiacli query gov vote 1 %s %v", fooAddr, flags)) + // Query the vote + vote := f.QueryGovVote(1, fooAddr) require.Equal(t, uint64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) - votes := executeGetVotes(t, fmt.Sprintf("gaiacli query gov votes 1 %v", flags)) + // Query the votes + votes := f.QueryGovVotes(1) require.Len(t, votes, 1) require.Equal(t, uint64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) - txs = executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags=action:vote&voter:%s %v", fooAddr, flags)) + // Ensure tags are applied to voting transaction properly + txs = f.QueryTxs(1, 50, "action:vote", fmt.Sprintf("voter:%s", fooAddr)) require.Len(t, txs, 1) - proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --status=DepositPeriod %v", flags), "") - require.Equal(t, "No matching proposals found", proposalsQuery) + // Ensure no proposals in deposit period + proposalsQuery = f.QueryGovProposals("--status=DepositPeriod") + require.Empty(t, proposalsQuery) - proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --status=VotingPeriod %v", flags), "") - require.Equal(t, " 1 - Test", proposalsQuery) + // Ensure the proposal returns as in the voting period + proposalsQuery = f.QueryGovProposals("--status=VotingPeriod") + require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) // submit a second test proposal - spStr = fmt.Sprintf("gaiacli tx gov submit-proposal %v", flags) - spStr += fmt.Sprintf(" --from=%s", "foo") - spStr += fmt.Sprintf(" --deposit=%s", fmt.Sprintf("5%s", stakeTypes.DefaultBondDenom)) - spStr += fmt.Sprintf(" --type=%s", "Text") - spStr += fmt.Sprintf(" --title=%s", "Apples") - spStr += fmt.Sprintf(" --description=%s", "test") - - executeWrite(t, spStr, app.DefaultKeyPass) - tests.WaitForNextNBlocksTM(1, port) - - proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --limit=1 %v", flags), "") - require.Equal(t, " 2 - Apples", proposalsQuery) - cleanupDirs(gaiadHome, gaiacliHome) + f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewInt64Coin(denom, 5)) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test limit on proposals query + proposalsQuery = f.QueryGovProposals("--limit=1") + require.Equal(t, uint64(2), proposalsQuery[0].GetProposalID()) + + f.Cleanup() } -func TestGaiaCLIValidateSignatures(t *testing.T) { +func TestGaiaCLIQueryTxPagination(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout( - t, fmt.Sprintf( - "gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr, - ), - ) + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + for i := 1; i <= 30; i++ { + success := executeWrite(t, fmt.Sprintf( + "gaiacli tx send %s --amount=%dfootoken --to=%s --from=foo", + f.Flags(), i, barAddr), app.DefaultKeyPass) + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + } + + // perPage = 15, 2 pages + txsPage1 := f.QueryTxs(1, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1, 15) + txsPage2 := f.QueryTxs(2, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2, 15) + require.NotEqual(t, txsPage1, txsPage2) + txsPage3 := f.QueryTxs(3, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage3, 15) + require.Equal(t, txsPage2, txsPage3) + + // perPage = 16, 2 pages + txsPage1 = f.QueryTxs(1, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1, 16) + txsPage2 = f.QueryTxs(2, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2, 14) + require.NotEqual(t, txsPage1, txsPage2) + + // perPage = 50 + txsPageFull := f.QueryTxs(1, 50, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPageFull, 30) + require.Equal(t, txsPageFull, append(txsPage1, txsPage2...)) + + // perPage = 0 + f.QueryTxsInvalid(errors.New("ERROR: page must greater than 0"), 0, 50, fmt.Sprintf("sender:%s", fooAddr)) + // limit = 0 + f.QueryTxsInvalid(errors.New("ERROR: limit must greater than 0"), 1, 0, fmt.Sprintf("sender:%s", fooAddr)) +} + +func TestGaiaCLIValidateSignatures(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) // generate sendTx with default gas - success, stdout, stderr := executeWriteRetStdStreams( - t, fmt.Sprintf( - "gaiacli tx send %v --amount=10%s --to=%s --from=foo --generate-only", - flags, stakeTypes.DefaultBondDenom, barAddr, - ), - []string{}..., - ) + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") require.True(t, success) require.Empty(t, stderr) @@ -490,12 +575,9 @@ func TestGaiaCLIValidateSignatures(t *testing.T) { defer os.Remove(unsignedTxFile.Name()) // validate we can successfully sign - success, stdout, _ = executeWriteRetStdStreams( - t, fmt.Sprintf("gaiacli tx sign %v --name=foo %v", flags, unsignedTxFile.Name()), - app.DefaultKeyPass, - ) + success, stdout, stderr = f.TxSign(keyFoo, unsignedTxFile.Name()) require.True(t, success) - + require.Empty(t, stderr) stdTx := unmarshalStdTx(t, stdout) require.Equal(t, len(stdTx.Msgs), 1) require.Equal(t, 1, len(stdTx.GetSignatures())) @@ -506,9 +588,7 @@ func TestGaiaCLIValidateSignatures(t *testing.T) { defer os.Remove(signedTxFile.Name()) // validate signatures - success, _, _ = executeWriteRetStdStreams( - t, fmt.Sprintf("gaiacli tx sign %v --validate-signatures %v", flags, signedTxFile.Name()), - ) + success, _, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") require.True(t, success) // modify the transaction @@ -518,31 +598,25 @@ func TestGaiaCLIValidateSignatures(t *testing.T) { defer os.Remove(modSignedTxFile.Name()) // validate signature validation failure due to different transaction sig bytes - success, _, _ = executeWriteRetStdStreams( - t, fmt.Sprintf("gaiacli tx sign %v --validate-signatures %v", flags, modSignedTxFile.Name()), - ) + success, _, _ = f.TxSign(keyFoo, modSignedTxFile.Name(), "--validate-signatures") require.False(t, success) + + f.Cleanup() } func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + f := InitFixtures(t) // start gaiad server - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", gaiadHome, servAddr, p2pAddr)) - + proc := f.GDStart() defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --home=%s", gaiacliHome)) + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) // Test generate sendTx with default gas - success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10%s --to=%s --from=foo --generate-only", - flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") require.True(t, success) require.Empty(t, stderr) msg := unmarshalStdTx(t, stdout) @@ -551,9 +625,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Test generate sendTx with --gas=$amount - success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10%s --to=%s --from=foo --gas=100 --generate-only", - flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) + success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=100", "--generate-only") require.True(t, success) require.Empty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -562,9 +634,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Test generate sendTx, estimate gas - success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx send %v --amount=10%s --to=%s --from=foo --gas=simulate --generate-only", - flags, stakeTypes.DefaultBondDenom, barAddr), []string{}...) + success, stdout, stderr = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), "--gas=auto", "--generate-only") require.True(t, success) require.NotEmpty(t, stderr) msg = unmarshalStdTx(t, stdout) @@ -576,14 +646,12 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { defer os.Remove(unsignedTxFile.Name()) // Test sign --validate-signatures - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --validate-signatures %v", flags, unsignedTxFile.Name())) + 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) // Test sign - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass) + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name()) require.True(t, success) msg = unmarshalStdTx(t, stdout) require.Equal(t, len(msg.Msgs), 1) @@ -594,412 +662,286 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { signedTxFile := writeToNewTempFile(t, stdout) defer os.Remove(signedTxFile.Name()) - // Test sign --print-signatures - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx sign %v --validate-signatures %v", flags, signedTxFile.Name())) + // 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(), fooAddr.String()), stdout) - // Test broadcast - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure foo has right amount of funds + fooAcc := f.QueryAccount(fooAddr) + require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64()) - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) + // Test broadcast + success, stdout, _ = f.TxBroadcast(signedTxFile.Name()) require.True(t, success) var result struct { Response abci.ResponseDeliverTx } + // Unmarshal the response and ensure that gas was properly used require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result)) require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasUsed)) require.Equal(t, msg.Fee.Gas, uint64(result.Response.GasWanted)) - tests.WaitForNextNBlocksTM(1, port) + tests.WaitForNextNBlocksTM(1, f.Port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) - require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) + // Ensure account state + barAcc := f.QueryAccount(barAddr) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, int64(10), barAcc.GetCoins().AmountOf(denom).Int64()) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(denom).Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) - require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - cleanupDirs(gaiadHome, gaiacliHome) + f.Cleanup() } -func TestGaiaCLIConfig(t *testing.T) { +func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) { t.Parallel() - chainID, servAddr, port, gaiadHome, gaiacliHome, _ := initializeFixtures(t) - node := fmt.Sprintf("%s:%s", servAddr, port) - executeWrite(t, fmt.Sprintf(`gaiacli --home=%s config node %s`, gaiacliHome, node)) - executeWrite(t, fmt.Sprintf(`gaiacli --home=%s config output text`, gaiacliHome)) - executeWrite(t, fmt.Sprintf(`gaiacli --home=%s config trust_node true`, gaiacliHome)) - executeWrite(t, fmt.Sprintf(`gaiacli --home=%s config chain_id %s`, gaiacliHome, chainID)) - executeWrite(t, fmt.Sprintf(`gaiacli --home=%s config trace false`, gaiacliHome)) - config, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) - require.NoError(t, err) - expectedConfig := fmt.Sprintf(`chain_id = "%s" -node = "%s" -output = "text" -trace = false -trust_node = true -`, chainID, node) - require.Equal(t, expectedConfig, string(config)) - cleanupDirs(gaiadHome, gaiacliHome) -} + f := InitFixtures(t) -func TestGaiadCollectGentxs(t *testing.T) { - t.Parallel() - // Initialise temporary directories - gaiadHome, gaiacliHome := getTestingHomeDirs(t.Name()) - gentxDir, err := ioutil.TempDir("", "") - gentxDoc := filepath.Join(gentxDir, "gentx.json") - require.NoError(t, err) + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) - tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "") - os.RemoveAll(filepath.Join(gaiadHome, "config", "gentx")) - executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass) - executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s foo", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli config --home=%s output json", gaiacliHome)) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) - // Run init - _ = executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) - // Add account to genesis.json - executeWriteCheckErr(t, fmt.Sprintf( - "gaiad add-genesis-account %s 150%s,1000fooToken --home=%s", fooAddr, stakeTypes.DefaultBondDenom, gaiadHome)) - executeWrite(t, fmt.Sprintf("cat %s%sconfig%sgenesis.json", gaiadHome, string(os.PathSeparator), string(os.PathSeparator))) - // Write gentx file - executeWriteCheckErr(t, fmt.Sprintf( - "gaiad gentx --name=foo --home=%s --home-client=%s --output-document=%s", gaiadHome, gaiacliHome, gentxDoc), app.DefaultKeyPass) - // Collect gentxs from a custom directory - executeWriteCheckErr(t, fmt.Sprintf("gaiad collect-gentxs --home=%s --gentx-dir=%s", gaiadHome, gentxDir), app.DefaultKeyPass) - cleanupDirs(gaiadHome, gaiacliHome, gentxDir) -} + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) -// --------------------------------------------------------------------------- -// Slashing + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(keyFooBarBaz, barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) -func TestSlashingGetParams(t *testing.T) { - t.Parallel() + // Write the output to disk + unsignedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) - cdc := app.MakeCodec() - chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr := initializeFixtures(t) - flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) - // start gaiad server - proc := tests.GoExecuteTWithStdout( - t, - fmt.Sprintf( - "gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", - gaiadHome, servAddr, p2pAddr, - ), - ) + // Write the output to disk + fooSignatureFile := writeToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) - defer proc.Stop(false) - tests.WaitForTMStart(port) - tests.WaitForNextNBlocksTM(1, port) + // Multisign, not enough signatures + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{fooSignatureFile.Name()}) + require.True(t, success) - res, errStr := tests.ExecuteT(t, fmt.Sprintf("gaiacli query slashing params %s", flags), "") - require.Empty(t, errStr) + // Write the output to disk + signedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) - var params slashing.Params - err := cdc.UnmarshalJSON([]byte(res), ¶ms) - require.NoError(t, err) + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.False(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.False(t, success) } -//___________________________________________________________________________________ -// helper methods +func TestGaiaCLIMultisignSortSignatures(t *testing.T) { + t.Parallel() + f := InitFixtures(t) -func getTestingHomeDirs(name string) (string, string) { - tmpDir := os.TempDir() - gaiadHome := fmt.Sprintf("%s%s%s%s.test_gaiad", tmpDir, string(os.PathSeparator), name, string(os.PathSeparator)) - gaiacliHome := fmt.Sprintf("%s%s%s%s.test_gaiacli", tmpDir, string(os.PathSeparator), name, string(os.PathSeparator)) - return gaiadHome, gaiacliHome -} + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) -func initializeFixtures(t *testing.T) (chainID, servAddr, port, gaiadHome, gaiacliHome, p2pAddr string) { - gaiadHome, gaiacliHome = getTestingHomeDirs(t.Name()) - tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "") - os.RemoveAll(filepath.Join(gaiadHome, "config", "gentx")) - executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass) - executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s foo", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiacli config --home=%s output json", gaiacliHome)) - fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) - chainID = executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) - - executeWriteCheckErr(t, fmt.Sprintf( - "gaiad add-genesis-account %s 150%s,1000fooToken --home=%s", fooAddr, stakeTypes.DefaultBondDenom, gaiadHome)) - executeWrite(t, fmt.Sprintf("cat %s%sconfig%sgenesis.json", gaiadHome, string(os.PathSeparator), string(os.PathSeparator))) - executeWriteCheckErr(t, fmt.Sprintf( - "gaiad gentx --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome), app.DefaultKeyPass) - executeWriteCheckErr(t, fmt.Sprintf("gaiad collect-gentxs --home=%s", gaiadHome), app.DefaultKeyPass) - // get a free port, also setup some common flags - servAddr, port, err := server.FreeTCPAddr() - require.NoError(t, err) - p2pAddr, _, err = server.FreeTCPAddr() - require.NoError(t, err) - return -} + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) -func marshalStdTx(t *testing.T, stdTx auth.StdTx) []byte { - cdc := app.MakeCodec() - bz, err := cdc.MarshalBinaryBare(stdTx) - require.NoError(t, err) - return bz -} + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) -func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { - cdc := app.MakeCodec() - require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) - return -} + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) -func writeToNewTempFile(t *testing.T, s string) *os.File { - fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_") - require.Nil(t, err) - _, err = fp.WriteString(s) - require.Nil(t, err) - return fp -} + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(keyFooBarBaz, barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) -func readGenesisFile(t *testing.T, genFile string) types.GenesisDoc { - var genDoc types.GenesisDoc - fp, err := os.Open(genFile) - require.NoError(t, err) - fileContents, err := ioutil.ReadAll(fp) - require.NoError(t, err) - defer fp.Close() - err = codec.Cdc.UnmarshalJSON(fileContents, &genDoc) - require.NoError(t, err) - return genDoc -} + // Write the output to disk + unsignedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) -//___________________________________________________________________________________ -// executors + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) -func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { - require.True(t, executeWrite(t, cmdStr, writes...)) -} + // Write the output to disk + fooSignatureFile := writeToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) -func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { - exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) - return -} + // Sign with baz's key + success, stdout, _ = f.TxSign(keyBaz, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) -func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { - proc := tests.GoExecuteT(t, cmdStr) + // Write the output to disk + bazSignatureFile := writeToNewTempFile(t, stdout) + defer os.Remove(bazSignatureFile.Name()) - for _, write := range writes { - _, err := proc.StdinPipe.Write([]byte(write + "\n")) - require.NoError(t, err) - } - stdout, stderr, err := proc.ReadAll() - if err != nil { - fmt.Println("Err on proc.ReadAll()", err, cmdStr) - } - // Log output. - if len(stdout) > 0 { - t.Log("Stdout:", cmn.Green(string(stdout))) - } - if len(stderr) > 0 { - t.Log("Stderr:", cmn.Red(string(stderr))) - } + // Multisign, keys in different order + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + bazSignatureFile.Name(), fooSignatureFile.Name()}) + require.True(t, success) - proc.Wait() - return proc.ExitState.Success(), string(stdout), string(stderr) + // Write the output to disk + signedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) } -func executeInit(t *testing.T, cmdStr string) (chainID string) { - _, stderr := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass) +func TestGaiaCLIMultisign(t *testing.T) { + t.Parallel() + f := InitFixtures(t) - var initRes map[string]json.RawMessage - err := json.Unmarshal([]byte(stderr), &initRes) - require.NoError(t, err) + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) - err = json.Unmarshal(initRes["chain_id"], &chainID) - require.NoError(t, err) + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + bazAddr := f.KeyAddress(keyBaz) - return -} + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10)) + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) -func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) { - out, _ := tests.ExecuteT(t, cmdStr, "") - var ko keys.KeyOutput - keys.UnmarshalJSON([]byte(out), &ko) + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) - pk, err := sdk.GetAccPubKeyBech32(ko.PubKey) - require.NoError(t, err) + // Test generate sendTx with multisig + success, stdout, stderr := f.TxSend(keyFooBarBaz, bazAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) - accAddr, err := sdk.AccAddressFromBech32(ko.Address) - require.NoError(t, err) + // Write the output to disk + unsignedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) - return accAddr, pk -} + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) -func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { - out, _ := tests.ExecuteT(t, cmdStr, "") - var initRes map[string]json.RawMessage - err := json.Unmarshal([]byte(out), &initRes) - require.NoError(t, err, "out %v, err %v", out, err) - value := initRes["value"] - var acc auth.BaseAccount - cdc := codec.New() - codec.RegisterCrypto(cdc) - err = cdc.UnmarshalJSON(value, &acc) - require.NoError(t, err, "value %v, err %v", string(value), err) - return acc -} + // Write the output to disk + fooSignatureFile := writeToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) -//___________________________________________________________________________________ -// txs + // Sign with bar's key + success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) -func executeGetTxs(t *testing.T, cmdStr string) []tx.Info { - out, _ := tests.ExecuteT(t, cmdStr, "") - var txs []tx.Info - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &txs) - require.NoError(t, err, "out %v\n, err %v", out, err) - return txs -} + // Write the output to disk + barSignatureFile := writeToNewTempFile(t, stdout) + defer os.Remove(barSignatureFile.Name()) -//___________________________________________________________________________________ -// stake + // Multisign + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + fooSignatureFile.Name(), barSignatureFile.Name()}) + require.True(t, success) -func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { - out, _ := tests.ExecuteT(t, cmdStr, "") - var validator stake.Validator - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &validator) - require.NoError(t, err, "out %v\n, err %v", out, err) - return validator -} + // Write the output to disk + signedTxFile := writeToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) -func executeGetValidatorUnbondingDelegations(t *testing.T, cmdStr string) []stake.UnbondingDelegation { - out, _ := tests.ExecuteT(t, cmdStr, "") - var ubds []stake.UnbondingDelegation - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &ubds) - require.NoError(t, err, "out %v\n, err %v", out, err) - return ubds -} + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) -func executeGetValidatorRedelegations(t *testing.T, cmdStr string) []stake.Redelegation { - out, _ := tests.ExecuteT(t, cmdStr, "") - var reds []stake.Redelegation - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &reds) - require.NoError(t, err, "out %v\n, err %v", out, err) - return reds + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) } -func executeGetValidatorDelegations(t *testing.T, cmdStr string) []stake.Delegation { - out, _ := tests.ExecuteT(t, cmdStr, "") - var delegations []stake.Delegation - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &delegations) - require.NoError(t, err, "out %v\n, err %v", out, err) - return delegations -} +func TestGaiaCLIConfig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + node := fmt.Sprintf("%s:%s", f.RPCAddr, f.Port) + + // Set available configuration options + f.CLIConfig("node", node) + f.CLIConfig("output", "text") + f.CLIConfig("trust-node", "true") + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("trace", "false") + f.CLIConfig("indent", "true") + + config, err := ioutil.ReadFile(path.Join(f.GCLIHome, "config", "config.toml")) + require.NoError(t, err) + expectedConfig := fmt.Sprintf(`chain-id = "%s" +indent = true +node = "%s" +output = "text" +trace = false +trust-node = true +`, f.ChainID, node) + require.Equal(t, expectedConfig, string(config)) -func executeGetPool(t *testing.T, cmdStr string) stake.Pool { - out, _ := tests.ExecuteT(t, cmdStr, "") - var pool stake.Pool - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &pool) - require.NoError(t, err, "out %v\n, err %v", out, err) - return pool + f.Cleanup() } -func executeGetParams(t *testing.T, cmdStr string) stake.Params { - out, _ := tests.ExecuteT(t, cmdStr, "") - var params stake.Params - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), ¶ms) - require.NoError(t, err, "out %v\n, err %v", out, err) - return params -} +func TestGaiadCollectGentxs(t *testing.T) { + t.Parallel() + f := NewFixtures(t) -//___________________________________________________________________________________ -// gov + // Initialise temporary directories + gentxDir, err := ioutil.TempDir("", "") + gentxDoc := filepath.Join(gentxDir, "gentx.json") + require.NoError(t, err) -func executeGetDepositParam(t *testing.T, cmdStr string) gov.DepositParams { - out, _ := tests.ExecuteT(t, cmdStr, "") - var depositParam gov.DepositParams - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &depositParam) - require.NoError(t, err, "out %v\n, err %v", out, err) - return depositParam -} + // Reset testing path + f.UnsafeResetAll() -func executeGetVotingParam(t *testing.T, cmdStr string) gov.VotingParams { - out, _ := tests.ExecuteT(t, cmdStr, "") - var votingParam gov.VotingParams - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &votingParam) - require.NoError(t, err, "out %v\n, err %v", out, err) - return votingParam -} + // Initialize keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) -func executeGetTallyingParam(t *testing.T, cmdStr string) gov.TallyParams { - out, _ := tests.ExecuteT(t, cmdStr, "") - var tallyingParam gov.TallyParams - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &tallyingParam) - require.NoError(t, err, "out %v\n, err %v", out, err) - return tallyingParam -} + // Configure json output + f.CLIConfig("output", "json") -func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal { - out, _ := tests.ExecuteT(t, cmdStr, "") - var proposal gov.Proposal - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &proposal) - require.NoError(t, err, "out %v\n, err %v", out, err) - return proposal -} + // Run init + f.GDInit(keyFoo) -func executeGetVote(t *testing.T, cmdStr string) gov.Vote { - out, _ := tests.ExecuteT(t, cmdStr, "") - var vote gov.Vote - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &vote) - require.NoError(t, err, "out %v\n, err %v", out, err) - return vote -} + // Add account to genesis.json + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) -func executeGetVotes(t *testing.T, cmdStr string) []gov.Vote { - out, _ := tests.ExecuteT(t, cmdStr, "") - var votes []gov.Vote - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &votes) - require.NoError(t, err, "out %v\n, err %v", out, err) - return votes -} + // Write gentx file + f.GenTx(keyFoo, fmt.Sprintf("--output-document=%s", gentxDoc)) -func executeGetDeposit(t *testing.T, cmdStr string) gov.Deposit { - out, _ := tests.ExecuteT(t, cmdStr, "") - var deposit gov.Deposit - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &deposit) - require.NoError(t, err, "out %v\n, err %v", out, err) - return deposit -} + // Collect gentxs from a custom directory + f.CollectGenTxs(fmt.Sprintf("--gentx-dir=%s", gentxDir)) -func executeGetDeposits(t *testing.T, cmdStr string) []gov.Deposit { - out, _ := tests.ExecuteT(t, cmdStr, "") - var deposits []gov.Deposit - cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &deposits) - require.NoError(t, err, "out %v\n, err %v", out, err) - return deposits + f.Cleanup(gentxDir) } -func cleanupDirs(dirs ...string) { - for _, d := range dirs { - os.RemoveAll(d) - } +func TestSlashingGetParams(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + params := f.QuerySlashingParams() + require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge) + require.Equal(t, int64(100), params.SignedBlocksWindow) + require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow) } diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go new file mode 100644 index 000000000000..7571985062a1 --- /dev/null +++ b/cmd/gaia/cli_test/test_helpers.go @@ -0,0 +1,591 @@ +package clitest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const ( + denom = "stake" + keyFoo = "foo" + keyBar = "bar" + fooDenom = "footoken" + feeDenom = "feetoken" + fee2Denom = "fee2token" + keyBaz = "baz" + keyFooBarBaz = "foobarbaz" +) + +var startCoins = sdk.Coins{ + sdk.NewInt64Coin(feeDenom, 1000000), + sdk.NewInt64Coin(fee2Denom, 1000000), + sdk.NewInt64Coin(fooDenom, 1000), + sdk.NewInt64Coin(denom, 150), +} + +//___________________________________________________________________________________ +// Fixtures + +// Fixtures is used to setup the testing environment +type Fixtures struct { + ChainID string + RPCAddr string + Port string + GDHome string + GCLIHome string + P2PAddr string + T *testing.T +} + +// NewFixtures creates a new instance of Fixtures with many vars set +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"), + GCLIHome: filepath.Join(tmpDir, ".gaiacli"), + RPCAddr: servAddr, + P2PAddr: p2pAddr, + Port: port, + } +} + +// InitFixtures is called at the beginning of a test +// and initializes a chain with 1 validator +func InitFixtures(t *testing.T) (f *Fixtures) { + f = NewFixtures(t) + + // Reset test state + f.UnsafeResetAll() + + // Ensure keystore has foo and bar keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysDelete(keyBar) + f.KeysDelete(keyFooBarBaz) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) + f.KeysAdd(keyBaz) + f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( + "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) + + // Ensure that CLI output is in JSON format + f.CLIConfig("output", "json") + + // NOTE: GDInit sets the ChainID + f.GDInit(keyFoo) + f.CLIConfig("chain-id", f.ChainID) + + // Start an account with tokens + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + f.GenTx(keyFoo) + f.CollectGenTxs() + return +} + +// Cleanup is meant to be run at the end of a test to clean up an remaining test state +func (f *Fixtures) Cleanup(dirs ...string) { + clean := append(dirs, f.GDHome, f.GCLIHome) + for _, d := range clean { + err := os.RemoveAll(d) + require.NoError(f.T, err) + } +} + +// Flags returns the flags necessary for making most CLI calls +func (f *Fixtures) Flags() string { + return fmt.Sprintf("--home=%s --node=%s", f.GCLIHome, f.RPCAddr) +} + +//___________________________________________________________________________________ +// gaiad + +// UnsafeResetAll is gaiad unsafe-reset-all +func (f *Fixtures) UnsafeResetAll(flags ...string) { + cmd := fmt.Sprintf("gaiad --home=%s unsafe-reset-all", f.GDHome) + executeWrite(f.T, addFlags(cmd, flags)) + err := os.RemoveAll(filepath.Join(f.GDHome, "config", "gentx")) + require.NoError(f.T, err) +} + +// GDInit is gaiad init +// NOTE: GDInit sets the ChainID for the Fixtures instance +func (f *Fixtures) GDInit(moniker string, flags ...string) { + cmd := fmt.Sprintf("gaiad init -o --moniker=%s --home=%s", moniker, f.GDHome) + _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass) + + var chainID string + var initRes map[string]json.RawMessage + + err := json.Unmarshal([]byte(stderr), &initRes) + require.NoError(f.T, err) + + err = json.Unmarshal(initRes["chain_id"], &chainID) + require.NoError(f.T, err) + + f.ChainID = chainID +} + +// AddGenesisAccount is gaiad add-genesis-account +func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, flags ...string) { + cmd := fmt.Sprintf("gaiad add-genesis-account %s %s --home=%s", address, coins, f.GDHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +// GenTx is gaiad gentx +func (f *Fixtures) GenTx(name string, flags ...string) { + cmd := fmt.Sprintf("gaiad gentx --name=%s --home=%s --home-client=%s", name, f.GDHome, f.GCLIHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// CollectGenTxs is gaiad collect-gentxs +func (f *Fixtures) CollectGenTxs(flags ...string) { + cmd := fmt.Sprintf("gaiad collect-gentxs --home=%s", f.GDHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// GDStart runs gaiad start with the appropriate flags and returns a process +func (f *Fixtures) GDStart(flags ...string) *tests.Process { + cmd := fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", f.GDHome, f.RPCAddr, f.P2PAddr) + proc := tests.GoExecuteTWithStdout(f.T, addFlags(cmd, flags)) + tests.WaitForTMStart(f.Port) + tests.WaitForNextNBlocksTM(1, f.Port) + return proc +} + +//___________________________________________________________________________________ +// gaiacli keys + +// KeysDelete is gaiacli keys delete +func (f *Fixtures) KeysDelete(name string, flags ...string) { + cmd := fmt.Sprintf("gaiacli keys delete --home=%s %s", f.GCLIHome, name) + executeWrite(f.T, addFlags(cmd, append(append(flags, "-y"), "-f"))) +} + +// KeysAdd is gaiacli keys add +func (f *Fixtures) KeysAdd(name string, flags ...string) { + cmd := fmt.Sprintf("gaiacli keys add --home=%s %s", f.GCLIHome, name) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// KeysShow is gaiacli keys show +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) + require.NoError(f.T, err) + return ko +} + +// KeyAddress returns the SDK account address from the key +func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { + ko := f.KeysShow(name) + accAddr, err := sdk.AccAddressFromBech32(ko.Address) + require.NoError(f.T, err) + return accAddr +} + +//___________________________________________________________________________________ +// gaiacli config + +// CLIConfig is gaiacli config +func (f *Fixtures) CLIConfig(key, value string, flags ...string) { + cmd := fmt.Sprintf("gaiacli config --home=%s %s %s", f.GCLIHome, key, value) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +//___________________________________________________________________________________ +// gaiacli tx send/sign/broadcast + +// TxSend is gaiacli tx send +func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx send %v --amount=%s --to=%s --from=%s", f.Flags(), amount, to, from) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), 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) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxBroadcast is gaiacli tx sign +func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx broadcast %v %v", f.Flags(), fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxMultisign is gaiacli tx multisign +func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string, + flags ...string) (bool, string, string) { + + cmd := fmt.Sprintf("gaiacli tx multisign %v %s %s %s", f.Flags(), + fileName, name, strings.Join(signaturesFiles, " "), + ) + return executeWriteRetStdStreams(f.T, cmd) +} + +//___________________________________________________________________________________ +// gaiacli tx staking + +// TxStakingCreateValidator is gaiacli tx staking create-validator +func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey) + cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05") + cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10") + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxStakingUnbond is gaiacli tx staking unbond +func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { + cmd := fmt.Sprintf("gaiacli tx staking unbond %v --from=%s --validator=%s --shares-amount=%v", f.Flags(), from, validator, shares) + return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli tx gov + +// TxGovSubmitProposal is gaiacli tx gov submit-proposal +func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx gov submit-proposal %v --from=%s --type=%s", f.Flags(), from, typ) + cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxGovDeposit is gaiacli tx gov deposit +func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx gov deposit %d %s --from=%s %v", proposalID, amount, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxGovVote is gaiacli tx gov vote +func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("gaiacli tx gov vote %d %s --from=%s %v", proposalID, option, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli query account + +// QueryAccount is gaiacli query account +func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.BaseAccount { + cmd := fmt.Sprintf("gaiacli query account %s %v", address, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var initRes map[string]json.RawMessage + err := json.Unmarshal([]byte(out), &initRes) + require.NoError(f.T, err, "out %v, err %v", out, err) + value := initRes["value"] + var acc auth.BaseAccount + cdc := codec.New() + codec.RegisterCrypto(cdc) + err = cdc.UnmarshalJSON(value, &acc) + require.NoError(f.T, err, "value %v, err %v", string(value), err) + return acc +} + +//___________________________________________________________________________________ +// gaiacli query txs + +// QueryTxs is gaiacli query txs +func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []tx.Info { + cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var txs []tx.Info + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &txs) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return txs +} + +// QueryTxsInvalid query txs with wrong parameters and compare expected error +func (f *Fixtures) QueryTxsInvalid(expectedErr error, page, limit int, tags ...string) { + cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) + _, err := tests.ExecuteT(f.T, cmd, "") + require.EqualError(f.T, expectedErr, err) +} + +//___________________________________________________________________________________ +// gaiacli query staking + +// QueryStakingValidator is gaiacli query staking validator +func (f *Fixtures) QueryStakingValidator(valAddr sdk.ValAddress, flags ...string) staking.Validator { + cmd := fmt.Sprintf("gaiacli query staking validator %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var validator staking.Validator + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &validator) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return validator +} + +// QueryStakingUnbondingDelegationsFrom is gaiacli query staking unbonding-delegations-from +func (f *Fixtures) QueryStakingUnbondingDelegationsFrom(valAddr sdk.ValAddress, flags ...string) []staking.UnbondingDelegation { + cmd := fmt.Sprintf("gaiacli query staking unbonding-delegations-from %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ubds []staking.UnbondingDelegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &ubds) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return ubds +} + +// QueryStakingDelegationsTo is gaiacli query staking delegations-to +func (f *Fixtures) QueryStakingDelegationsTo(valAddr sdk.ValAddress, flags ...string) []staking.Delegation { + cmd := fmt.Sprintf("gaiacli query staking delegations-to %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var delegations []staking.Delegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &delegations) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return delegations +} + +// QueryStakingPool is gaiacli query staking pool +func (f *Fixtures) QueryStakingPool(flags ...string) staking.Pool { + cmd := fmt.Sprintf("gaiacli query staking pool %v", f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var pool staking.Pool + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &pool) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return pool +} + +// QueryStakingParameters is gaiacli query staking parameters +func (f *Fixtures) QueryStakingParameters(flags ...string) staking.Params { + cmd := fmt.Sprintf("gaiacli query staking params %v", f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var params staking.Params + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), ¶ms) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return params +} + +//___________________________________________________________________________________ +// gaiacli query gov + +// QueryGovParamDeposit is gaiacli query gov param deposit +func (f *Fixtures) QueryGovParamDeposit() gov.DepositParams { + cmd := fmt.Sprintf("gaiacli query gov param deposit %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var depositParam gov.DepositParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &depositParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return depositParam +} + +// QueryGovParamVoting is gaiacli query gov param voting +func (f *Fixtures) QueryGovParamVoting() gov.VotingParams { + cmd := fmt.Sprintf("gaiacli query gov param voting %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var votingParam gov.VotingParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votingParam +} + +// QueryGovParamTallying is gaiacli query gov param tallying +func (f *Fixtures) QueryGovParamTallying() gov.TallyParams { + cmd := fmt.Sprintf("gaiacli query gov param tallying %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var tallyingParam gov.TallyParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &tallyingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return tallyingParam +} + +// QueryGovProposals is gaiacli query gov proposals +func (f *Fixtures) QueryGovProposals(flags ...string) gov.Proposals { + cmd := fmt.Sprintf("gaiacli query gov proposals %v", f.Flags()) + stdout, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + if strings.Contains(stderr, "No matching proposals found") { + return gov.Proposals{} + } + require.Empty(f.T, stderr) + var out gov.Proposals + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(stdout), &out) + require.NoError(f.T, err) + return out +} + +// QueryGovProposal is gaiacli query gov proposal +func (f *Fixtures) QueryGovProposal(proposalID int, flags ...string) gov.Proposal { + cmd := fmt.Sprintf("gaiacli query gov proposal %d %v", proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var proposal gov.Proposal + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &proposal) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return proposal +} + +// QueryGovVote is gaiacli query gov vote +func (f *Fixtures) QueryGovVote(proposalID int, voter sdk.AccAddress, flags ...string) gov.Vote { + cmd := fmt.Sprintf("gaiacli query gov vote %d %s %v", proposalID, voter, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var vote gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &vote) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return vote +} + +// QueryGovVotes is gaiacli query gov votes +func (f *Fixtures) QueryGovVotes(proposalID int, flags ...string) []gov.Vote { + cmd := fmt.Sprintf("gaiacli query gov votes %d %v", proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var votes []gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votes) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votes +} + +// QueryGovDeposit is gaiacli query gov deposit +func (f *Fixtures) QueryGovDeposit(proposalID int, depositor sdk.AccAddress, flags ...string) gov.Deposit { + cmd := fmt.Sprintf("gaiacli query gov deposit %d %s %v", proposalID, depositor, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposit gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposit) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposit +} + +// QueryGovDeposits is gaiacli query gov deposits +func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposit { + cmd := fmt.Sprintf("gaiacli query gov deposits %d %v", propsalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposits []gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposits) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposits +} + +//___________________________________________________________________________________ +// query slashing + +// QuerySlashingParams is gaiacli query slashing params +func (f *Fixtures) QuerySlashingParams() slashing.Params { + cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var params slashing.Params + err := cdc.UnmarshalJSON([]byte(res), ¶ms) + require.NoError(f.T, err) + return params +} + +//___________________________________________________________________________________ +// executors + +func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { + require.True(t, executeWrite(t, cmdStr, writes...)) +} + +func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { + exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) + return +} + +func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { + proc := tests.GoExecuteT(t, cmdStr) + + // Enables use of interactive commands + for _, write := range writes { + _, err := proc.StdinPipe.Write([]byte(write + "\n")) + require.NoError(t, err) + } + + // Read both stdout and stderr from the process + stdout, stderr, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, cmdStr) + } + + // Log output. + if len(stdout) > 0 { + t.Log("Stdout:", cmn.Green(string(stdout))) + } + if len(stderr) > 0 { + t.Log("Stderr:", cmn.Red(string(stderr))) + } + + // Wait for process to exit + proc.Wait() + + // Return succes, stdout, stderr + return proc.ExitState.Success(), string(stdout), string(stderr) +} + +//___________________________________________________________________________________ +// utils + +func addFlags(cmd string, flags []string) string { + for _, f := range flags { + cmd += " " + f + } + return strings.TrimSpace(cmd) +} + +func queryTags(tags []string) (out string) { + for _, tag := range tags { + out += tag + "&" + } + return strings.TrimSuffix(out, "&") +} + +func writeToNewTempFile(t *testing.T, s string) *os.File { + fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_") + require.Nil(t, err) + _, err = fp.WriteString(s) + require.Nil(t, err) + return fp +} + +func marshalStdTx(t *testing.T, stdTx auth.StdTx) []byte { + cdc := app.MakeCodec() + bz, err := cdc.MarshalBinaryBare(stdTx) + require.NoError(t, err) + return bz +} + +func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { + cdc := app.MakeCodec() + require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) + return +} diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 354de8c5d0b2..f390324aa383 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -22,30 +22,27 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" + at "github.com/cosmos/cosmos-sdk/x/auth" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + dist "github.com/cosmos/cosmos-sdk/x/distribution" + gv "github.com/cosmos/cosmos-sdk/x/gov" gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" + sl "github.com/cosmos/cosmos-sdk/x/slashing" slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" - stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + st "github.com/cosmos/cosmos-sdk/x/staking" + staking "github.com/cosmos/cosmos-sdk/x/staking/client/rest" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" distClient "github.com/cosmos/cosmos-sdk/x/distribution/client" govClient "github.com/cosmos/cosmos-sdk/x/gov/client" slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client" - stakeClient "github.com/cosmos/cosmos-sdk/x/stake/client" + stakingClient "github.com/cosmos/cosmos-sdk/x/staking/client" _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" ) -const ( - storeAcc = "acc" - storeGov = "gov" - storeSlashing = "slashing" - storeStake = "stake" - storeDist = "distr" -) - func main() { // Configure cobra to sort commands cobra.EnableCommandSorting = false @@ -67,10 +64,10 @@ func main() { // Module clients hold cli commnads (tx,query) and lcd routes // TODO: Make the lcd command take a list of ModuleClient mc := []sdk.ModuleClients{ - govClient.NewModuleClient(storeGov, cdc), - distClient.NewModuleClient(storeDist, cdc), - stakeClient.NewModuleClient(storeStake, cdc), - slashingClient.NewModuleClient(storeSlashing, cdc), + govClient.NewModuleClient(gv.StoreKey, cdc), + distClient.NewModuleClient(dist.StoreKey, cdc), + stakingClient.NewModuleClient(st.StoreKey, cdc), + slashingClient.NewModuleClient(sl.StoreKey, cdc), } rootCmd := &cobra.Command{ @@ -78,9 +75,14 @@ func main() { Short: "Command line interface for interacting with gaiad", } + // Add --chain-id to persistent flags and mark it required + rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { + return initConfig(rootCmd) + } + // Construct Root Command rootCmd.AddCommand( - rpc.InitClientCommand(), rpc.StatusCommand(), client.ConfigCmd(), queryCmd(cdc, mc), @@ -91,16 +93,13 @@ func main() { keys.Commands(), client.LineBreak, version.VersionCmd, + client.NewCompletionCmd(rootCmd, true), ) // Add flags and prefix all env exposed with GA executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) - err := initConfig(rootCmd) - if err != nil { - panic(err) - } - err = executor.Execute() + err := executor.Execute() if err != nil { fmt.Printf("Failed executing CLI command: %s, exiting...\n", err) os.Exit(1) @@ -120,7 +119,7 @@ func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { tx.SearchTxCmd(cdc), tx.QueryTxCmd(cdc), client.LineBreak, - authcmd.GetAccountCmd(storeAcc, cdc), + authcmd.GetAccountCmd(at.StoreKey, cdc), ) for _, m := range mc { @@ -140,6 +139,7 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { bankcmd.SendTxCmd(cdc), client.LineBreak, authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), bankcmd.GetBroadcastCommand(cdc), client.LineBreak, ) @@ -159,9 +159,9 @@ func registerRoutes(rs *lcd.RestServer) { 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, storeAcc) + auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) - stake.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) } @@ -189,7 +189,9 @@ func initConfig(cmd *cobra.Command) error { return err } } - + if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil { + return err + } if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { return err } diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 2791415c9350..4014bef7330e 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -4,8 +4,6 @@ import ( "encoding/json" "io" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/spf13/cobra" "github.com/spf13/viper" @@ -15,9 +13,12 @@ import ( "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -42,6 +43,7 @@ func main() { rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc)) + rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) @@ -55,21 +57,24 @@ func main() { } func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { - return app.NewGaiaApp(logger, db, traceStore, true, - baseapp.SetPruning(viper.GetString("pruning")), - baseapp.SetMinimumFees(viper.GetString("minimum_fees")), + return app.NewGaiaApp( + logger, db, traceStore, true, + baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))), + baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), ) } func exportAppStateAndTMValidators( logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, ) (json.RawMessage, []tmtypes.GenesisValidator, error) { - gApp := app.NewGaiaApp(logger, db, traceStore, false) if height != -1 { + gApp := app.NewGaiaApp(logger, db, traceStore, false) err := gApp.LoadHeight(height) if err != nil { return nil, nil, err } + return gApp.ExportAppStateAndValidators(forZeroHeight) } + gApp := app.NewGaiaApp(logger, db, traceStore, true) return gApp.ExportAppStateAndValidators(forZeroHeight) } diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 7d6a0b034de4..022192861e37 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -7,6 +7,8 @@ import ( "os" "path" + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/spf13/cobra" @@ -26,7 +28,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" ) @@ -48,7 +50,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error { fmt.Println(err) os.Exit(1) } - app := NewGaiaApp(logger, db, baseapp.SetPruning(viper.GetString("pruning"))) + app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning")))) // print some info id := app.LastCommitID() @@ -82,9 +84,9 @@ func runHackCmd(cmd *cobra.Command, args []string) error { ctx := app.NewContext(true, abci.Header{}) // check for the powerkey and the validator from the store - store := ctx.KVStore(app.keyStake) + store := ctx.KVStore(app.keyStaking) res := store.Get(powerKey) - val, _ := app.stakeKeeper.GetValidator(ctx, trouble) + val, _ := app.stakingKeeper.GetValidator(ctx, trouble) fmt.Println("checking height", checkHeight, res, val) if res == nil { bottomHeight = checkHeight @@ -131,8 +133,8 @@ type GaiaApp struct { // keys to access the substores keyMain *sdk.KVStoreKey keyAccount *sdk.KVStoreKey - keyStake *sdk.KVStoreKey - tkeyStake *sdk.TransientStoreKey + keyStaking *sdk.KVStoreKey + tkeyStaking *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyParams *sdk.KVStoreKey tkeyParams *sdk.TransientStoreKey @@ -141,7 +143,7 @@ type GaiaApp struct { accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper - stakeKeeper stake.Keeper + stakingKeeper staking.Keeper slashingKeeper slashing.Keeper paramsKeeper params.Keeper } @@ -156,39 +158,41 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp var app = &GaiaApp{ BaseApp: bApp, cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyStake: sdk.NewKVStoreKey("stake"), - tkeyStake: sdk.NewTransientStoreKey("transient_stake"), - keySlashing: sdk.NewKVStoreKey("slashing"), - keyParams: sdk.NewKVStoreKey("params"), - tkeyParams: sdk.NewTransientStoreKey("transient_params"), + keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keyStaking: sdk.NewKVStoreKey(staking.StoreKey), + tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), + keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), + keyParams: sdk.NewKVStoreKey(params.StoreKey), + tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), } + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) + // define the accountKeeper app.accountKeeper = auth.NewAccountKeeper( app.cdc, - app.keyAccount, // target store + app.keyAccount, // target store + app.paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, // prototype ) // add handlers app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) - app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace) + app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace) // register message routes app.Router(). AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("stake", stake.NewHandler(app.stakeKeeper)) + AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)) // initialize BaseApp app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) - app.MountStores(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing, app.keyParams) + app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keySlashing, app.keyParams) app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) err := app.LoadLatestVersion(app.keyMain) if err != nil { @@ -204,7 +208,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp func MakeCodec() *codec.Codec { var cdc = codec.New() bank.RegisterCodec(cdc) - stake.RegisterCodec(cdc) + staking.RegisterCodec(cdc) slashing.RegisterCodec(cdc) auth.RegisterCodec(cdc) sdk.RegisterCodec(cdc) @@ -225,7 +229,7 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // application updates every end block // nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - validatorUpdates, tags := stake.EndBlocker(ctx, app.stakeKeeper) + validatorUpdates, tags := staking.EndBlocker(ctx, app.stakingKeeper) return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, @@ -250,13 +254,13 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci app.accountKeeper.SetAccount(ctx, acc) } - // load the initial stake information - validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + // load the initial staking information + validators, err := staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData) if err != nil { panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") } - slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData) return abci.ResponseInitChain{ Validators: validators, diff --git a/cmd/gaia/cmd/gaiareplay/main.go b/cmd/gaia/cmd/gaiareplay/main.go index 7e6392bf1230..ef6bcb139a4a 100644 --- a/cmd/gaia/cmd/gaiareplay/main.go +++ b/cmd/gaia/cmd/gaiareplay/main.go @@ -7,6 +7,8 @@ import ( "path/filepath" "time" + "github.com/cosmos/cosmos-sdk/store" + cpm "github.com/otiai10/copy" "github.com/spf13/cobra" @@ -106,7 +108,7 @@ func run(rootDir string) { fmt.Println("Creating application") myapp := app.NewGaiaApp( ctx.Logger, appDB, traceStoreWriter, true, - baseapp.SetPruning("everything"), // nothing + baseapp.SetPruning(store.PruneEverything), // nothing ) // Genesis diff --git a/cmd/gaia/init/genesis_accts.go b/cmd/gaia/init/genesis_accts.go index 4f5043877956..bb9c4af0c8ce 100644 --- a/cmd/gaia/init/genesis_accts.go +++ b/cmd/gaia/init/genesis_accts.go @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/common" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" @@ -19,7 +20,7 @@ import ( // AddGenesisAccountCmd returns add-genesis-account cobra Command func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "add-genesis-account [address] [coin][,[coin]]", + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", Short: "Add genesis account to genesis.json", Args: cobra.ExactArgs(2), RunE: func(_ *cobra.Command, args []string) error { @@ -28,7 +29,15 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command addr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { - return err + kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome)) + if err != nil { + return err + } + info, err := kb.Get(args[0]) + if err != nil { + return err + } + addr = info.GetAddress() } coins, err := sdk.ParseCoins(args[1]) if err != nil { @@ -60,6 +69,7 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command } cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") return cmd } diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index 64d483a28a9d..25de93aab498 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -26,12 +26,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - "github.com/cosmos/cosmos-sdk/x/stake/client/cli" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/client/cli" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ( - defaultAmount = "100" + stakeTypes.DefaultBondDenom + defaultAmount = "100" + stakingTypes.DefaultBondDenom defaultCommissionRate = "0.1" defaultCommissionMaxRate = "0.2" defaultCommissionMaxChangeRate = "0.01" @@ -113,7 +113,7 @@ following delegation and commission default parameters: // Run gaiad tx create-validator txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) cliCtx := context.NewCLIContext().WithCodec(cdc) - cliCtx, txBldr, msg, err := cli.BuildCreateValidatorMsg(cliCtx, txBldr) + txBldr, msg, err := cli.BuildCreateValidatorMsg(cliCtx, txBldr) if err != nil { return err } @@ -168,7 +168,7 @@ following delegation and commission default parameters: func accountInGenesis(genesisState app.GenesisState, key sdk.AccAddress, coins sdk.Coins) error { accountIsInGenesis := false - bondDenom := genesisState.StakeData.Params.BondDenom + bondDenom := genesisState.StakingData.Params.BondDenom // Check if the account is in genesis for _, acc := range genesisState.Accounts { @@ -178,8 +178,8 @@ func accountInGenesis(genesisState app.GenesisState, key sdk.AccAddress, coins s // Ensure account contains enough funds of default bond denom if coins.AmountOf(bondDenom).GT(acc.Coins.AmountOf(bondDenom)) { return fmt.Errorf( - "Account %v is in genesis, but the only has %v%v available to stake, not %v%v", - key, acc.Coins.AmountOf(bondDenom), bondDenom, coins.AmountOf(bondDenom), bondDenom, + "account %v is in genesis, but it only has %v%v available to stake, not %v%v", + key.String(), acc.Coins.AmountOf(bondDenom), bondDenom, coins.AmountOf(bondDenom), bondDenom, ) } accountIsInGenesis = true @@ -191,7 +191,7 @@ func accountInGenesis(genesisState app.GenesisState, key sdk.AccAddress, coins s return nil } - return fmt.Errorf("Account %s in not in the app_state.accounts array of genesis.json", key) + return fmt.Errorf("account %s in not in the app_state.accounts array of genesis.json", key) } func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip, chainID string, diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 10b0b8265f7b..111a58032697 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -10,15 +10,16 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/spf13/cobra" "github.com/spf13/viper" - cfg "github.com/tendermint/tendermint/config" + tmconfig "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" @@ -76,13 +77,20 @@ Example: cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") - cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String( + client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created", + ) + cmd.Flags().String( + server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", stakingtypes.DefaultBondDenom), + "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)", + ) return cmd } -func initTestnet(config *cfg.Config, cdc *codec.Codec) error { +func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error { var chainID string + outDir := viper.GetString(flagOutputDir) numValidators := viper.GetInt(flagNumValidators) @@ -95,6 +103,9 @@ func initTestnet(config *cfg.Config, cdc *codec.Codec) error { nodeIDs := make([]string, numValidators) valPubKeys := make([]crypto.PubKey, numValidators) + gaiaConfig := srvconfig.DefaultConfig() + gaiaConfig.MinGasPrices = viper.GetString(server.FlagMinGasPrices) + var ( accs []app.GenesisAccount genFiles []string @@ -180,17 +191,17 @@ func initTestnet(config *cfg.Config, cdc *codec.Codec) error { accs = append(accs, app.GenesisAccount{ Address: addr, Coins: sdk.Coins{ - sdk.NewInt64Coin(fmt.Sprintf("%sToken", nodeDirName), 1000), - sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 150), + sdk.NewInt64Coin(fmt.Sprintf("%stoken", nodeDirName), 1000), + sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 500), }, }) - msg := stake.NewMsgCreateValidator( + msg := staking.NewMsgCreateValidator( sdk.ValAddress(addr), valPubKeys[i], - sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 100), - stake.NewDescription(nodeDirName, "", "", ""), - stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + sdk.NewInt64Coin(stakingtypes.DefaultBondDenom, 100), + staking.NewDescription(nodeDirName, "", "", ""), + staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), ) tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo) @@ -213,6 +224,9 @@ func initTestnet(config *cfg.Config, cdc *codec.Codec) error { _ = os.RemoveAll(outDir) return err } + + gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml") + srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig) } if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil { @@ -261,7 +275,7 @@ func initGenFiles( } func collectGenFiles( - cdc *codec.Codec, config *cfg.Config, chainID string, + cdc *codec.Codec, config *tmconfig.Config, chainID string, monikers, nodeIDs []string, valPubKeys []crypto.PubKey, numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string, ) error { diff --git a/cmd/gaia/init/utils.go b/cmd/gaia/init/utils.go index 14c625d7f31e..149b4d10e1bb 100644 --- a/cmd/gaia/init/utils.go +++ b/cmd/gaia/init/utils.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "path/filepath" "time" amino "github.com/tendermint/go-amino" @@ -16,6 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" ) // ExportGenesisFile creates and writes the genesis configuration to disk. An @@ -58,20 +60,6 @@ func ExportGenesisFileWithTime( return genDoc.SaveAs(genFile) } -// read of create the private key file for this config -func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { - var privValidator *privval.FilePV - - if common.FileExists(privValFile) { - privValidator = privval.LoadFilePV(privValFile) - } else { - privValidator = privval.GenFilePV(privValFile) - privValidator.Save() - } - - return privValidator.GetPubKey() -} - // InitializeNodeValidatorFiles creates private validator and p2p configuration files. func InitializeNodeValidatorFiles( config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error, @@ -83,7 +71,19 @@ func InitializeNodeValidatorFiles( } nodeID = string(nodeKey.ID()) - valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) + server.UpgradeOldPrivValFile(config) + + pvKeyFile := config.PrivValidatorKeyFile() + if err := common.EnsureDir(filepath.Dir(pvKeyFile), 0777); err != nil { + return nodeID, valPubKey, nil + } + + pvStateFile := config.PrivValidatorStateFile() + if err := common.EnsureDir(filepath.Dir(pvStateFile), 0777); err != nil { + return nodeID, valPubKey, nil + } + + valPubKey = privval.LoadOrGenFilePV(pvKeyFile, pvStateFile).GetPubKey() return nodeID, valPubKey, nil } diff --git a/cmd/logjack/main.go b/cmd/logjack/main.go deleted file mode 100644 index cb4048814b92..000000000000 --- a/cmd/logjack/main.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "os" - "strconv" - "strings" - - auto "github.com/tendermint/tendermint/libs/autofile" - cmn "github.com/tendermint/tendermint/libs/common" -) - -//nolint -const Version = "0.0.2" -const sleepSeconds = 1 // Every second -const readBufferSize = 1024 // 1KB at a time - -// Parse command-line options -func parseFlags() (headPath string, chopSize int64, limitSize int64, version bool) { - var flagSet = flag.NewFlagSet(os.Args[0], flag.ExitOnError) - var chopSizeStr, limitSizeStr string - flagSet.StringVar(&headPath, "head", "logjack.out", "Destination (head) file.") - flagSet.StringVar(&chopSizeStr, "chop", "100M", "Move file if greater than this") - flagSet.StringVar(&limitSizeStr, "limit", "10G", "Only keep this much (for each specified file). Remove old files.") - flagSet.BoolVar(&version, "version", false, "Version") - flagSet.Parse(os.Args[1:]) //nolint - chopSize = parseBytesize(chopSizeStr) - limitSize = parseBytesize(limitSizeStr) - return -} - -func main() { - - // Read options - headPath, chopSize, limitSize, version := parseFlags() - if version { - fmt.Printf("logjack version %v\n", Version) - return - } - - // Open Group - group, err := auto.OpenGroup(headPath, auto.GroupHeadSizeLimit(chopSize), auto.GroupTotalSizeLimit(limitSize)) - if err != nil { - fmt.Printf("logjack couldn't create output file %v\n", headPath) - os.Exit(1) - } - // TODO: Maybe fix Group to re-allow these mutations. - // group.SetHeadSizeLimit(chopSize) - // group.SetTotalSizeLimit(limitSize) - err = group.Start() - if err != nil { - fmt.Printf("logjack couldn't start with file %v\n", headPath) - os.Exit(1) - } - - go func() { - // Forever, read from stdin and write to AutoFile. - buf := make([]byte, readBufferSize) - for { - n, err := os.Stdin.Read(buf) - group.Write(buf[:n]) //nolint - group.Flush() //nolint - if err != nil { - group.Stop() //nolint - if err == io.EOF { - os.Exit(0) - } else { - fmt.Println("logjack errored") - os.Exit(1) - } - } - } - }() - - // Trap signal - cmn.TrapSignal(func() { - fmt.Println("logjack shutting down") - }) -} - -func parseBytesize(chopSize string) int64 { - // Handle suffix multiplier - var multiplier int64 = 1 - if strings.HasSuffix(chopSize, "T") { - multiplier = 1042 * 1024 * 1024 * 1024 - chopSize = chopSize[:len(chopSize)-1] - } - if strings.HasSuffix(chopSize, "G") { - multiplier = 1042 * 1024 * 1024 - chopSize = chopSize[:len(chopSize)-1] - } - if strings.HasSuffix(chopSize, "M") { - multiplier = 1042 * 1024 - chopSize = chopSize[:len(chopSize)-1] - } - if strings.HasSuffix(chopSize, "K") { - multiplier = 1042 - chopSize = chopSize[:len(chopSize)-1] - } - - // Parse the numeric part - chopSizeInt, err := strconv.Atoi(chopSize) - if err != nil { - panic(err) - } - - return int64(chopSizeInt) * multiplier -} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index a18b1c0c196d..94aaf953c0c4 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -19,7 +19,10 @@ module.exports = { indexName: 'cosmos_network', debug: false }, - nav: [{ text: "Back to Cosmos", link: "https://cosmos.network" }], + nav: [ + { text: "Back to Cosmos", link: "https://cosmos.network" }, + { text: "RPC", link: "../rpc/" } + ], sidebar: [ { title: "Overview", @@ -34,6 +37,7 @@ module.exports = { title: "Gaia", collapsable: false, children: [ + "/gaia/what-is-gaia" "/gaia/installation", "/gaia/join-testnet", "/gaia/validators/validator-setup", diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index c21bdacc531e..5870d3e27e14 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -66,25 +66,25 @@ to send users to the GitHub. To build and serve the documentation locally, run: -``` +```bash npm install -g vuepress ``` then change the following line in the `config.js`: -``` +```js base: "/docs/", ``` to: -``` +```js base: "/", ``` Finally, go up one directory to the root of the repo and run: -``` +```bash # from root of repo vuepress build docs cd dist/docs @@ -110,3 +110,17 @@ We are using [Algolia](https://www.algolia.com) to power full-text search. This Because the build processes are identical (as is the information contained herein), this file should be kept in sync as much as possible with its [counterpart in the Tendermint Core repo](https://github.com/tendermint/tendermint/blob/develop/docs/DOCS_README.md). +### Update and Build the RPC docs + +1. Execute the following command at the root directory to install the swagger-ui generate tool. + ```bash + make tools + ``` +2. Edit API docs + 1. Directly Edit API docs manually: `client/lcd/swagger-ui/swagger.yaml`. + 2. Edit API docs within the [Swagger Editor](https://editor.swagger.io/). Please refer to this [document](https://swagger.io/docs/specification/2-0/basic-structure/) for the correct structure in `.yaml`. +3. Download `swagger.yaml` and replace the old `swagger.yaml` under fold `client/lcd/swagger-ui`. +4. Compile gaiacli + ```bash + make install + ``` diff --git a/docs/README.md b/docs/README.md index 37a784287f68..09c96f9ebb56 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,40 +1,33 @@ -# Welcome to the SDK Docs! +# Cosmos SDK Documentation -Welcome to the SDK docs! +## Get Started +- **[SDK Intro](./intro/README.md)**: High-level overview of the Cosmos SDK. +- **[SDK application tutorial](https://github.com/cosmos/sdk-application-tutorial)**: A tutorial to learn the SDK. It showcases how to build an SDK-based blockchain from scratch, and explains the basic principles of the SDK in the process. -![cosmonaut reading the cosmos docs in space](./cosmos-docs.jpg) +## Resources -## Learn the SDK +- [Specifications](./spec/README.md): Specifications of modules and other parts of the Cosmos SDK. +- [SDK API Reference](https://godoc.org/github.com/cosmos/cosmos-sdk): Godocs of the Cosmos SDK. +- [REST API spec](https://cosmos.network/rpc/): List of endpoints to interact with a `gaia` full-node through REST. -### SDK Intro - -If you are a newcomer and would like to learn more about the Cosmos SDK, this **[intro](./intro/README.md)** is a good starting place. - -### SDK tutorial - -If you like to learn by doing, you can follow the **[SDK application tutorial](https://github.com/cosmos/sdk-application-tutorial)**. It showcases how to build an SDK-based blockchain from scratch, and teaches you about the basic principles the SDK in the process. - -## Use the SDK - -The following sections contain the information you need if you want to build a fully-functional SDK-based blockchain: +## Cosmos Hub testnet ->*NOTE*: We are currently working on improving the docs. Some info might be missing. If that is the case, try the Cosmos [Forum](https://forum.cosmos.network). Failing that, [open an issue](https://github.com/cosmos/cosmos-sdk/issues/new). +- [Join the public testnet](./gaia/join-testnet.md) of the Cosmos Hub. +- [Start your own `gaia` testnet](./gaia/deploy-testnet.md). -- [Introduction](./intro/README.md): Contains introductory high-level material on the Cosmos SDK. -- [Gaia](./gaia/README.md): Contains all documentation related to the gaia application (current name for the Cosmos-Hub). Also contains info on how to join gaia testnets. -- [Clients](./clients/README.md): Documentation about SDK clients like the SDK Command-Line interface and the SDK Light-client. -- [Specifications](./spec/README.md): Contains SDK and modules specifications. -- [SDK API Reference](https://godoc.org/github.com/cosmos/cosmos-sdk): Godocs of the Cosmos-SDK. -- [REST API spec](https://cosmos.network/rpc/): List of endpoints to interract with a `gaia` full-node through REST. +## Creating a new SDK project -If you are reading this on the Cosmos Website, please know that you can find more information on [github](https://github.com/cosmos/cosmos-sdk/tree/develop/docs). Also if you find any issues with the documentation please [*open a Pull Request*](https://github.com/cosmos/cosmos-sdk/compare?expand=1), or at least [*open an Issue*](https://github.com/cosmos/cosmos-sdk/issues/new) to update the docs! +To create a new project, you can either: -## Cosmos Hub testnet +- Fork [this repo](https://github.com/cosmos/sdk-application-tutorial/). Do not forget to remove the `nameservice` module from the various files if you don't need it. +- Use community tools like [chainkit](https://github.com/blocklayerhq/chainkit). -To install the latest version of the `gaia` application (application of the Cosmos Hub) and join the public testnet, **click [here](./gaia/join-testnet.md)** +## Languages -To start your own `gaia` testnet, **click [here](./gaia/deploy-testnet.md)** +The Cosmos-SDK is currently written in [Golang](https://golang.org/), though the +framework could be implemented similarly in other languages. +Contact us for information about funding an implementation in another language. ## Contribute @@ -43,4 +36,4 @@ considerations when making changes. ## Version -This documentation is built from the following commit: + This documentation is built from the following commit: diff --git a/docs/_attic/sdk/core/app1.md b/docs/_attic/sdk/core/app1.md index a1aa76f72e39..4b2257b3c0a2 100644 --- a/docs/_attic/sdk/core/app1.md +++ b/docs/_attic/sdk/core/app1.md @@ -426,7 +426,7 @@ func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app1Name, logger, db, tx1Decoder) // Create a capability key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) // Register message routes. // Note the handler receives the keyAccount and thus diff --git a/docs/_attic/sdk/core/app2.md b/docs/_attic/sdk/core/app2.md index b0e42fd10df7..b43fbfc883db 100644 --- a/docs/_attic/sdk/core/app2.md +++ b/docs/_attic/sdk/core/app2.md @@ -2,7 +2,7 @@ In the previous app we built a simple bank with one message type `send` for sending coins and one store for storing accounts. -Here we build `App2`, which expands on `App1` by introducing +Here we build `App2`, which expands on `App1` by introducing - a new message type for issuing new coins - a new store for coin metadata (like who can issue coins) @@ -38,7 +38,7 @@ methods for `MsgIssue` are similar to `MsgSend`. ## Handler We'll need a new handler to support the new message type. It just checks if the -sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information +sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information in the issuer store: ```go @@ -107,11 +107,11 @@ but that's left as an excercise for the reader :). ## Amino -Now that we have two implementations of `Msg`, we won't know before hand +Now that we have two implementations of `Msg`, we won't know before hand which type is contained in a serialized `Tx`. Ideally, we would use the `Msg` interface inside our `Tx` implementation, but the JSON decoder can't -decode into interface types. In fact, there's no standard way to unmarshal -into interfaces in Go. This is one of the primary reasons we built +decode into interface types. In fact, there's no standard way to unmarshal +into interfaces in Go. This is one of the primary reasons we built [Amino](https://github.com/tendermint/go-amino) :). While SDK developers can encode transactions and state objects however they @@ -121,7 +121,7 @@ excludes the `oneof` keyword. While `oneof` provides union types, Amino aims to provide interfaces. The main difference being that with union types, you have to know all the types -up front. But anyone can implement an interface type whenever and however +up front. But anyone can implement an interface type whenever and however they like. To implement interface types, Amino allows any concrete implementation of an @@ -164,7 +164,7 @@ Now that we're using Amino, we can embed the `Msg` interface directly in our // Simple tx to wrap the Msg. type app2Tx struct { sdk.Msg - + PubKey crypto.PubKey Signature []byte } @@ -189,7 +189,7 @@ func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder { ## AnteHandler -Now that we have an implementation of `Tx` that includes more than just the Msg, +Now that we have an implementation of `Tx` that includes more than just the Msg, we need to specify how that extra information is validated and processed. This is the role of the `AnteHandler`. The word `ante` here denotes "before", as the `AnteHandler` is run before a `Handler`. While an app can have many Handlers, @@ -209,7 +209,7 @@ according to whatever capability keys it was granted. Instead of a `Msg`, however, it takes a `Tx`. Like Handler, AnteHandler returns a `Result` type, but it also returns a new -`Context` and an `abort bool`. +`Context` and an `abort bool`. For `App2`, we simply check if the PubKey matches the Address, and the Signature validates with the PubKey: @@ -259,7 +259,7 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app2Name, logger, db, txDecoder(cdc)) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) // Create a key for accessing the issue store. keyIssue := sdk.NewKVStoreKey("issue") diff --git a/docs/_attic/sdk/core/app3.md b/docs/_attic/sdk/core/app3.md index a84bb6873da9..420423681447 100644 --- a/docs/_attic/sdk/core/app3.md +++ b/docs/_attic/sdk/core/app3.md @@ -1,8 +1,8 @@ # Modules In the previous app, we introduced a new `Msg` type and used Amino to encode -transactions. We also introduced additional data to the `Tx`, and used a simple -`AnteHandler` to validate it. +transactions. We also introduced additional data to the `Tx`, and used a simple +`AnteHandler` to validate it. Here, in `App3`, we introduce two built-in SDK modules to replace the `Msg`, `Tx`, `Handler`, and `AnteHandler` implementations we've seen @@ -16,11 +16,11 @@ The `x/bank` module implements `Msg` and `Handler` - it has everything we need to transfer coins between accounts. Here, we'll introduce the important types from `x/auth` and `x/bank`, and use -them to build `App3`, our shortest app yet. The complete code can be found in +them to build `App3`, our shortest app yet. The complete code can be found in [app3.go](examples/app3.go), and at the end of this section. For more details, see the -[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and +[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and [x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation. ## Accounts @@ -74,7 +74,6 @@ The default implementation of `Account` is the `BaseAccount`: ```go // BaseAccount - base account structure. // Extend this by embedding this in your AppAccount. -// See the examples/basecoin/types/account.go for an example. type BaseAccount struct { Address sdk.AccAddress `json:"address"` Coins sdk.Coins `json:"coins"` @@ -102,7 +101,7 @@ the `BaseAccount` to store additional data without requiring another lookup from the store. Creating an AccountKeeper is easy - we just need to specify a codec, a -capability key, and a prototype of the object being encoded +capability key, and a prototype of the object being encoded ```go accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) @@ -145,7 +144,7 @@ type StdTx struct { This is the standard form for a transaction in the SDK. Besides the Msgs, it includes: -- a fee to be paid by the first signer +- a fee to be paid by the first signer - replay protecting nonces in the signature - a memo of prunable additional data @@ -166,8 +165,8 @@ type StdSignature struct { } ``` -The signature includes both an `AccountNumber` and a `Sequence`. -The `Sequence` must match the one in the +The signature includes both an `AccountNumber` and a `Sequence`. +The `Sequence` must match the one in the corresponding account when the transaction is processed, and will increment by one with every transaction. This prevents the same transaction from being replayed multiple times, resolving the insecurity that @@ -200,10 +199,10 @@ transaction fee can be paid, and reject the transaction if not. The `StdTx` supports multiple messages and multiple signers. To sign the transaction, each signer must collect the following information: -- the ChainID +- the ChainID - the AccountNumber and Sequence for the given signer's account (from the blockchain) -- the transaction fee +- the transaction fee - the list of transaction messages - an optional memo @@ -244,7 +243,7 @@ Note that validating signatures requires checking that the correct account number and sequence was used by each signer, as this information is required in the `StdSignBytes`. -If any of the above are not satisfied, the AnteHandelr returns an error. +If any of the above are not satisfied, the AnteHandelr returns an error. If all of the above verifications pass, the AnteHandler makes the following changes to the state: @@ -254,7 +253,7 @@ changes to the state: - deduct the fee from the first signer's account Recall that incrementing the `Sequence` prevents "replay attacks" where -the same message could be executed over and over again. +the same message could be executed over and over again. The PubKey is required for signature verification, but it is only required in the StdSignature once. From that point on, it will be stored in the account. @@ -267,13 +266,13 @@ Now that we've seen the `auth.AccountKeeper` and how its used to build a complete AnteHandler, it's time to look at how to build higher-level abstractions for taking action on accounts. -Earlier, we noted that `Mappers` are abstactions over KVStores that handle -marshalling and unmarshalling data types to and from underlying stores. -We can build another abstraction on top of `Mappers` that we call `Keepers`, +Earlier, we noted that `Mappers` are abstactions over KVStores that handle +marshalling and unmarshalling data types to and from underlying stores. +We can build another abstraction on top of `Mappers` that we call `Keepers`, which expose only limitted functionality on the underlying types stored by the `Mapper`. For instance, the `x/bank` module defines the canonical versions of `MsgSend` -and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However, +and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However, rather than passing a `KVStore` or even an `AccountKeeper` directly to the handler, we introduce a `bank.Keeper`, which can only be used to transfer coins in and out of accounts. This allows us to determine up front that the only effect the bank module's @@ -302,21 +301,21 @@ docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full Note we can refine the `bank.Keeper` by restricting it's method set. For instance, the -[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper) +[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper) is a read-only version, while the -[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper) +[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper) only executes transfers of coins from input accounts to output accounts. We use this `Keeper` paradigm extensively in the SDK as the way to define what kind of functionality each module gets access to. In particular, we try to follow the *principle of least authority*. -Rather than providing full blown access to the `KVStore` or the `AccountKeeper`, +Rather than providing full blown access to the `KVStore` or the `AccountKeeper`, we restrict access to a small number of functions that do very specific things. ## App3 -With the `auth.AccountKeeper` and `bank.Keeper` in hand, +With the `auth.AccountKeeper` and `bank.Keeper` in hand, we're now ready to build `App3`. The `x/auth` and `x/bank` modules do all the heavy lifting: @@ -330,8 +329,8 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app3Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") - keyFees := sdk.NewKVStoreKey("fee") // TODO + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) + keyFees := sdk.NewKVStoreKey(auth.FeeStoreKey) // TODO // Set various mappers/keepers to interact easily with underlying stores accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) @@ -355,8 +354,8 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { } ``` -Note we use `bank.NewHandler`, which handles only `bank.MsgSend`, -and receives only the `bank.Keeper`. See the +Note we use `bank.NewHandler`, which handles only `bank.MsgSend`, +and receives only the `bank.Keeper`. See the [x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) for more details. @@ -366,7 +365,7 @@ We also use the default txDecoder in `x/auth`, which decodes amino-encoded ## Conclusion Armed with native modules for authentication and coin transfer, -emboldened by the paradigm of mappers and keepers, -and ever invigorated by the desire to build secure state-machines, +emboldened by the paradigm of mappers and keepers, +and ever invigorated by the desire to build secure state-machines, we find ourselves here with a full-blown, all-checks-in-place, multi-asset cryptocurrency - the beating heart of the Cosmos-SDK. diff --git a/docs/_attic/sdk/core/examples/app1.go b/docs/_attic/sdk/core/examples/app1.go index 25e8b40def5f..715a88bc72b6 100644 --- a/docs/_attic/sdk/core/examples/app1.go +++ b/docs/_attic/sdk/core/examples/app1.go @@ -9,6 +9,7 @@ import ( bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" ) const ( @@ -22,7 +23,7 @@ func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app1Name, logger, db, tx1Decoder) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) // Register message routes. // Note the handler gets access to the account store. diff --git a/docs/_attic/sdk/core/examples/app2.go b/docs/_attic/sdk/core/examples/app2.go index 620a1911355b..5dcf291450ca 100644 --- a/docs/_attic/sdk/core/examples/app2.go +++ b/docs/_attic/sdk/core/examples/app2.go @@ -15,6 +15,7 @@ import ( bapp "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" ) const ( @@ -42,7 +43,7 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app2Name, logger, db, tx2Decoder(cdc)) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) // Create a key for accessing the issue store. keyIssue := sdk.NewKVStoreKey("issue") diff --git a/docs/_attic/sdk/core/examples/app2_test.go b/docs/_attic/sdk/core/examples/app2_test.go index 0712e7addbb7..bed30f0e48b8 100644 --- a/docs/_attic/sdk/core/examples/app2_test.go +++ b/docs/_attic/sdk/core/examples/app2_test.go @@ -21,7 +21,7 @@ func TestEncoding(t *testing.T) { sendMsg := MsgSend{ From: addr1, To: addr2, - Amount: sdk.Coins{sdk.NewCoin("testCoins", sdk.NewInt(100))}, + Amount: sdk.Coins{sdk.NewCoin("testcoins", sdk.NewInt(100))}, } // Construct transaction @@ -55,7 +55,7 @@ func TestEncoding(t *testing.T) { issueMsg := MsgIssue{ Issuer: addr1, Receiver: addr2, - Coin: sdk.Coin{"testCoin", sdk.NewInt(100)}, + Coin: sdk.NewCoin("testcoin", sdk.NewInt(100)), } signBytes = issueMsg.GetSignBytes() diff --git a/docs/_attic/sdk/core/examples/app3.go b/docs/_attic/sdk/core/examples/app3.go index 453970c1af3b..cd30a2f4daa0 100644 --- a/docs/_attic/sdk/core/examples/app3.go +++ b/docs/_attic/sdk/core/examples/app3.go @@ -26,8 +26,8 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app3Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") - keyFees := sdk.NewKVStoreKey("fee") // TODO + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) + keyFees := sdk.NewKVStoreKey(auth.FeeStoreKey) // TODO // Set various mappers/keepers to interact easily with underlying stores accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) diff --git a/docs/_attic/sdk/core/examples/app4.go b/docs/_attic/sdk/core/examples/app4.go index 6d45c403153e..9bc8d37516e4 100644 --- a/docs/_attic/sdk/core/examples/app4.go +++ b/docs/_attic/sdk/core/examples/app4.go @@ -25,14 +25,14 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app4Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. - keyAccount := sdk.NewKVStoreKey("acc") + keyAccount := sdk.NewKVStoreKey(auth.StoreKey) // Set various mappers/keepers to interact easily with underlying stores accountKeeper := auth.NewAccountKeeper(cdc, keyAccount, auth.ProtoBaseAccount) bankKeeper := bank.NewBaseKeeper(accountKeeper) // TODO - keyFees := sdk.NewKVStoreKey("fee") + keyFees := sdk.NewKVStoreKey(auth.FeeStoreKey) feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) app.SetAnteHandler(auth.NewAnteHandler(accountKeeper, feeKeeper)) diff --git a/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md b/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md index a90a6913e2c3..26365c506ada 100644 --- a/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md +++ b/docs/_attic/sdk/sdk-by-examples/simple-governance/bridging-it-all.md @@ -56,7 +56,7 @@ type SimpleGovApp struct { Let us do a quick reminder so that it is clear why we need these stores and keepers. Our application is primarily based on the `simple_governance` module. However, we have established in section [Keepers for our app](module-keeper.md) that our module needs access to two other modules: the `bank` module and the `stake` module. We also need the `auth` module for basic account functionalities. Finally, we need access to the main multistore to declare the stores of each of the module we use. -## CLI and Rest server +## CLI and Rest server We will need to add the newly created commands to our application. To do so, go to the `cmd` folder inside your root directory: @@ -66,7 +66,7 @@ cd cmd ``` `simplegovd` is the folder that stores the command for running the server daemon, whereas `simplegovcli` defines the commands of your application. -### Application CLI +### Application CLI **File: [`cmd/simplegovcli/maing.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/cmd/simplegovcli/main.go)** @@ -191,9 +191,9 @@ var cdc = MakeCodec() var app = &SimpleGovApp{ BaseApp: bam.NewBaseApp(appName, cdc, logger, db), cdc: cdc, - capKeyMainStore: sdk.NewKVStoreKey("main"), - capKeyAccountStore: sdk.NewKVStoreKey("acc"), - capKeyStakingStore: sdk.NewKVStoreKey("stake"), + capKeyMainStore: sdk.NewKVStoreKey(bam.MainStoreKey), + capKeyAccountStore: sdk.NewKVStoreKey(auth.StoreKey), + capKeyStakingStore: sdk.NewKVStoreKey(stake.StoreKey), capKeySimpleGovStore: sdk.NewKVStoreKey("simpleGov"), } ``` @@ -248,4 +248,4 @@ func MakeCodec() *codec.Codec { cdc.RegisterConcrete(&types.AppAccount{}, "simpleGov/Account", nil) return cdc } -``` \ No newline at end of file +``` diff --git a/docs/architecture/adr-002-docs-structure.md b/docs/architecture/adr-002-docs-structure.md index 4d7688e73388..ef7a14129582 100644 --- a/docs/architecture/adr-002-docs-structure.md +++ b/docs/architecture/adr-002-docs-structure.md @@ -2,7 +2,7 @@ ## Context -There is a need for a scalable structure of the SDK documentation. Current documentation includes a lot of non-related SDK material, is difficult to maintain and hard to follow as a user. +There is a need for a scalable structure of the SDK documentation. Current documentation includes a lot of non-related SDK material, is difficult to maintain and hard to follow as a user. Ideally, we would have: - All docs related to dev frameworks or tools live in their respective github repos (sdk repo would contain sdk docs, hub repo would contain hub docs, lotion repo would contain lotion docs, etc.) @@ -34,9 +34,6 @@ docs/ │ │ ├── cli │ ├── gas │ └── commands -├── examples/ -│ ├── basecoin/ -│ └── democoin/ ├── clients/ │ ├── lite/ │ ├── service-providers @@ -46,11 +43,10 @@ docs/ The files in each sub-folders do not matter and will likely change. What matters is the sectioning: -- `README`: Landing page of the docs. +- `README`: Landing page of the docs. - `into`: Introductory material. Goal is to have a short explainer of the SDK and then channel people to the resource they need. The [sdk-tutorial](https://github.com/cosmos/sdk-application-tutorial/) will be highlighted, as well as the `godocs`. - `gaia`: Contains all docs related to the `gaia` application. Will later be renamed to `cosmos-hub` or `chub` and probably moved to its own repository. -- `concepts`: Contains high-level explanations of the abstractions of the SDK. It does not contain specific code implementation and does not need to be updated often. **It is not an API specification of the interfaces**. API spec is the `godoc`. -- `examples`: Contain a couple examples of sdk application like `basecoin` and `democoin`. Developers need to maintain them up-to-date and make sure they compile as the SDK gets upgraded. +- `concepts`: Contains high-level explanations of the abstractions of the SDK. It does not contain specific code implementation and does not need to be updated often. **It is not an API specification of the interfaces**. API spec is the `godoc`. - `clients`: Contains specs and info about the various SDK clients. - `spec`: Contains specs of modules, and others. - `architecture`: Contains architecture-related docs like the present one. @@ -73,16 +69,16 @@ Accepted ### Positive -- Much clearer organisation of the SDK docs. +- Much clearer organisation of the SDK docs. - The `/docs` folder now only contains SDK and gaia related material. Later, it will only contain SDK related material. -- Developers only have to update `/docs` folder when they open a PR (and not `/examples` for example). +- Developers only have to update `/docs` folder when they open a PR (and not `/examples` for example). - Easier for developers to find what they need to update in the docs thanks to reworked architecture. -- Cleaner vuepress build for website docs. +- Cleaner vuepress build for website docs. - Will help build an executable doc (cf https://github.com/cosmos/cosmos-sdk/issues/2611) ### Neutral -- We need to move a bunch of deprecated stuff to `/_attic` folder. +- We need to move a bunch of deprecated stuff to `/_attic` folder. - We need to integrate content in `docs/sdk/docs/core` in `concepts`. - We need to move all the content that currently lives in `docs` and does not fit in new structure (like `lotion`, intro material, whitepaper) to the website repository. - Update `DOCS_README.md` @@ -91,4 +87,4 @@ Accepted - https://github.com/cosmos/cosmos-sdk/issues/1460 - https://github.com/cosmos/cosmos-sdk/pull/2695 -- https://github.com/cosmos/cosmos-sdk/issues/2611 \ No newline at end of file +- https://github.com/cosmos/cosmos-sdk/issues/2611 diff --git a/docs/concepts/baseapp.md b/docs/concepts/baseapp.md index ad3b5671679d..e96cec525e5c 100644 --- a/docs/concepts/baseapp.md +++ b/docs/concepts/baseapp.md @@ -13,7 +13,7 @@ commitment, only the `DeliverTx` is persisted. The BaseApp requires stores to be mounted via capabilities keys - handlers can only access stores they're given the key to. The `baseApp` ensures all stores are properly loaded, cached, and committed. One mounted store is considered the -"main" - it holds the latest block header, from which we can find and load the +"main" (`baseApp.MainStoreKey`) - it holds the latest block header, from which we can find and load the most recent state. The BaseApp distinguishes between two handler types - the `AnteHandler` and the @@ -116,9 +116,9 @@ otherwise a gas meter with `ConsensusParam.BlockSize.MaxGas` is initialized. Before the transaction logic is run, the `BlockGasMeter` is first checked to see if any gas remains. If no gas remains, then `DeliverTx` immediately returns -an error. +an error. After the transaction has been processed, the used gas (up to the transaction gas limit) is deducted from the BlockGasMeter. If the remaining gas exceeds the meter's limits, then DeliverTx returns an error and the transaction is not -committed. +committed. diff --git a/docs/examples/README.md b/docs/examples/README.md deleted file mode 100644 index 5d872a7c938d..000000000000 --- a/docs/examples/README.md +++ /dev/null @@ -1,320 +0,0 @@ -# Basecoin Example - -Here we explain how to get started with a basic Basecoin blockchain, how -to send transactions between accounts using the ``basecli`` tool, and -what is happening under the hood. - -## Setup and Install - -You will need to have go installed on your computer. Please refer to the [cosmos testnet tutorial](https://cosmos.network/validators/tutorial), which will always have the most updated instructions on how to get setup with go and the cosmos repository. - -Once you have go installed, run the command: - -``` -go get github.com/cosmos/cosmos-sdk -``` - -There will be an error stating `can't load package: package github.com/cosmos/cosmos-sdk: no Go files`, however you can ignore this error, it doesn't affect us. Now change directories to: - -``` -cd $GOPATH/src/github.com/cosmos/cosmos-sdk -``` - -And run : - -``` -make get_tools // run make update_tools if you already had it installed -make get_vendor_deps -make install_examples -``` -Then run `make install_examples`, which creates binaries for `basecli` and `basecoind`. You can look at the Makefile if you want to see the details on what these make commands are doing. - -## Using basecli and basecoind - -Check the versions by running: - -``` -basecli version -basecoind version -``` - -They should read something like `0.17.1-5d18d5f`, but the versions will be constantly updating so don't worry if your version is higher that 0.17.1. That's a good thing. - -Note that you can always check help in the terminal by running `basecli -h` or `basecoind -h`. It is good to check these out if you are stuck, because updates to the code base might slightly change the commands, and you might find the correct command in there. - -Let's start by initializing the basecoind daemon. Run the command - -``` -basecoind init -``` - -And you should see something like this: - -``` -{ - "chain_id": "test-chain-z77iHG", - "node_id": "e14c5056212b5736e201dd1d64c89246f3288129", - "app_message": { - "secret": "pluck life bracket worry guilt wink upgrade olive tilt output reform census member trouble around abandon" - } -} -``` - -This creates the `~/.basecoind folder`, which has config.toml, genesis.json, node_key.json, priv_validator.json. Take some time to review what is contained in these files if you want to understand what is going on at a deeper level. - - -## Generating keys - -The next thing we'll need to do is add the key from priv_validator.json to the gaiacli key manager. For this we need the 16 word seed that represents the private key, and a password. You can also get the 16 word seed from the output seen above, under `"secret"`. Then run the command: - -``` -basecli keys add alice --recover -``` - -Which will give you three prompts: - -``` -Enter a passphrase for your key: -Repeat the passphrase: -Enter your recovery seed phrase: -``` - -You just created your first locally stored key, under the name alice, and this account is linked to the private key that is running the basecoind validator node. Once you do this, the ~/.basecli folder is created, which will hold the alice key and any other keys you make. Now that you have the key for alice, you can start up the blockchain by running - -``` -basecoind start -``` - -You should see blocks being created at a fast rate, with a lot of output in the terminal. - -Next we need to make some more keys so we can use the send transaction functionality of basecoin. Open a new terminal, and run the following commands, to make two new accounts, and give each account a password you can remember: - -``` -basecli keys add bob -basecli keys add charlie -``` - -You can see your keys with the command: - -``` -basecli keys list -``` - -You should now see alice, bob and charlie's account all show up. - -``` -NAME: ADDRESS: PUBKEY: -alice cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f cosmospub1addwnpepq0w037u5g7y7lvdvsred2dehg90j84k0weyss5ynysf0nnnax74agrsxns6 -bob cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz cosmospub1addwnpepqwe97n8lryxrzvamrvjfj24jys3uzf8wndfvqa2l7mh5nsv4jrvdznvyeg6 -charlie cosmos13wq5mklhn03ljpd4dkph5rflk5a3ssma2ag07q cosmospub1addwnpepqdmtxv35rrmv2dvcr3yhfyxj7dzrd4z4rnhmclksq4g55a4wpl54clvx33l -``` - - -## Send transactions - -Lets send bob and charlie some tokens. First, lets query alice's account so we can see what kind of tokens she has: - -``` -basecli account cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f -``` - -Where `cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f` is alice's address we got from running `basecli keys list`. You should see a large amount of "mycoin" there. If you search for bob's or charlie's address, the command will fail, because they haven't been added into the blockchain database yet since they have no coins. We need to send them some! - -The following command will send coins from alice, to bob: - -``` -basecli send --from=alice --amount=10000mycoin --to=cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz ---sequence=0 --chain-id=test-chain-AE4XQo -``` - -Flag Descriptions: -- `from` is the name you gave your key -- `mycoin` is the name of the token for this basecoin demo, initialized in the genesis.json file -- `sequence` is a tally of how many transactions have been made by this account. Since this is the first tx on this account, it is 0 -- `chain-id` is the unique ID that helps tendermint identify which network to connect to. You can find it in the terminal output from the gaiad daemon in the header block , or in the genesis.json file at `~/.basecoind/config/genesis.json` - -Now if we check bobs account, it should have `10000 mycoin`. You can do so by running : - -``` -basecli account cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz -``` - -Now lets send some from bob to charlie. Make sure you send less than bob has, otherwise the transaction will fail: - -``` -basecli send --from=bob --amount=5000mycoin --to=cosmos13wq5mklhn03ljpd4dkph5rflk5a3ssma2ag07q ---sequence=0 --chain-id=test-chain-AE4XQo -``` - -Note how we use the ``--from`` flag to select a different account to send from. - -Lets now try to send from bob back to alice: - -``` -basecli send --from=bob --amount=3000mycoin --to=cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f ---sequence=1 --chain-id=test-chain-AE4XQo -``` - -Notice that the sequence is now 1, since we have already recorded bobs 1st transaction as `sequence 0`. Also note the ``hash`` value in the response in the terminal - this is the hash of the transaction. We can query for the transaction with this command: - -``` -basecli tx -``` - -It will return the details of the transaction hash, such as how many coins were send and to which address, and on what block it occurred. - -That is the basic implementation of basecoin! - - -## Reset the basecoind blockchain and basecli data - -**WARNING:** Running these commands will wipe out any existing -information in both the ``~/.basecli`` and ``~/.basecoind`` directories, -including private keys. This should be no problem considering that basecoin -is just an example, but it is always good to pay extra attention when -you are removing private keys, in any scenario involving a blockchain. - -To remove all the files created and refresh your environment (e.g., if -starting this tutorial again or trying something new), the following -commands are run: - -``` -basecoind unsafe-reset-all -rm -rf ~/.basecoind -rm -rf ~/.basecli -``` - -## Technical Details on how Basecoin Works - -This section describes some of the more technical aspects for what is going on under the hood of Basecoin. - -## Proof - -Even if you don't see it in the UI, the result of every query comes with -a proof. This is a Merkle proof that the result of the query is actually -contained in the state. And the state's Merkle root is contained in a -recent block header. Behind the scenes, ``basecli`` will not only -verify that this state matches the header, but also that the header is -properly signed by the known validator set. It will even update the -validator set as needed, so long as there have not been major changes -and it is secure to do so. So, if you wonder why the query may take a -second... there is a lot of work going on in the background to make sure -even a lying full node can't trick your client. - -## Accounts and Transactions - -For a better understanding of how to further use the tools, it helps to -understand the underlying data structures, so lets look at accounts and transactions. - -### Accounts - -The Basecoin state consists entirely of a set of accounts. Each account -contains an address, a public key, a balance in many different coin denominations, -and a strictly increasing sequence number for replay protection. This -type of account was directly inspired by accounts in Ethereum, and is -unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). - -``` -type BaseAccount struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` - PubKey crypto.PubKey `json:"public_key"` - Sequence int64 `json:"sequence"` -} -``` - -You can also add more fields to accounts, and basecoin actually does so. Basecoin -adds a Name field in order to show how easily the base account structure can be -modified to suit any applications needs. It takes the `auth.BaseAccount` we see above, -and extends it with `Name`. - -``` -type AppAccount struct { - auth.BaseAccount - Name string `json:"name"` -} -``` - -Within accounts, coin balances are stored. Basecoin is a multi-asset cryptocurrency, so each account can have many -different kinds of tokens, which are held in an array. - -``` -type Coins []Coin - -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} -``` - -If you want to add more coins to a blockchain, you can do so manually in -the ``~/.basecoin/genesis.json`` before you start the blockchain for the -first time. - -Accounts are serialized and stored in a Merkle tree under the key -``base/a/
``, where ``
`` is the address of the account. -Typically, the address of the account is the first 20-bytes of the ``sha256`` hash -of the public key, but other formats are acceptable as well, as defined -in the `Tendermint crypto -library `__. The Merkle tree -used in Basecoin is a balanced, binary search tree, which we call an -`IAVL tree `__. - -### Transactions - -Basecoin defines a transaction type, the `SendTx`, which allows tokens -to be sent to other accounts. The `SendTx` takes a list of inputs and -a list of outputs, and transfers all the tokens listed in the inputs -from their corresponding accounts to the accounts listed in the output. -The `SendTx` is structured as follows: -``` -type SendTx struct { - Gas int64 `json:"gas"` - Fee Coin `json:"fee"` - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` -} - -type TxInput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput - Signature []byte `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 -} - -type TxOutput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // -} -``` -Note the `SendTx` includes a field for `Gas` and `Fee`. The -`Gas` limits the total amount of computation that can be done by the -transaction, while the `Fee` refers to the total amount paid in fees. -This is slightly different from Ethereum's concept of `Gas` and -`GasPrice`, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` -and `Fee` are independent, and the `GasPrice` is implicit. - -In Basecoin, the `Fee` is meant to be used by the validators to inform -the ordering of transactions, like in Bitcoin. And the `Gas` is meant -to be used by the application plugin to control its execution. There is -currently no means to pass `Fee` information to the Tendermint -validators, but it will come soon... so this version of Basecoin does -not actually fully implement fees and gas, but it still allows us -to send transactions between accounts. - -Note also that the `PubKey` only needs to be sent for -`Sequence == 0`. After that, it is stored under the account in the -Merkle tree and subsequent transactions can exclude it, using only the -`Address` to refer to the sender. Ethereum does not require public -keys to be sent in transactions as it uses a different elliptic curve -scheme which enables the public key to be derived from the signature -itself. - -Finally, note that the use of multiple inputs and multiple outputs -allows us to send many different types of tokens between many different -accounts at once in an atomic transaction. Thus, the `SendTx` can -serve as a basic unit of decentralized exchange. When using multiple -inputs and outputs, you must make sure that the sum of coins of the -inputs equals the sum of coins of the outputs (no creating money), and -that all accounts that provide inputs have signed the transaction. diff --git a/docs/examples/basecoin/app/app.go b/docs/examples/basecoin/app/app.go deleted file mode 100644 index a469e0b2a44d..000000000000 --- a/docs/examples/basecoin/app/app.go +++ /dev/null @@ -1,190 +0,0 @@ -package app - -import ( - "encoding/json" - "os" - - abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types" - 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/ibc" -) - -const ( - appName = "BasecoinApp" -) - -// default home directories for expected binaries -var ( - DefaultCLIHome = os.ExpandEnv("$HOME/.basecli") - DefaultNodeHome = os.ExpandEnv("$HOME/.basecoind") -) - -// BasecoinApp implements an extended ABCI application. It contains a BaseApp, -// a codec for serialization, KVStore keys for multistore state management, and -// various mappers and keepers to manage getting, setting, and serializing the -// integral app types. -type BasecoinApp struct { - *bam.BaseApp - cdc *codec.Codec - - // keys to access the multistore - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - - // manage getting and setting accounts - accountKeeper auth.AccountKeeper - feeCollectionKeeper auth.FeeCollectionKeeper - bankKeeper bank.Keeper - ibcMapper ibc.Mapper -} - -// NewBasecoinApp returns a reference to a new BasecoinApp given a logger and -// database. Internally, a codec is created along with all the necessary keys. -// In addition, all necessary mappers and keepers are created, routes -// registered, and finally the stores being mounted along with any necessary -// chain initialization. -func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *BasecoinApp { - // create and register app-level codec for TXs and accounts - cdc := MakeCodec() - - // create your application type - var app = &BasecoinApp{ - cdc: cdc, - BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...), - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - } - - // define and attach the mappers and keepers - app.accountKeeper = auth.NewAccountKeeper( - cdc, - app.keyAccount, // target store - func() auth.Account { - return &types.AppAccount{} - }, - ) - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) - app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, ibc.DefaultCodespace) - - // register message routes - app.Router(). - AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)) - - // perform initialization logic - app.SetInitChainer(app.initChainer) - app.SetBeginBlocker(app.BeginBlocker) - app.SetEndBlocker(app.EndBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) - - // mount the multistore and load the latest state - app.MountStores(app.keyMain, app.keyAccount, app.keyIBC) - err := app.LoadLatestVersion(app.keyMain) - if err != nil { - cmn.Exit(err.Error()) - } - - app.Seal() - - return app -} - -// MakeCodec creates a new codec codec and registers all the necessary types -// with the codec. -func MakeCodec() *codec.Codec { - cdc := codec.New() - - codec.RegisterCrypto(cdc) - sdk.RegisterCodec(cdc) - bank.RegisterCodec(cdc) - ibc.RegisterCodec(cdc) - auth.RegisterCodec(cdc) - - // register custom type - cdc.RegisterConcrete(&types.AppAccount{}, "basecoin/Account", nil) - - cdc.Seal() - - return cdc -} - -// BeginBlocker reflects logic to run before any TXs application are processed -// by the application. -func (app *BasecoinApp) BeginBlocker(_ sdk.Context, _ abci.RequestBeginBlock) abci.ResponseBeginBlock { - return abci.ResponseBeginBlock{} -} - -// EndBlocker reflects logic to run after all TXs are processed by the -// application. -func (app *BasecoinApp) EndBlocker(_ sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { - return abci.ResponseEndBlock{} -} - -// initChainer implements the custom application logic that the BaseApp will -// invoke upon initialization. In this case, it will take the application's -// state provided by 'req' and attempt to deserialize said state. The state -// should contain all the genesis accounts. These accounts will be added to the -// application's account mapper. -func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes - - genesisState := new(types.GenesisState) - err := app.cdc.UnmarshalJSON(stateJSON, genesisState) - if err != nil { - // TODO: https://github.com/cosmos/cosmos-sdk/issues/468 - panic(err) - } - - for _, gacc := range genesisState.Accounts { - acc, err := gacc.ToAppAccount() - if err != nil { - // TODO: https://github.com/cosmos/cosmos-sdk/issues/468 - panic(err) - } - - acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx) - app.accountKeeper.SetAccount(ctx, acc) - } - - return abci.ResponseInitChain{} -} - -// ExportAppStateAndValidators implements custom application logic that exposes -// various parts of the application's state and set of validators. An error is -// returned if any step getting the state or set of validators fails. -func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - ctx := app.NewContext(true, abci.Header{}) - accounts := []*types.GenesisAccount{} - - appendAccountsFn := func(acc auth.Account) bool { - account := &types.GenesisAccount{ - Address: acc.GetAddress(), - Coins: acc.GetCoins(), - } - - accounts = append(accounts, account) - return false - } - - app.accountKeeper.IterateAccounts(ctx, appendAccountsFn) - - genState := types.GenesisState{Accounts: accounts} - appState, err = codec.MarshalJSONIndent(app.cdc, genState) - if err != nil { - return nil, nil, err - } - - return appState, validators, err -} diff --git a/docs/examples/basecoin/app/app_test.go b/docs/examples/basecoin/app/app_test.go deleted file mode 100644 index 4dae71194325..000000000000 --- a/docs/examples/basecoin/app/app_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package app - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -func setGenesis(baseApp *BasecoinApp, accounts ...*types.AppAccount) (types.GenesisState, error) { - genAccts := make([]*types.GenesisAccount, len(accounts)) - for i, appAct := range accounts { - genAccts[i] = types.NewGenesisAccount(appAct) - } - - genesisState := types.GenesisState{Accounts: genAccts} - stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genesisState) - if err != nil { - return types.GenesisState{}, err - } - - // initialize and commit the chain - baseApp.InitChain(abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, AppStateBytes: stateBytes, - }) - baseApp.Commit() - - return genesisState, nil -} - -func TestGenesis(t *testing.T) { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - baseApp := NewBasecoinApp(logger, db) - - // construct a pubkey and an address for the test account - pubkey := ed25519.GenPrivKey().PubKey() - addr := sdk.AccAddress(pubkey.Address()) - - // construct some test coins - coins, err := sdk.ParseCoins("77foocoin,99barcoin") - require.Nil(t, err) - - // create an auth.BaseAccount for the given test account and set it's coins - baseAcct := auth.NewBaseAccountWithAddress(addr) - err = baseAcct.SetCoins(coins) - require.Nil(t, err) - - // create a new test AppAccount with the given auth.BaseAccount - appAcct := types.NewAppAccount("foobar", baseAcct) - genState, err := setGenesis(baseApp, appAcct) - require.Nil(t, err) - - // create a context for the BaseApp - ctx := baseApp.BaseApp.NewContext(true, abci.Header{}) - res := baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address) - require.Equal(t, appAcct, res) - - // reload app and ensure the account is still there - baseApp = NewBasecoinApp(logger, db) - - stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genState) - require.Nil(t, err) - - // initialize the chain with the expected genesis state - baseApp.InitChain(abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, AppStateBytes: stateBytes, - }) - - ctx = baseApp.BaseApp.NewContext(true, abci.Header{}) - res = baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address) - require.Equal(t, appAcct, res) -} diff --git a/docs/examples/basecoin/cli_test/cli_test.go b/docs/examples/basecoin/cli_test/cli_test.go deleted file mode 100644 index 7e6a648df35b..000000000000 --- a/docs/examples/basecoin/cli_test/cli_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package clitest - -import ( - "encoding/json" - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/tests" -) - -var ( - basecoindHome = "" - basecliHome = "" -) - -func init() { - basecoindHome, basecliHome = getTestingHomeDirs() -} - -func TestInitStartSequence(t *testing.T) { - os.RemoveAll(basecoindHome) - servAddr, port, err := server.FreeTCPAddr() - require.NoError(t, err) - executeInit(t) - executeStart(t, servAddr, port) -} - -func executeInit(t *testing.T) { - var ( - chainID string - initRes map[string]json.RawMessage - ) - _, stderr := tests.ExecuteT(t, fmt.Sprintf("basecoind --home=%s --home-client=%s init --name=test", basecoindHome, basecliHome), app.DefaultKeyPass) - err := json.Unmarshal([]byte(stderr), &initRes) - require.NoError(t, err) - err = json.Unmarshal(initRes["chain_id"], &chainID) - require.NoError(t, err) -} - -func executeStart(t *testing.T, servAddr, port string) { - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("basecoind start --home=%s --rpc.laddr=%v", basecoindHome, servAddr)) - defer proc.Stop(false) - tests.WaitForTMStart(port) -} - -func getTestingHomeDirs() (string, string) { - tmpDir := os.TempDir() - basecoindHome := fmt.Sprintf("%s%s.test_basecoind", tmpDir, string(os.PathSeparator)) - basecliHome := fmt.Sprintf("%s%s.test_basecli", tmpDir, string(os.PathSeparator)) - return basecoindHome, basecliHome -} diff --git a/docs/examples/basecoin/cmd/basecli/main.go b/docs/examples/basecoin/cmd/basecli/main.go deleted file mode 100644 index fe681a3ad885..000000000000 --- a/docs/examples/basecoin/cmd/basecli/main.go +++ /dev/null @@ -1,127 +0,0 @@ -package main - -import ( - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/client/lcd" - _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" - "github.com/cosmos/cosmos-sdk/client/rpc" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/app" - - "github.com/spf13/cobra" - "github.com/tendermint/tendermint/libs/cli" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/version" - authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" - bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" - ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" - slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" - slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" - stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" - stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" -) - -const ( - storeAcc = "acc" - storeSlashing = "slashing" - storeStake = "stake" -) - -// rootCmd is the entry point for this binary -var ( - rootCmd = &cobra.Command{ - Use: "basecli", - Short: "Basecoin light-client", - } -) - -func main() { - // disable sorting - cobra.EnableCommandSorting = false - - // get the codec - cdc := app.MakeCodec() - - // Setup certain SDK config - config := sdk.GetConfig() - config.SetBech32PrefixForAccount("baseacc", "basepub") - config.SetBech32PrefixForValidator("baseval", "basevalpub") - config.SetBech32PrefixForConsensusNode("basecons", "baseconspub") - config.Seal() - - // TODO: Setup keybase, viper object, etc. to be passed into - // the below functions and eliminate global vars, like we do - // with the cdc. - - // add standard rpc, and tx commands - rootCmd.AddCommand( - rpc.InitClientCommand(), - rpc.StatusCommand(), - client.LineBreak, - tx.SearchTxCmd(cdc), - tx.QueryTxCmd(cdc), - client.LineBreak, - ) - - // add query/post commands (custom to binary) - rootCmd.AddCommand( - stakecmd.GetCmdQueryValidator(storeStake, cdc), - stakecmd.GetCmdQueryValidators(storeStake, cdc), - stakecmd.GetCmdQueryValidatorUnbondingDelegations(storeStake, cdc), - stakecmd.GetCmdQueryValidatorRedelegations(storeStake, cdc), - stakecmd.GetCmdQueryDelegation(storeStake, cdc), - stakecmd.GetCmdQueryDelegations(storeStake, cdc), - stakecmd.GetCmdQueryPool(storeStake, cdc), - stakecmd.GetCmdQueryParams(storeStake, cdc), - stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc), - stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc), - stakecmd.GetCmdQueryRedelegation(storeStake, cdc), - stakecmd.GetCmdQueryRedelegations(storeStake, cdc), - slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc), - stakecmd.GetCmdQueryValidatorDelegations(storeStake, cdc), - authcmd.GetAccountCmd(storeAcc, cdc), - ) - - rootCmd.AddCommand( - bankcmd.SendTxCmd(cdc), - ibccmd.IBCTransferCmd(cdc), - ibccmd.IBCRelayCmd(cdc), - stakecmd.GetCmdCreateValidator(cdc), - stakecmd.GetCmdEditValidator(cdc), - stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(storeStake, cdc), - stakecmd.GetCmdRedelegate(storeStake, cdc), - slashingcmd.GetCmdUnjail(cdc), - ) - - // add proxy, version and key info - rootCmd.AddCommand( - client.LineBreak, - lcd.ServeCommand(cdc, registerRoutes), - keys.Commands(), - client.LineBreak, - version.VersionCmd, - ) - - // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) - err := executor.Execute() - if err != nil { - // Note: Handle with #870 - panic(err) - } -} - -func registerRoutes(rs *lcd.RestServer) { - 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, storeAcc) - bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) - stake.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) - slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) -} diff --git a/docs/examples/basecoin/cmd/basecoind/main.go b/docs/examples/basecoin/cmd/basecoind/main.go deleted file mode 100644 index 9cb246671858..000000000000 --- a/docs/examples/basecoin/cmd/basecoind/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/tendermint/tendermint/p2p" - - "github.com/cosmos/cosmos-sdk/baseapp" - gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/cli" - "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/server" -) - -const ( - flagClientHome = "home-client" -) - -func main() { - cdc := app.MakeCodec() - ctx := server.NewDefaultContext() - - rootCmd := &cobra.Command{ - Use: "basecoind", - Short: "Basecoin Daemon (server)", - PersistentPreRunE: server.PersistentPreRunEFn(ctx), - } - - rootCmd.AddCommand(InitCmd(ctx, cdc)) - - server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) - - // prepare and add flags - rootDir := os.ExpandEnv("$HOME/.basecoind") - executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) - - err := executor.Execute() - if err != nil { - // Note: Handle with #870 - panic(err) - } -} - -// get cmd to initialize all files for tendermint and application -// nolint: errcheck -func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "init", - Short: "Initialize genesis config, priv-validator file, and p2p-node file", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) - chainID := viper.GetString(client.FlagChainID) - if chainID == "" { - chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) - } - - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return err - } - nodeID := string(nodeKey.ID()) - - pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile()) - genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk) - if err != nil { - return err - } - - appState, err := server.SimpleAppGenState( - cdc, tmtypes.GenesisDoc{}, []json.RawMessage{genTx}) - if err != nil { - return err - } - appStateJSON, err := cdc.MarshalJSON(appState) - if err != nil { - return err - } - - toPrint := struct { - ChainID string `json:"chain_id"` - NodeID string `json:"node_id"` - AppMessage json.RawMessage `json:"app_message"` - }{ - chainID, - nodeID, - appMessage, - } - out, err := codec.MarshalJSONIndent(cdc, toPrint) - if err != nil { - return err - } - fmt.Fprintf(os.Stderr, "%s\n", string(out)) - return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID, - []tmtypes.GenesisValidator{validator}, appStateJSON) - }, - } - - cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") - cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().String(client.FlagChainID, "", - "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().String(client.FlagName, "", "validator's moniker") - cmd.MarkFlagRequired(client.FlagName) - return cmd -} - -func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Application { - return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning"))) -} - -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer, _ int64, _ bool) ( - json.RawMessage, []tmtypes.GenesisValidator, error) { - bapp := app.NewBasecoinApp(logger, db) - return bapp.ExportAppStateAndValidators() -} diff --git a/docs/examples/basecoin/types/account.go b/docs/examples/basecoin/types/account.go deleted file mode 100644 index 41b437718041..000000000000 --- a/docs/examples/basecoin/types/account.go +++ /dev/null @@ -1,81 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -var _ auth.Account = (*AppAccount)(nil) - -// AppAccount is a custom extension for this application. It is an example of -// extending auth.BaseAccount with custom fields. It is compatible with the -// stock auth.AccountKeeper, since auth.AccountKeeper uses the flexible go-amino -// library. -type AppAccount struct { - auth.BaseAccount - - Name string `json:"name"` -} - -// nolint -func (acc AppAccount) GetName() string { return acc.Name } -func (acc *AppAccount) SetName(name string) { acc.Name = name } - -// NewAppAccount returns a reference to a new AppAccount given a name and an -// auth.BaseAccount. -func NewAppAccount(name string, baseAcct auth.BaseAccount) *AppAccount { - return &AppAccount{BaseAccount: baseAcct, Name: name} -} - -// GetAccountDecoder returns the AccountDecoder function for the custom -// AppAccount. -func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder { - return func(accBytes []byte) (auth.Account, error) { - if len(accBytes) == 0 { - return nil, sdk.ErrTxDecode("accBytes are empty") - } - - acct := new(AppAccount) - err := cdc.UnmarshalBinaryBare(accBytes, &acct) - if err != nil { - panic(err) - } - - return acct, err - } -} - -// GenesisState reflects the genesis state of the application. -type GenesisState struct { - Accounts []*GenesisAccount `json:"accounts"` -} - -// GenesisAccount reflects a genesis account the application expects in it's -// genesis state. -type GenesisAccount struct { - Name string `json:"name"` - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` -} - -// NewGenesisAccount returns a reference to a new GenesisAccount given an -// AppAccount. -func NewGenesisAccount(aa *AppAccount) *GenesisAccount { - return &GenesisAccount{ - Name: aa.Name, - Address: aa.Address, - Coins: aa.Coins.Sort(), - } -} - -// ToAppAccount converts a GenesisAccount to an AppAccount. -func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) { - return &AppAccount{ - Name: ga.Name, - BaseAccount: auth.BaseAccount{ - Address: ga.Address, - Coins: ga.Coins.Sort(), - }, - }, nil -} diff --git a/docs/examples/democoin/app/app.go b/docs/examples/democoin/app/app.go deleted file mode 100644 index 99c1e3ed932c..000000000000 --- a/docs/examples/democoin/app/app.go +++ /dev/null @@ -1,197 +0,0 @@ -package app - -import ( - "encoding/json" - "os" - - abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - "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" - "github.com/cosmos/cosmos-sdk/x/ibc" - - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/types" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/sketchy" -) - -const ( - appName = "DemocoinApp" -) - -// default home directories for expected binaries -var ( - DefaultCLIHome = os.ExpandEnv("$HOME/.democli") - DefaultNodeHome = os.ExpandEnv("$HOME/.democoind") -) - -// Extended ABCI application -type DemocoinApp struct { - *bam.BaseApp - cdc *codec.Codec - - // keys to access the substores - capKeyMainStore *sdk.KVStoreKey - capKeyAccountStore *sdk.KVStoreKey - capKeyPowStore *sdk.KVStoreKey - capKeyIBCStore *sdk.KVStoreKey - capKeyStakingStore *sdk.KVStoreKey - - // keepers - feeCollectionKeeper auth.FeeCollectionKeeper - bankKeeper bank.Keeper - coolKeeper cool.Keeper - powKeeper pow.Keeper - ibcMapper ibc.Mapper - stakeKeeper simplestake.Keeper - - // Manage getting and setting accounts - accountKeeper auth.AccountKeeper -} - -func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { - - // Create app-level codec for txs and accounts. - var cdc = MakeCodec() - - // Create your application object. - var app = &DemocoinApp{ - BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc)), - cdc: cdc, - capKeyMainStore: sdk.NewKVStoreKey("main"), - capKeyAccountStore: sdk.NewKVStoreKey("acc"), - capKeyPowStore: sdk.NewKVStoreKey("pow"), - capKeyIBCStore: sdk.NewKVStoreKey("ibc"), - capKeyStakingStore: sdk.NewKVStoreKey("stake"), - } - - // Define the accountKeeper. - app.accountKeeper = auth.NewAccountKeeper( - cdc, - app.capKeyAccountStore, // target store - types.ProtoAppAccount, // prototype - ) - - // Add handlers. - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) - app.coolKeeper = cool.NewKeeper(app.capKeyMainStore, app.bankKeeper, cool.DefaultCodespace) - app.powKeeper = pow.NewKeeper(app.capKeyPowStore, pow.NewConfig("pow", int64(1)), app.bankKeeper, pow.DefaultCodespace) - app.ibcMapper = ibc.NewMapper(app.cdc, app.capKeyIBCStore, ibc.DefaultCodespace) - app.stakeKeeper = simplestake.NewKeeper(app.capKeyStakingStore, app.bankKeeper, simplestake.DefaultCodespace) - app.Router(). - AddRoute("bank", bank.NewHandler(app.bankKeeper)). - AddRoute("cool", cool.NewHandler(app.coolKeeper)). - AddRoute("pow", app.powKeeper.Handler). - AddRoute("sketchy", sketchy.NewHandler()). - AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)). - AddRoute("simplestake", simplestake.NewHandler(app.stakeKeeper)) - - // Initialize BaseApp. - app.SetInitChainer(app.initChainerFn(app.coolKeeper, app.powKeeper)) - app.MountStores(app.capKeyMainStore, app.capKeyAccountStore, app.capKeyPowStore, app.capKeyIBCStore, app.capKeyStakingStore) - app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) - err := app.LoadLatestVersion(app.capKeyMainStore) - if err != nil { - cmn.Exit(err.Error()) - } - - app.Seal() - - return app -} - -// custom tx codec -func MakeCodec() *codec.Codec { - var cdc = codec.New() - codec.RegisterCrypto(cdc) // Register crypto. - sdk.RegisterCodec(cdc) // Register Msgs - cool.RegisterCodec(cdc) - pow.RegisterCodec(cdc) - bank.RegisterCodec(cdc) - ibc.RegisterCodec(cdc) - simplestake.RegisterCodec(cdc) - - // Register AppAccount - cdc.RegisterInterface((*auth.Account)(nil), nil) - cdc.RegisterConcrete(&types.AppAccount{}, "democoin/Account", nil) - - cdc.Seal() - - return cdc -} - -// custom logic for democoin initialization -// nolint: unparam -func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes - - genesisState := new(types.GenesisState) - err := app.cdc.UnmarshalJSON(stateJSON, genesisState) - if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") - } - - for _, gacc := range genesisState.Accounts { - acc, err := gacc.ToAppAccount() - if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") - } - app.accountKeeper.SetAccount(ctx, acc) - } - - // Application specific genesis handling - err = cool.InitGenesis(ctx, app.coolKeeper, genesisState.CoolGenesis) - if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") - } - - err = pow.InitGenesis(ctx, app.powKeeper, genesisState.POWGenesis) - if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") - } - - return abci.ResponseInitChain{} - } -} - -// Custom logic for state export -func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - ctx := app.NewContext(true, abci.Header{}) - - // iterate to get the accounts - accounts := []*types.GenesisAccount{} - appendAccount := func(acc auth.Account) (stop bool) { - account := &types.GenesisAccount{ - Address: acc.GetAddress(), - Coins: acc.GetCoins(), - } - accounts = append(accounts, account) - return false - } - app.accountKeeper.IterateAccounts(ctx, appendAccount) - - genState := types.GenesisState{ - Accounts: accounts, - POWGenesis: pow.ExportGenesis(ctx, app.powKeeper), - CoolGenesis: cool.ExportGenesis(ctx, app.coolKeeper), - } - appState, err = codec.MarshalJSONIndent(app.cdc, genState) - if err != nil { - return nil, nil, err - } - return appState, validators, nil -} diff --git a/docs/examples/democoin/app/app_test.go b/docs/examples/democoin/app/app_test.go deleted file mode 100644 index 200103466ac2..000000000000 --- a/docs/examples/democoin/app/app_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package app - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/types" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -func setGenesis(bapp *DemocoinApp, trend string, accs ...auth.BaseAccount) error { - genaccs := make([]*types.GenesisAccount, len(accs)) - for i, acc := range accs { - genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"}) - } - - genesisState := types.GenesisState{ - Accounts: genaccs, - CoolGenesis: cool.Genesis{trend}, - } - - stateBytes, err := codec.MarshalJSONIndent(bapp.cdc, genesisState) - if err != nil { - return err - } - - // Initialize the chain - vals := []abci.ValidatorUpdate{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - return nil -} - -func TestGenesis(t *testing.T) { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - bapp := NewDemocoinApp(logger, db) - - // Construct some genesis bytes to reflect democoin/types/AppAccount - pk := ed25519.GenPrivKey().PubKey() - addr := sdk.AccAddress(pk.Address()) - coins, err := sdk.ParseCoins("77foocoin,99barcoin") - require.Nil(t, err) - baseAcc := auth.BaseAccount{ - Address: addr, - Coins: coins, - } - acc := &types.AppAccount{baseAcc, "foobart"} - - err = setGenesis(bapp, "ice-cold", baseAcc) - require.Nil(t, err) - // A checkTx context - ctx := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountKeeper.GetAccount(ctx, baseAcc.Address) - require.Equal(t, acc, res1) - - // reload app and ensure the account is still there - bapp = NewDemocoinApp(logger, db) - bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) - ctx = bapp.BaseApp.NewContext(true, abci.Header{}) - res1 = bapp.accountKeeper.GetAccount(ctx, baseAcc.Address) - require.Equal(t, acc, res1) -} diff --git a/docs/examples/democoin/cli_test/cli_test.go b/docs/examples/democoin/cli_test/cli_test.go deleted file mode 100644 index 1aa9d94acb6f..000000000000 --- a/docs/examples/democoin/cli_test/cli_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package clitest - -import ( - "encoding/json" - "fmt" - "os" - "testing" - - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/tests" -) - -var ( - democoindHome = "" - democliHome = "" -) - -func init() { - democoindHome, democliHome = getTestingHomeDirs() -} - -func TestInitStartSequence(t *testing.T) { - os.RemoveAll(democoindHome) - servAddr, port, err := server.FreeTCPAddr() - require.NoError(t, err) - executeInit(t) - executeStart(t, servAddr, port) -} - -func executeInit(t *testing.T) { - var ( - chainID string - initRes map[string]json.RawMessage - ) - _, stderr := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s --home-client=%s init --name=test", democoindHome, democliHome), app.DefaultKeyPass) - err := json.Unmarshal([]byte(stderr), &initRes) - require.NoError(t, err) - err = json.Unmarshal(initRes["chain_id"], &chainID) - require.NoError(t, err) -} - -func executeStart(t *testing.T, servAddr, port string) { - proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("democoind start --home=%s --rpc.laddr=%v", democoindHome, servAddr)) - defer proc.Stop(false) - tests.WaitForTMStart(port) -} - -func getTestingHomeDirs() (string, string) { - tmpDir := os.TempDir() - democoindHome := fmt.Sprintf("%s%s.test_democoind", tmpDir, string(os.PathSeparator)) - democliHome := fmt.Sprintf("%s%s.test_democli", tmpDir, string(os.PathSeparator)) - return democoindHome, democliHome -} diff --git a/docs/examples/democoin/cmd/democli/main.go b/docs/examples/democoin/cmd/democli/main.go deleted file mode 100644 index d3d0e5ff0808..000000000000 --- a/docs/examples/democoin/cmd/democli/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/tendermint/tendermint/libs/cli" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/client/lcd" - "github.com/cosmos/cosmos-sdk/client/rpc" - "github.com/cosmos/cosmos-sdk/client/tx" - - "github.com/cosmos/cosmos-sdk/version" - authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" - bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" - - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/app" - coolcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool/client/cli" - powcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow/client/cli" - simplestakingcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake/client/cli" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// rootCmd is the entry point for this binary -var ( - rootCmd = &cobra.Command{ - Use: "democli", - Short: "Democoin light-client", - } - storeAcc = "acc" -) - -func main() { - // disable sorting - cobra.EnableCommandSorting = false - - // get the codec - cdc := app.MakeCodec() - - // Setup certain SDK config - config := sdk.GetConfig() - config.SetBech32PrefixForAccount("demoacc", "demopub") - config.SetBech32PrefixForValidator("demoval", "demovalpub") - config.SetBech32PrefixForConsensusNode("democons", "democonspub") - config.Seal() - - // TODO: setup keybase, viper object, etc. to be passed into - // the below functions and eliminate global vars, like we do - // with the cdc - - // add standard rpc, and tx commands - - rootCmd.AddCommand( - rpc.InitClientCommand(), - rpc.StatusCommand(), - client.LineBreak, - tx.SearchTxCmd(cdc), - tx.QueryTxCmd(cdc), - client.LineBreak, - ) - - // add query/post commands (custom to binary) - // start with commands common to basecoin - rootCmd.AddCommand( - authcmd.GetAccountCmd(storeAcc, cdc), - ) - rootCmd.AddCommand( - bankcmd.SendTxCmd(cdc), - ) - rootCmd.AddCommand( - client.PostCommands( - simplestakingcmd.BondTxCmd(cdc), - )...) - rootCmd.AddCommand( - client.PostCommands( - simplestakingcmd.UnbondTxCmd(cdc), - )...) - // and now democoin specific commands - rootCmd.AddCommand( - client.PostCommands( - coolcmd.QuizTxCmd(cdc), - coolcmd.SetTrendTxCmd(cdc), - powcmd.MineCmd(cdc), - )...) - - // add proxy, version and key info - rootCmd.AddCommand( - client.LineBreak, - lcd.ServeCommand(cdc, registerRoutes), - keys.Commands(), - client.LineBreak, - version.VersionCmd, - ) - - // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) - err := executor.Execute() - if err != nil { - // handle with #870 - panic(err) - } -} - -func registerRoutes(rs *lcd.RestServer) { - 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, storeAcc) - bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) -} diff --git a/docs/examples/democoin/cmd/democoind/main.go b/docs/examples/democoin/cmd/democoind/main.go deleted file mode 100644 index f0553ee5d3e7..000000000000 --- a/docs/examples/democoin/cmd/democoind/main.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" - - "github.com/cosmos/cosmos-sdk/client" - - "github.com/spf13/cobra" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/cli" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" - - gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/app" - "github.com/cosmos/cosmos-sdk/server" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - flagClientHome = "home-client" -) - -// coolGenAppParams sets up the app_state and appends the cool app state -func CoolAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( - appState json.RawMessage, err error) { - appState, err = server.SimpleAppGenState(cdc, tmtypes.GenesisDoc{}, appGenTxs) - if err != nil { - return - } - - key := "cool" - value := json.RawMessage(`{ - "trend": "ice-cold" - }`) - - appState, err = server.InsertKeyJSON(cdc, appState, key, value) - if err != nil { - return - } - - key = "pow" - value = json.RawMessage(`{ - "difficulty": "1", - "count": "0" - }`) - - appState, err = server.InsertKeyJSON(cdc, appState, key, value) - return -} - -// get cmd to initialize all files for tendermint and application -// nolint: errcheck -func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "init", - Short: "Initialize genesis config, priv-validator file, and p2p-node file", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) - chainID := viper.GetString(client.FlagChainID) - if chainID == "" { - chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) - } - - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return err - } - nodeID := string(nodeKey.ID()) - - pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile()) - genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk) - if err != nil { - return err - } - - appState, err := CoolAppGenState(cdc, tmtypes.GenesisDoc{}, - []json.RawMessage{genTx}) - if err != nil { - return err - } - appStateJSON, err := cdc.MarshalJSON(appState) - if err != nil { - return err - } - - toPrint := struct { - ChainID string `json:"chain_id"` - NodeID string `json:"node_id"` - AppMessage json.RawMessage `json:"app_message"` - }{ - chainID, - nodeID, - appMessage, - } - out, err := codec.MarshalJSONIndent(cdc, toPrint) - if err != nil { - return err - } - fmt.Fprintf(os.Stderr, "%s\n", string(out)) - return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID, - []tmtypes.GenesisValidator{validator}, appStateJSON) - }, - } - - cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") - cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") - cmd.Flags().String(client.FlagChainID, "", - "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().String(client.FlagName, "", "validator's moniker") - cmd.MarkFlagRequired(client.FlagName) - return cmd -} - -func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application { - return app.NewDemocoinApp(logger, db) -} - -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer, _ int64, _ bool) ( - json.RawMessage, []tmtypes.GenesisValidator, error) { - dapp := app.NewDemocoinApp(logger, db) - return dapp.ExportAppStateAndValidators() -} - -func main() { - cdc := app.MakeCodec() - - // Setup certain SDK config - config := sdk.GetConfig() - config.SetBech32PrefixForAccount("demoacc", "demopub") - config.SetBech32PrefixForValidator("demoval", "demovalpub") - config.SetBech32PrefixForConsensusNode("democons", "democonspub") - config.Seal() - - ctx := server.NewDefaultContext() - - rootCmd := &cobra.Command{ - Use: "democoind", - Short: "Democoin Daemon (server)", - PersistentPreRunE: server.PersistentPreRunEFn(ctx), - } - - rootCmd.AddCommand(InitCmd(ctx, cdc)) - rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc)) - - server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) - - // prepare and add flags - rootDir := os.ExpandEnv("$HOME/.democoind") - executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) - err := executor.Execute() - if err != nil { - // handle with #870 - panic(err) - } -} diff --git a/docs/examples/democoin/mock/validator.go b/docs/examples/democoin/mock/validator.go deleted file mode 100644 index ee8e497cb238..000000000000 --- a/docs/examples/democoin/mock/validator.go +++ /dev/null @@ -1,163 +0,0 @@ -package mock - -import ( - "bytes" - - "github.com/tendermint/tendermint/crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Validator implements sdk.Validator -type Validator struct { - Address sdk.ValAddress - Power sdk.Dec -} - -// Implements sdk.Validator -func (v Validator) GetStatus() sdk.BondStatus { - return sdk.Bonded -} - -// Implements sdk.Validator -func (v Validator) GetOperator() sdk.ValAddress { - return v.Address -} - -// Implements sdk.Validator -func (v Validator) GetConsPubKey() crypto.PubKey { - return nil -} - -// Implements sdk.Validator -func (v Validator) GetConsAddr() sdk.ConsAddress { - return nil -} - -// Implements sdk.Validator -func (v Validator) GetTokens() sdk.Dec { - return sdk.ZeroDec() -} - -// Implements sdk.Validator -func (v Validator) GetPower() sdk.Dec { - return v.Power -} - -// Implements sdk.Validator -func (v Validator) GetDelegatorShares() sdk.Dec { - return sdk.ZeroDec() -} - -// Implements sdk.Validator -func (v Validator) GetCommission() sdk.Dec { - return sdk.ZeroDec() -} - -// Implements sdk.Validator -func (v Validator) GetJailed() bool { - return false -} - -// Implements sdk.Validator -func (v Validator) GetBondHeight() int64 { - return 0 -} - -// Implements sdk.Validator -func (v Validator) GetMoniker() string { - return "" -} - -// Implements sdk.Validator -type ValidatorSet struct { - Validators []Validator -} - -// IterateValidators implements sdk.ValidatorSet -func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { - for i, val := range vs.Validators { - if fn(int64(i), val) { - break - } - } -} - -// IterateBondedValidatorsByPower implements sdk.ValidatorSet -func (vs *ValidatorSet) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { - vs.IterateValidators(ctx, fn) -} - -// IterateLastValidators implements sdk.ValidatorSet -func (vs *ValidatorSet) IterateLastValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { - vs.IterateValidators(ctx, fn) -} - -// Validator implements sdk.ValidatorSet -func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) sdk.Validator { - for _, val := range vs.Validators { - if bytes.Equal(val.Address.Bytes(), addr.Bytes()) { - return val - } - } - return nil -} - -// ValidatorByPubKey implements sdk.ValidatorSet -func (vs *ValidatorSet) ValidatorByConsPubKey(_ sdk.Context, _ crypto.PubKey) sdk.Validator { - panic("not implemented") -} - -// ValidatorByPubKey implements sdk.ValidatorSet -func (vs *ValidatorSet) ValidatorByConsAddr(_ sdk.Context, _ sdk.ConsAddress) sdk.Validator { - panic("not implemented") -} - -// TotalPower implements sdk.ValidatorSet -func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Dec { - res := sdk.ZeroDec() - for _, val := range vs.Validators { - res = res.Add(val.Power) - } - return res -} - -// Helper function for adding new validator -func (vs *ValidatorSet) AddValidator(val Validator) { - vs.Validators = append(vs.Validators, val) -} - -// Helper function for removing exsting validator -func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) { - pos := -1 - for i, val := range vs.Validators { - if bytes.Equal(val.Address, addr) { - pos = i - break - } - } - if pos == -1 { - return - } - vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...) -} - -// Implements sdk.ValidatorSet -func (vs *ValidatorSet) Slash(_ sdk.Context, _ sdk.ConsAddress, _ int64, _ int64, _ sdk.Dec) { - panic("not implemented") -} - -// Implements sdk.ValidatorSet -func (vs *ValidatorSet) Jail(_ sdk.Context, _ sdk.ConsAddress) { - panic("not implemented") -} - -// Implements sdk.ValidatorSet -func (vs *ValidatorSet) Unjail(_ sdk.Context, _ sdk.ConsAddress) { - panic("not implemented") -} - -// Implements sdk.ValidatorSet -func (vs *ValidatorSet) Delegation(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) sdk.Delegation { - panic("not implemented") -} diff --git a/docs/examples/democoin/types/account.go b/docs/examples/democoin/types/account.go deleted file mode 100644 index 341f6cfc834c..000000000000 --- a/docs/examples/democoin/types/account.go +++ /dev/null @@ -1,82 +0,0 @@ -package types - -import ( - "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/docs/examples/democoin/x/cool" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow" -) - -var _ auth.Account = (*AppAccount)(nil) - -// Custom extensions for this application. This is just an example of -// extending auth.BaseAccount with custom fields. -// -// This is compatible with the stock auth.AccountStore, since -// auth.AccountStore uses the flexible go-amino library. -type AppAccount struct { - auth.BaseAccount - Name string `json:"name"` -} - -// Constructor for AppAccount -func ProtoAppAccount() auth.Account { - return &AppAccount{} -} - -// nolint -func (acc AppAccount) GetName() string { return acc.Name } -func (acc *AppAccount) SetName(name string) { acc.Name = name } - -// Get the AccountDecoder function for the custom AppAccount -func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder { - return func(accBytes []byte) (res auth.Account, err error) { - if len(accBytes) == 0 { - return nil, sdk.ErrTxDecode("accBytes are empty") - } - acct := new(AppAccount) - err = cdc.UnmarshalBinaryBare(accBytes, &acct) - if err != nil { - panic(err) - } - return acct, err - } -} - -//___________________________________________________________________________________ - -// State to Unmarshal -type GenesisState struct { - Accounts []*GenesisAccount `json:"accounts"` - POWGenesis pow.Genesis `json:"pow"` - CoolGenesis cool.Genesis `json:"cool"` -} - -// GenesisAccount doesn't need pubkey or sequence -type GenesisAccount struct { - Name string `json:"name"` - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` -} - -func NewGenesisAccount(aa *AppAccount) *GenesisAccount { - return &GenesisAccount{ - Name: aa.Name, - Address: aa.Address, - Coins: aa.Coins.Sort(), - } -} - -// convert GenesisAccount to AppAccount -func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) { - baseAcc := auth.BaseAccount{ - Address: ga.Address, - Coins: ga.Coins.Sort(), - } - return &AppAccount{ - BaseAccount: baseAcc, - Name: ga.Name, - }, nil -} diff --git a/docs/examples/democoin/x/assoc/validator_set.go b/docs/examples/democoin/x/assoc/validator_set.go deleted file mode 100644 index 69b35501f317..000000000000 --- a/docs/examples/democoin/x/assoc/validator_set.go +++ /dev/null @@ -1,107 +0,0 @@ -package assoc - -import ( - "bytes" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// ValidatorSet defines -type ValidatorSet struct { - sdk.ValidatorSet - - store sdk.KVStore - cdc *codec.Codec - - maxAssoc int - addrLen int -} - -var _ sdk.ValidatorSet = ValidatorSet{} - -// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet -func NewValidatorSet(cdc *codec.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { - if maxAssoc < 0 || addrLen < 0 { - panic("Cannot use negative integer for NewValidatorSet") - } - return ValidatorSet{ - ValidatorSet: valset, - - store: store, - cdc: cdc, - - maxAssoc: maxAssoc, - addrLen: addrLen, - } -} - -// Implements sdk.ValidatorSet -func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) (res sdk.Validator) { - base := valset.store.Get(GetBaseKey(addr)) - res = valset.ValidatorSet.Validator(ctx, base) - if res == nil { - res = valset.ValidatorSet.Validator(ctx, addr) - } - return -} - -// GetBaseKey :: sdk.ValAddress -> sdk.ValAddress -func GetBaseKey(addr sdk.ValAddress) []byte { - return append([]byte{0x00}, addr...) -} - -// GetAssocPrefix :: sdk.ValAddress -> (sdk.ValAddress -> byte) -func GetAssocPrefix(base sdk.ValAddress) []byte { - return append([]byte{0x01}, base...) -} - -// GetAssocKey :: (sdk.ValAddress, sdk.ValAddress) -> byte -func GetAssocKey(base sdk.ValAddress, assoc sdk.ValAddress) []byte { - return append(append([]byte{0x01}, base...), assoc...) -} - -// Associate associates new address with validator address -// nolint: unparam -func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool { - if len(base) != valset.addrLen || len(assoc) != valset.addrLen { - return false - } - // If someone already owns the associated address - if valset.store.Get(GetBaseKey(assoc)) != nil { - return false - } - valset.store.Set(GetBaseKey(assoc), base) - valset.store.Set(GetAssocKey(base, assoc), []byte{0x00}) - return true -} - -// Dissociate removes association between addresses -// nolint: unparam -func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool { - if len(base) != valset.addrLen || len(assoc) != valset.addrLen { - return false - } - // No associated address found for given validator - if !bytes.Equal(valset.store.Get(GetBaseKey(assoc)), base) { - return false - } - valset.store.Delete(GetBaseKey(assoc)) - valset.store.Delete(GetAssocKey(base, assoc)) - return true -} - -// Associations returns all associated addresses with a validator -// nolint: unparam -func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.ValAddress) (res []sdk.ValAddress) { - res = make([]sdk.ValAddress, valset.maxAssoc) - iter := sdk.KVStorePrefixIterator(valset.store, GetAssocPrefix(base)) - defer iter.Close() - i := 0 - for ; iter.Valid(); iter.Next() { - key := iter.Key() - res[i] = key[len(key)-valset.addrLen:] - i++ - } - return res[:i] -} diff --git a/docs/examples/democoin/x/assoc/validator_set_test.go b/docs/examples/democoin/x/assoc/validator_set_test.go deleted file mode 100644 index 66f03c840324..000000000000 --- a/docs/examples/democoin/x/assoc/validator_set_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package assoc - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func defaultContext(key sdk.StoreKey) sdk.Context { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil) - return ctx -} - -func TestValidatorSet(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx := defaultContext(key) - - addr1 := []byte("addr1") - addr2 := []byte("addr2") - - base := &mock.ValidatorSet{[]mock.Validator{ - {addr1, sdk.NewDec(1)}, - {addr2, sdk.NewDec(2)}, - }} - - valset := NewValidatorSet(codec.New(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5) - - require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) - require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) - - assoc1 := []byte("asso1") - assoc2 := []byte("asso2") - - require.True(t, valset.Associate(ctx, addr1, assoc1)) - require.True(t, valset.Associate(ctx, addr2, assoc2)) - - require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1)) - require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2)) - - require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) - require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) - - assocs := valset.Associations(ctx, addr1) - require.Equal(t, 1, len(assocs)) - require.True(t, bytes.Equal(assoc1, assocs[0])) - - require.False(t, valset.Associate(ctx, addr1, assoc2)) - require.False(t, valset.Associate(ctx, addr2, assoc1)) - - valset.Dissociate(ctx, addr1, assoc1) - valset.Dissociate(ctx, addr2, assoc2) - - require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) - require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) - - require.Nil(t, valset.Validator(ctx, assoc1)) - require.Nil(t, valset.Validator(ctx, assoc2)) -} diff --git a/docs/examples/democoin/x/cool/app_test.go b/docs/examples/democoin/x/cool/app_test.go deleted file mode 100644 index 7e9e29f2452d..000000000000 --- a/docs/examples/democoin/x/cool/app_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package cool - -import ( - "testing" - - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - bank "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" -) - -var ( - priv1 = ed25519.GenPrivKey() - pubKey = priv1.PubKey() - addr1 = sdk.AccAddress(pubKey.Address()) - - quizMsg1 = MsgQuiz{ - Sender: addr1, - CoolAnswer: "icecold", - } - - quizMsg2 = MsgQuiz{ - Sender: addr1, - CoolAnswer: "badvibesonly", - } - - setTrendMsg1 = MsgSetTrend{ - Sender: addr1, - Cool: "icecold", - } - - setTrendMsg2 = MsgSetTrend{ - Sender: addr1, - Cool: "badvibesonly", - } - - setTrendMsg3 = MsgSetTrend{ - Sender: addr1, - Cool: "warmandkind", - } -) - -// initialize the mock application for this module -func getMockApp(t *testing.T) *mock.App { - mapp := mock.NewApp() - - RegisterCodec(mapp.Cdc) - keyCool := sdk.NewKVStoreKey("cool") - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) - keeper := NewKeeper(keyCool, bankKeeper, DefaultCodespace) - mapp.Router().AddRoute("cool", NewHandler(keeper)) - - mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold")) - - require.NoError(t, mapp.CompleteSetup(keyCool)) - return mapp -} - -// overwrite the mock init chainer -func getInitChainer(mapp *mock.App, keeper Keeper, newTrend string) sdk.InitChainer { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - mapp.InitChainer(ctx, req) - keeper.setTrend(ctx, newTrend) - - return abci.ResponseInitChain{} - } -} - -func TestMsgQuiz(t *testing.T) { - mapp := getMockApp(t) - - // Construct genesis state - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: nil, - } - accs := []auth.Account{acc1} - - // Initialize the chain (nil) - mock.SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) - require.Equal(t, acc1, res1) - - // Set the trend, submit a really cool quiz and check for reward - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []uint64{0}, []uint64{0}, true, true, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []uint64{0}, []uint64{1}, true, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("icecold", sdk.NewInt(69))}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []uint64{0}, []uint64{2}, false, false, priv1) // result without reward - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("icecold", sdk.NewInt(69))}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []uint64{0}, []uint64{3}, true, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("icecold", sdk.NewInt(138))}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []uint64{0}, []uint64{4}, true, true, priv1) // reset the trend - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []uint64{0}, []uint64{5}, false, false, priv1) // the same answer will nolonger do! - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("icecold", sdk.NewInt(138))}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []uint64{0}, []uint64{6}, true, true, priv1) // earlier answer now relevant again - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("badvibesonly", sdk.NewInt(69)), sdk.NewCoin("icecold", sdk.NewInt(138))}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []uint64{0}, []uint64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool -} diff --git a/docs/examples/democoin/x/cool/client/cli/tx.go b/docs/examples/democoin/x/cool/client/cli/tx.go deleted file mode 100644 index 637e00b8c603..000000000000 --- a/docs/examples/democoin/x/cool/client/cli/tx.go +++ /dev/null @@ -1,60 +0,0 @@ -package cli - -import ( - "github.com/spf13/cobra" - - "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/docs/examples/democoin/x/cool" - sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -// QuizTxCmd invokes the coolness quiz transaction. -func QuizTxCmd(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "cool [answer]", - Short: "What's cooler than being cool?", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(cdc) - - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - msg := cool.NewMsgQuiz(from, args[0]) - - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } -} - -// SetTrendTxCmd sends a new cool trend transaction. -func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "setcool [answer]", - Short: "You're so cool, tell us what is cool!", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(cdc) - - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - msg := cool.NewMsgSetTrend(from, args[0]) - - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } -} diff --git a/docs/examples/democoin/x/cool/codec.go b/docs/examples/democoin/x/cool/codec.go deleted file mode 100644 index 491c006177d1..000000000000 --- a/docs/examples/democoin/x/cool/codec.go +++ /dev/null @@ -1,11 +0,0 @@ -package cool - -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - -// Register concrete types on codec codec -func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgQuiz{}, "cool/Quiz", nil) - cdc.RegisterConcrete(MsgSetTrend{}, "cool/SetTrend", nil) -} diff --git a/docs/examples/democoin/x/cool/errors.go b/docs/examples/democoin/x/cool/errors.go deleted file mode 100644 index e31d664708df..000000000000 --- a/docs/examples/democoin/x/cool/errors.go +++ /dev/null @@ -1,20 +0,0 @@ -package cool - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Cool errors reserve 400 ~ 499. -const ( - DefaultCodespace sdk.CodespaceType = "cool" - - // Cool module reserves error 400-499 lawl - CodeIncorrectCoolAnswer sdk.CodeType = 400 -) - -// ErrIncorrectCoolAnswer - Error returned upon an incorrect guess -func ErrIncorrectCoolAnswer(codespace sdk.CodespaceType, answer string) sdk.Error { - return sdk.NewError(codespace, CodeIncorrectCoolAnswer, fmt.Sprintf("incorrect cool answer: %v", answer)) -} diff --git a/docs/examples/democoin/x/cool/handler.go b/docs/examples/democoin/x/cool/handler.go deleted file mode 100644 index 98da59690adb..000000000000 --- a/docs/examples/democoin/x/cool/handler.go +++ /dev/null @@ -1,57 +0,0 @@ -package cool - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// This is just an example to demonstrate a functional custom module -// with full feature set functionality. -// -// /$$$$$$$ /$$$$$$ /$$$$$$ /$$ -// /$$_____/ /$$__ $$ /$$__ $$| $$ -//| $$ | $$ \ $$| $$ \ $$| $$ -//| $$ | $$ | $$| $$ | $$| $$ -//| $$$$$$$| $$$$$$/| $$$$$$/| $$$$$$$ -// \_______/ \______/ \______/ |______/ - -// NewHandler returns a handler for "cool" type messages. -func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgSetTrend: - return handleMsgSetTrend(ctx, k, msg) - case MsgQuiz: - return handleMsgQuiz(ctx, k, msg) - default: - errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Type()) - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - -// Handle MsgQuiz This is the engine of your module -func handleMsgSetTrend(ctx sdk.Context, k Keeper, msg MsgSetTrend) sdk.Result { - k.setTrend(ctx, msg.Cool) - return sdk.Result{} -} - -// Handle MsgQuiz This is the engine of your module -func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result { - - correct := k.CheckTrend(ctx, msg.CoolAnswer) - - if !correct { - return ErrIncorrectCoolAnswer(k.codespace, msg.CoolAnswer).Result() - } - - bonusCoins := sdk.Coins{sdk.NewInt64Coin(msg.CoolAnswer, 69)} - - _, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) - if err != nil { - return err.Result() - } - - return sdk.Result{} -} diff --git a/docs/examples/democoin/x/cool/keeper.go b/docs/examples/democoin/x/cool/keeper.go deleted file mode 100644 index 9f46b0209e8d..000000000000 --- a/docs/examples/democoin/x/cool/keeper.go +++ /dev/null @@ -1,56 +0,0 @@ -package cool - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" -) - -// Keeper - handlers sets/gets of custom variables for your module -type Keeper struct { - ck bank.Keeper - - storeKey sdk.StoreKey // The (unexposed) key used to access the store from the Context. - - codespace sdk.CodespaceType -} - -// NewKeeper - Returns the Keeper -func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper { - return Keeper{bankKeeper, key, codespace} -} - -// Key to knowing the trend on the streets! -var trendKey = []byte("TrendKey") - -// GetTrend - returns the current cool trend -func (k Keeper) GetTrend(ctx sdk.Context) string { - store := ctx.KVStore(k.storeKey) - bz := store.Get(trendKey) - return string(bz) -} - -// Implements sdk.AccountKeeper. -func (k Keeper) setTrend(ctx sdk.Context, newTrend string) { - store := ctx.KVStore(k.storeKey) - store.Set(trendKey, []byte(newTrend)) -} - -// CheckTrend - Returns true or false based on whether guessedTrend is currently cool or not -func (k Keeper) CheckTrend(ctx sdk.Context, guessedTrend string) bool { - if guessedTrend == k.GetTrend(ctx) { - return true - } - return false -} - -// InitGenesis - store the genesis trend -func InitGenesis(ctx sdk.Context, k Keeper, data Genesis) error { - k.setTrend(ctx, data.Trend) - return nil -} - -// ExportGenesis - output the genesis trend -func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { - trend := k.GetTrend(ctx) - return Genesis{trend} -} diff --git a/docs/examples/democoin/x/cool/keeper_test.go b/docs/examples/democoin/x/cool/keeper_test.go deleted file mode 100644 index 904681382c2a..000000000000 --- a/docs/examples/democoin/x/cool/keeper_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package cool - -import ( - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth" - bank "github.com/cosmos/cosmos-sdk/x/bank" -) - -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { - db := dbm.NewMemDB() - capKey := sdk.NewKVStoreKey("capkey") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms, capKey -} - -func TestCoolKeeper(t *testing.T) { - ms, capKey := setupMultiStore() - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil) - ck := bank.NewBaseKeeper(am) - keeper := NewKeeper(capKey, ck, DefaultCodespace) - - err := InitGenesis(ctx, keeper, Genesis{"icy"}) - require.Nil(t, err) - - genesis := ExportGenesis(ctx, keeper) - require.Nil(t, err) - require.Equal(t, genesis, Genesis{"icy"}) - - res := keeper.GetTrend(ctx) - require.Equal(t, res, "icy") - - keeper.setTrend(ctx, "fiery") - res = keeper.GetTrend(ctx) - require.Equal(t, res, "fiery") -} diff --git a/docs/examples/democoin/x/cool/types.go b/docs/examples/democoin/x/cool/types.go deleted file mode 100644 index 11d3dde6bc7c..000000000000 --- a/docs/examples/democoin/x/cool/types.go +++ /dev/null @@ -1,108 +0,0 @@ -package cool - -import ( - "encoding/json" - "fmt" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// a really cool msg type, these fields are can be entirely arbitrary and -// custom to your message -type MsgSetTrend struct { - Sender sdk.AccAddress - Cool string -} - -// genesis state - specify genesis trend -type Genesis struct { - Trend string `json:"trend"` -} - -// new cool message -func NewMsgSetTrend(sender sdk.AccAddress, cool string) MsgSetTrend { - return MsgSetTrend{ - Sender: sender, - Cool: cool, - } -} - -// enforce the msg type at compile time -var _ sdk.Msg = MsgSetTrend{} - -// nolint -func (msg MsgSetTrend) Route() string { return "cool" } -func (msg MsgSetTrend) Type() string { return "set_trend" } -func (msg MsgSetTrend) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } -func (msg MsgSetTrend) String() string { - return fmt.Sprintf("MsgSetTrend{Sender: %v, Cool: %v}", msg.Sender, msg.Cool) -} - -// Validate Basic is used to quickly disqualify obviously invalid messages quickly -func (msg MsgSetTrend) ValidateBasic() sdk.Error { - if len(msg.Sender) == 0 { - return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("") - } - if strings.Contains(msg.Cool, "hot") { - return sdk.ErrUnauthorized("").TraceSDK("hot is not cool") - } - if strings.Contains(msg.Cool, "warm") { - return sdk.ErrUnauthorized("").TraceSDK("warm is not very cool") - } - return nil -} - -// Get the bytes for the message signer to sign on -func (msg MsgSetTrend) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -//_______________________________________________________________________ - -// A message type to quiz how cool you are. these fields are can be entirely -// arbitrary and custom to your message -type MsgQuiz struct { - Sender sdk.AccAddress - CoolAnswer string -} - -// New cool message -func NewMsgQuiz(sender sdk.AccAddress, coolerthancool string) MsgQuiz { - return MsgQuiz{ - Sender: sender, - CoolAnswer: coolerthancool, - } -} - -// enforce the msg type at compile time -var _ sdk.Msg = MsgQuiz{} - -// nolint -func (msg MsgQuiz) Route() string { return "cool" } -func (msg MsgQuiz) Type() string { return "quiz" } -func (msg MsgQuiz) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } -func (msg MsgQuiz) String() string { - return fmt.Sprintf("MsgQuiz{Sender: %v, CoolAnswer: %v}", msg.Sender, msg.CoolAnswer) -} - -// Validate Basic is used to quickly disqualify obviously invalid messages quickly -func (msg MsgQuiz) ValidateBasic() sdk.Error { - if len(msg.Sender) == 0 { - return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("") - } - return nil -} - -// Get the bytes for the message signer to sign on -func (msg MsgQuiz) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} diff --git a/docs/examples/democoin/x/oracle/README.md b/docs/examples/democoin/x/oracle/README.md deleted file mode 100644 index 605e5c5bd408..000000000000 --- a/docs/examples/democoin/x/oracle/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Oracle Module - -`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state. - -## Integration - -See `x/oracle/oracle_test.go` for the code that using Oracle - -To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended. - -```go -type MyPayload struct { - Data int - Nonce int -} -``` - -When you write a payload, its `.Route()` should return same route with your module is registered on the router. It is because `oracle.Msg` inherits `.Route()` from its embedded payload and it should be handled on the user modules. - -Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`. - -```go -func NewHandler(keeper Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case oracle.Msg: - return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error { - switch p := p.(type) { - case MyPayload: - return handleMyPayload(ctx, keeper, p) - } - } - } - } -} -``` - -In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`. - -```go -func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { - return Keeper { - cdc: cdc, - key: key, - - // ValidatorSet to get validators infor - valset: valset, - - // The keeper will pass payload - // when more than 2/3 signed on it - supermaj: supermaj, - // The keeper will prune votes after 100 blocks from last sign - timeout: timeout, - } -} -``` - -Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events. diff --git a/docs/examples/democoin/x/oracle/errors.go b/docs/examples/democoin/x/oracle/errors.go deleted file mode 100644 index 1378fa76f551..000000000000 --- a/docs/examples/democoin/x/oracle/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package oracle - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Oracle errors reserve 1101-1199 -const ( - CodeNotValidator sdk.CodeType = 1101 - CodeAlreadyProcessed sdk.CodeType = 1102 - CodeAlreadySigned sdk.CodeType = 1103 - CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest -) - -// ---------------------------------------- -// Error constructors - -// ErrNotValidator called when the signer of a Msg is not a validator -func ErrNotValidator(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { - return sdk.NewError(codespace, CodeNotValidator, address.String()) -} - -// ErrAlreadyProcessed called when a payload is already processed -func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeAlreadyProcessed, "") -} - -// ErrAlreadySigned called when the signer is trying to double signing -func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeAlreadySigned, "") -} diff --git a/docs/examples/democoin/x/oracle/handler.go b/docs/examples/democoin/x/oracle/handler.go deleted file mode 100644 index fb7587a12683..000000000000 --- a/docs/examples/democoin/x/oracle/handler.go +++ /dev/null @@ -1,106 +0,0 @@ -package oracle - -import ( - "bytes" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Handler handles payload after it passes voting process -type Handler func(ctx sdk.Context, p Payload) sdk.Error - -func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info { - info.Power = info.Power.Add(val.GetPower()) - - // Return if the voted power is not bigger than required power - totalPower := valset.TotalPower(ctx) - requiredPower := totalPower.Mul(keeper.supermaj) - if !info.Power.GT(requiredPower) { - return info - } - - // Check if the validators hash has been changed during the vote process - // and recalculate voted power - hash := ctx.BlockHeader().ValidatorsHash - if !bytes.Equal(hash, info.Hash) { - info.Power = sdk.ZeroDec() - info.Hash = hash - prefix := GetSignPrefix(p, keeper.cdc) - store := ctx.KVStore(keeper.key) - iter := sdk.KVStorePrefixIterator(store, prefix) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - if valset.Validator(ctx, iter.Value()) != nil { - store.Delete(iter.Key()) - continue - } - info.Power = info.Power.Add(val.GetPower()) - } - if !info.Power.GT(totalPower.Mul(keeper.supermaj)) { - return info - } - } - - info.Status = Processed - return info -} - -// Handle is used by other modules to handle Msg -func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result { - valset := keeper.valset - - signer := o.Signer - payload := o.Payload - - // Check the oracle is not in process - info := keeper.Info(ctx, payload) - if info.Status != Pending { - return ErrAlreadyProcessed(codespace).Result() - } - - // Check if it is reporting timeout - now := ctx.BlockHeight() - if now > info.LastSigned+keeper.timeout { - info = Info{Status: Timeout} - keeper.setInfo(ctx, payload, info) - keeper.clearSigns(ctx, payload) - return sdk.Result{} - } - info.LastSigned = ctx.BlockHeight() - - // check the signer is a validator - val := valset.Validator(ctx, sdk.ValAddress(signer)) - if val == nil { - return ErrNotValidator(codespace, signer).Result() - } - - // Check double signing - if keeper.signed(ctx, payload, signer) { - return ErrAlreadySigned(codespace).Result() - } - - keeper.sign(ctx, payload, signer) - - info = keeper.update(ctx, val, valset, payload, info) - if info.Status == Processed { - info = Info{Status: Processed} - } - - keeper.setInfo(ctx, payload, info) - - if info.Status == Processed { - keeper.clearSigns(ctx, payload) - cctx, write := ctx.CacheContext() - err := h(cctx, payload) - if err != nil { - return sdk.Result{ - Code: sdk.CodeOK, - Log: err.ABCILog(), - } - } - write() - - } - - return sdk.Result{} -} diff --git a/docs/examples/democoin/x/oracle/keeper.go b/docs/examples/democoin/x/oracle/keeper.go deleted file mode 100644 index d061d2f8ed4f..000000000000 --- a/docs/examples/democoin/x/oracle/keeper.go +++ /dev/null @@ -1,111 +0,0 @@ -package oracle - -import ( - "github.com/cosmos/cosmos-sdk/codec" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Keeper of the oracle store -type Keeper struct { - key sdk.StoreKey - cdc *codec.Codec - - valset sdk.ValidatorSet - - supermaj sdk.Dec - timeout int64 -} - -// NewKeeper constructs a new keeper -func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper { - if timeout < 0 { - panic("Timeout should not be negative") - } - - return Keeper{ - key: key, - cdc: cdc, - - valset: valset, - - supermaj: supermaj, - timeout: timeout, - } -} - -// InfoStatus - current status of an Info -type InfoStatus int8 - -// Define InfoStatus -const ( - Pending = InfoStatus(iota) - Processed - Timeout -) - -// Info for each payload -type Info struct { - Power sdk.Dec - Hash []byte - LastSigned int64 - Status InfoStatus -} - -// EmptyInfo construct an empty Info -func EmptyInfo(ctx sdk.Context) Info { - return Info{ - Power: sdk.ZeroDec(), - Hash: ctx.BlockHeader().ValidatorsHash, - LastSigned: ctx.BlockHeight(), - Status: Pending, - } -} - -// Info returns the information about a payload -func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { - store := ctx.KVStore(keeper.key) - - key := GetInfoKey(p, keeper.cdc) - bz := store.Get(key) - if bz == nil { - return EmptyInfo(ctx) - } - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res) - - return -} - -func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { - store := ctx.KVStore(keeper.key) - - key := GetInfoKey(p, keeper.cdc) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(info) - store.Set(key, bz) -} - -func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.AccAddress) { - store := ctx.KVStore(keeper.key) - - key := GetSignKey(p, signer, keeper.cdc) - store.Set(key, signer) -} - -func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.AccAddress) bool { - store := ctx.KVStore(keeper.key) - - key := GetSignKey(p, signer, keeper.cdc) - return store.Has(key) -} - -func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) { - store := ctx.KVStore(keeper.key) - - prefix := GetSignPrefix(p, keeper.cdc) - - iter := sdk.KVStorePrefixIterator(store, prefix) - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) - } - iter.Close() -} diff --git a/docs/examples/democoin/x/oracle/keeper_keys.go b/docs/examples/democoin/x/oracle/keeper_keys.go deleted file mode 100644 index d678692be9fa..000000000000 --- a/docs/examples/democoin/x/oracle/keeper_keys.go +++ /dev/null @@ -1,23 +0,0 @@ -package oracle - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// GetInfoKey returns the key for OracleInfo -func GetInfoKey(p Payload, cdc *codec.Codec) []byte { - bz := cdc.MustMarshalBinaryLengthPrefixed(p) - return append([]byte{0x00}, bz...) -} - -// GetSignPrefix returns the prefix for signs -func GetSignPrefix(p Payload, cdc *codec.Codec) []byte { - bz := cdc.MustMarshalBinaryLengthPrefixed(p) - return append([]byte{0x01}, bz...) -} - -// GetSignKey returns the key for sign -func GetSignKey(p Payload, signer sdk.AccAddress, cdc *codec.Codec) []byte { - return append(GetSignPrefix(p, cdc), signer...) -} diff --git a/docs/examples/democoin/x/oracle/oracle_test.go b/docs/examples/democoin/x/oracle/oracle_test.go deleted file mode 100644 index b8b613e318d3..000000000000 --- a/docs/examples/democoin/x/oracle/oracle_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package oracle - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func defaultContext(keys ...sdk.StoreKey) sdk.Context { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - for _, key := range keys { - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - } - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil) - return ctx -} - -type seqOracle struct { - Seq int - Nonce int -} - -func (o seqOracle) Route() string { - return "seq" -} -func (o seqOracle) Type() string { - return "seq" -} - -func (o seqOracle) ValidateBasic() sdk.Error { - return nil -} - -func makeCodec() *codec.Codec { - var cdc = codec.New() - - cdc.RegisterInterface((*sdk.Msg)(nil), nil) - cdc.RegisterConcrete(Msg{}, "test/Oracle", nil) - - cdc.RegisterInterface((*Payload)(nil), nil) - cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil) - - cdc.Seal() - - return cdc -} - -func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case Msg: - return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error { - switch p := p.(type) { - case seqOracle: - return handleSeqOracle(ctx, key, p) - default: - return sdk.ErrUnknownRequest("") - } - }, ctx, msg, codespace) - default: - return sdk.ErrUnknownRequest("").Result() - } - } -} - -func getSequence(ctx sdk.Context, key sdk.StoreKey) int { - store := ctx.KVStore(key) - seqbz := store.Get([]byte("seq")) - - var seq int - if seqbz == nil { - seq = 0 - } else { - codec.New().MustUnmarshalBinaryLengthPrefixed(seqbz, &seq) - } - - return seq -} - -func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error { - store := ctx.KVStore(key) - - seq := getSequence(ctx, key) - if seq != o.Seq { - return sdk.NewError(sdk.CodespaceRoot, 1, "") - } - - bz := codec.New().MustMarshalBinaryLengthPrefixed(seq + 1) - store.Set([]byte("seq"), bz) - - return nil -} - -func TestOracle(t *testing.T) { - cdc := makeCodec() - - addr1 := []byte("addr1") - addr2 := []byte("addr2") - addr3 := []byte("addr3") - addr4 := []byte("addr4") - valset := &mock.ValidatorSet{[]mock.Validator{ - {addr1, sdk.NewDec(7)}, - {addr2, sdk.NewDec(7)}, - {addr3, sdk.NewDec(1)}, - }} - - key := sdk.NewKVStoreKey("testkey") - ctx := defaultContext(key) - - bz, err := json.Marshal(valset) - require.Nil(t, err) - ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) - - ork := NewKeeper(key, cdc, valset, sdk.NewDecWithPrec(667, 3), 100) // 66.7% - h := seqHandler(ork, key, sdk.CodespaceRoot) - - // Nonmock.Validator signed, transaction failed - msg := Msg{seqOracle{0, 0}, []byte("randomguy")} - res := h(ctx, msg) - require.False(t, res.IsOK()) - require.Equal(t, 0, getSequence(ctx, key)) - - // Less than 2/3 signed, msg not processed - msg.Signer = addr1 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 0, getSequence(ctx, key)) - - // Double signed, transaction failed - res = h(ctx, msg) - require.False(t, res.IsOK()) - require.Equal(t, 0, getSequence(ctx, key)) - - // More than 2/3 signed, msg processed - msg.Signer = addr2 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // Already processed, transaction failed - msg.Signer = addr3 - res = h(ctx, msg) - require.False(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // Less than 2/3 signed, msg not processed - msg = Msg{seqOracle{100, 1}, addr1} - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // More than 2/3 signed but payload is invalid - msg.Signer = addr2 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.NotEqual(t, "", res.Log) - require.Equal(t, 1, getSequence(ctx, key)) - - // Already processed, transaction failed - msg.Signer = addr3 - res = h(ctx, msg) - require.False(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // Should handle mock.Validator set change - valset.AddValidator(mock.Validator{addr4, sdk.NewDec(12)}) - bz, err = json.Marshal(valset) - require.Nil(t, err) - ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) - - // Less than 2/3 signed, msg not processed - msg = Msg{seqOracle{1, 2}, addr1} - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // Less than 2/3 signed, msg not processed - msg.Signer = addr2 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 1, getSequence(ctx, key)) - - // More than 2/3 signed, msg processed - msg.Signer = addr4 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 2, getSequence(ctx, key)) - - // Should handle mock.Validator set change while oracle process is happening - msg = Msg{seqOracle{2, 3}, addr4} - - // Less than 2/3 signed, msg not processed - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 2, getSequence(ctx, key)) - - // Signed mock.Validator is kicked out - valset.RemoveValidator(addr4) - bz, err = json.Marshal(valset) - require.Nil(t, err) - ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) - - // Less than 2/3 signed, msg not processed - msg.Signer = addr1 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 2, getSequence(ctx, key)) - - // More than 2/3 signed, msg processed - msg.Signer = addr2 - res = h(ctx, msg) - require.True(t, res.IsOK()) - require.Equal(t, 3, getSequence(ctx, key)) -} diff --git a/docs/examples/democoin/x/oracle/types.go b/docs/examples/democoin/x/oracle/types.go deleted file mode 100644 index 06590e3a0c62..000000000000 --- a/docs/examples/democoin/x/oracle/types.go +++ /dev/null @@ -1,34 +0,0 @@ -package oracle - -import ( - "encoding/json" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Msg - struct for voting on payloads -type Msg struct { - Payload - Signer sdk.AccAddress -} - -// GetSignBytes implements sdk.Msg -func (msg Msg) GetSignBytes() []byte { - bz, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(bz) -} - -// GetSigners implements sdk.Msg -func (msg Msg) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{msg.Signer} -} - -// Payload defines inner data for actual execution -type Payload interface { - Route() string - Type() string - ValidateBasic() sdk.Error -} diff --git a/docs/examples/democoin/x/pow/app_test.go b/docs/examples/democoin/x/pow/app_test.go deleted file mode 100644 index 1556996b37c0..000000000000 --- a/docs/examples/democoin/x/pow/app_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package pow - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" -) - -var ( - priv1 = ed25519.GenPrivKey() - addr1 = sdk.AccAddress(priv1.PubKey().Address()) -) - -// initialize the mock application for this module -func getMockApp(t *testing.T) *mock.App { - mapp := mock.NewApp() - - RegisterCodec(mapp.Cdc) - keyPOW := sdk.NewKVStoreKey("pow") - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) - config := Config{"pow", 1} - keeper := NewKeeper(keyPOW, config, bankKeeper, DefaultCodespace) - mapp.Router().AddRoute("pow", keeper.Handler) - - mapp.SetInitChainer(getInitChainer(mapp, keeper)) - - require.NoError(t, mapp.CompleteSetup(keyPOW)) - - mapp.Seal() - - return mapp -} - -// overwrite the mock init chainer -func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - mapp.InitChainer(ctx, req) - - genesis := Genesis{ - Difficulty: 1, - Count: 0, - } - InitGenesis(ctx, keeper, genesis) - - return abci.ResponseInitChain{} - } -} - -func TestMsgMine(t *testing.T) { - mapp := getMockApp(t) - - // Construct genesis state - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: nil, - } - accs := []auth.Account{acc1} - - // Initialize the chain (nil) - mock.SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) - require.Equal(t, acc1, res1) - - // Mine and check for reward - mineMsg1 := GenerateMsgMine(addr1, 1, 2) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []uint64{0}, []uint64{0}, true, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("pow", sdk.NewInt(1))}) - // Mine again and check for reward - mineMsg2 := GenerateMsgMine(addr1, 2, 3) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []uint64{0}, []uint64{1}, true, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("pow", sdk.NewInt(2))}) - // Mine again - should be invalid - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []uint64{0}, []uint64{1}, false, false, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{sdk.NewCoin("pow", sdk.NewInt(2))}) -} diff --git a/docs/examples/democoin/x/pow/client/cli/tx.go b/docs/examples/democoin/x/pow/client/cli/tx.go deleted file mode 100644 index 4c91d723f4ea..000000000000 --- a/docs/examples/democoin/x/pow/client/cli/tx.go +++ /dev/null @@ -1,56 +0,0 @@ -package cli - -import ( - "strconv" - - "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/docs/examples/democoin/x/pow" - sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - - "github.com/spf13/cobra" -) - -// command to mine some pow! -func MineCmd(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "mine [difficulty] [count] [nonce] [solution]", - Short: "Mine some coins with proof-of-work!", - Args: cobra.ExactArgs(4), - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(cdc) - - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - difficulty, err := strconv.ParseUint(args[0], 0, 64) - if err != nil { - return err - } - - count, err := strconv.ParseUint(args[1], 0, 64) - if err != nil { - return err - } - - nonce, err := strconv.ParseUint(args[2], 0, 64) - if err != nil { - return err - } - - solution := []byte(args[3]) - msg := pow.NewMsgMine(from, difficulty, count, nonce, solution) - - // Build and sign the transaction, then broadcast to a Tendermint - // node. - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } -} diff --git a/docs/examples/democoin/x/pow/codec.go b/docs/examples/democoin/x/pow/codec.go deleted file mode 100644 index 8f4296f17a43..000000000000 --- a/docs/examples/democoin/x/pow/codec.go +++ /dev/null @@ -1,10 +0,0 @@ -package pow - -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - -// Register concrete types on codec codec -func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgMine{}, "pow/Mine", nil) -} diff --git a/docs/examples/democoin/x/pow/errors.go b/docs/examples/democoin/x/pow/errors.go deleted file mode 100644 index 8368c2359d47..000000000000 --- a/docs/examples/democoin/x/pow/errors.go +++ /dev/null @@ -1,79 +0,0 @@ -package pow - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// TODO remove, seems hacky -type CodeType = sdk.CodeType - -// POW errors reserve 200 ~ 299 -const ( - DefaultCodespace sdk.CodespaceType = "pow" - CodeInvalidDifficulty CodeType = 201 - CodeNonexistentDifficulty CodeType = 202 - CodeNonexistentReward CodeType = 203 - CodeNonexistentCount CodeType = 204 - CodeInvalidProof CodeType = 205 - CodeNotBelowTarget CodeType = 206 - CodeInvalidCount CodeType = 207 - CodeUnknownRequest CodeType = sdk.CodeUnknownRequest -) - -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidDifficulty: - return "insuffient difficulty" - case CodeNonexistentDifficulty: - return "nonexistent difficulty" - case CodeNonexistentReward: - return "nonexistent reward" - case CodeNonexistentCount: - return "nonexistent count" - case CodeInvalidProof: - return "invalid proof" - case CodeNotBelowTarget: - return "not below target" - case CodeInvalidCount: - return "invalid count" - case CodeUnknownRequest: - return "unknown request" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -// nolint -func ErrInvalidDifficulty(codespace sdk.CodespaceType, msg string) sdk.Error { - return newError(codespace, CodeInvalidDifficulty, msg) -} -func ErrNonexistentDifficulty(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeNonexistentDifficulty, "") -} -func ErrNonexistentReward(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeNonexistentReward, "") -} -func ErrNonexistentCount(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeNonexistentCount, "") -} -func ErrInvalidProof(codespace sdk.CodespaceType, msg string) sdk.Error { - return newError(codespace, CodeInvalidProof, msg) -} -func ErrNotBelowTarget(codespace sdk.CodespaceType, msg string) sdk.Error { - return newError(codespace, CodeNotBelowTarget, msg) -} -func ErrInvalidCount(codespace sdk.CodespaceType, msg string) sdk.Error { - return newError(codespace, CodeInvalidCount, msg) -} - -func msgOrDefaultMsg(msg string, code CodeType) string { - if msg != "" { - return msg - } - return codeToDefaultMsg(code) -} - -func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { - msg = msgOrDefaultMsg(msg, code) - return sdk.NewError(codespace, code, msg) -} diff --git a/docs/examples/democoin/x/pow/handler.go b/docs/examples/democoin/x/pow/handler.go deleted file mode 100644 index 5aa4cbab5e01..000000000000 --- a/docs/examples/democoin/x/pow/handler.go +++ /dev/null @@ -1,33 +0,0 @@ -package pow - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// POW handler -func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgMine: - return handleMsgMine(ctx, pk, msg) - default: - errMsg := "Unrecognized pow Msg type: " + msg.Type() - return sdk.ErrUnknownRequest(errMsg).Result() - } -} - -func handleMsgMine(ctx sdk.Context, pk Keeper, msg MsgMine) sdk.Result { - - // precondition: msg has passed ValidateBasic - - newDiff, newCount, err := pk.CheckValid(ctx, msg.Difficulty, msg.Count) - if err != nil { - return err.Result() - } - - err = pk.ApplyValid(ctx, msg.Sender, newDiff, newCount) - if err != nil { - return err.Result() - } - - return sdk.Result{} -} diff --git a/docs/examples/democoin/x/pow/handler_test.go b/docs/examples/democoin/x/pow/handler_test.go deleted file mode 100644 index ce398b7c2869..000000000000 --- a/docs/examples/democoin/x/pow/handler_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package pow - -import ( - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - - codec "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth" - bank "github.com/cosmos/cosmos-sdk/x/bank" -) - -func TestPowHandler(t *testing.T) { - ms, capKey := setupMultiStore() - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount) - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - config := NewConfig("pow", int64(1)) - ck := bank.NewBaseKeeper(am) - keeper := NewKeeper(capKey, config, ck, DefaultCodespace) - - handler := keeper.Handler - - addr := sdk.AccAddress([]byte("sender")) - count := uint64(1) - difficulty := uint64(2) - - err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) - require.Nil(t, err) - - nonce, proof := mine(addr, count, difficulty) - msg := NewMsgMine(addr, difficulty, count, nonce, proof) - - result := handler(ctx, msg) - require.Equal(t, result, sdk.Result{}) - - newDiff, err := keeper.GetLastDifficulty(ctx) - require.Nil(t, err) - require.Equal(t, newDiff, uint64(2)) - - newCount, err := keeper.GetLastCount(ctx) - require.Nil(t, err) - require.Equal(t, newCount, uint64(1)) - - // todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691 - - difficulty = uint64(4) - nonce, proof = mine(addr, count, difficulty) - msg = NewMsgMine(addr, difficulty, count, nonce, proof) - - result = handler(ctx, msg) - require.NotEqual(t, result, sdk.Result{}) -} diff --git a/docs/examples/democoin/x/pow/keeper.go b/docs/examples/democoin/x/pow/keeper.go deleted file mode 100644 index 6c3bfc4eb866..000000000000 --- a/docs/examples/democoin/x/pow/keeper.go +++ /dev/null @@ -1,135 +0,0 @@ -package pow - -import ( - "fmt" - "strconv" - - sdk "github.com/cosmos/cosmos-sdk/types" - bank "github.com/cosmos/cosmos-sdk/x/bank" -) - -// module users must specify coin denomination and reward (constant) per PoW solution -type Config struct { - Denomination string - Reward int64 -} - -// genesis info must specify starting difficulty and starting count -type Genesis struct { - Difficulty uint64 `json:"difficulty"` - Count uint64 `json:"count"` -} - -// POW Keeper -type Keeper struct { - key sdk.StoreKey - config Config - ck bank.Keeper - codespace sdk.CodespaceType -} - -func NewConfig(denomination string, reward int64) Config { - return Config{denomination, reward} -} - -func NewKeeper(key sdk.StoreKey, config Config, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { - return Keeper{key, config, ck, codespace} -} - -// InitGenesis for the POW module -func InitGenesis(ctx sdk.Context, k Keeper, genesis Genesis) error { - k.SetLastDifficulty(ctx, genesis.Difficulty) - k.SetLastCount(ctx, genesis.Count) - return nil -} - -// ExportGenesis for the PoW module -func ExportGenesis(ctx sdk.Context, k Keeper) Genesis { - difficulty, err := k.GetLastDifficulty(ctx) - if err != nil { - panic(err) - } - count, err := k.GetLastCount(ctx) - if err != nil { - panic(err) - } - return Genesis{ - difficulty, - count, - } -} - -var lastDifficultyKey = []byte("lastDifficultyKey") - -// get the last mining difficulty -func (k Keeper) GetLastDifficulty(ctx sdk.Context) (uint64, error) { - store := ctx.KVStore(k.key) - stored := store.Get(lastDifficultyKey) - if stored == nil { - panic("no stored difficulty") - } else { - return strconv.ParseUint(string(stored), 0, 64) - } -} - -// set the last mining difficulty -func (k Keeper) SetLastDifficulty(ctx sdk.Context, diff uint64) { - store := ctx.KVStore(k.key) - store.Set(lastDifficultyKey, []byte(strconv.FormatUint(diff, 16))) -} - -var countKey = []byte("count") - -// get the last count -func (k Keeper) GetLastCount(ctx sdk.Context) (uint64, error) { - store := ctx.KVStore(k.key) - stored := store.Get(countKey) - if stored == nil { - panic("no stored count") - } else { - return strconv.ParseUint(string(stored), 0, 64) - } -} - -// set the last count -func (k Keeper) SetLastCount(ctx sdk.Context, count uint64) { - store := ctx.KVStore(k.key) - store.Set(countKey, []byte(strconv.FormatUint(count, 16))) -} - -// Is the keeper state valid? -func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (uint64, uint64, sdk.Error) { - - lastDifficulty, err := k.GetLastDifficulty(ctx) - if err != nil { - return 0, 0, ErrNonexistentDifficulty(k.codespace) - } - - newDifficulty := lastDifficulty + 1 - - lastCount, err := k.GetLastCount(ctx) - if err != nil { - return 0, 0, ErrNonexistentCount(k.codespace) - } - - newCount := lastCount + 1 - - if count != newCount { - return 0, 0, ErrInvalidCount(k.codespace, fmt.Sprintf("invalid count: was %d, should have been %d", count, newCount)) - } - if difficulty != newDifficulty { - return 0, 0, ErrInvalidDifficulty(k.codespace, fmt.Sprintf("invalid difficulty: was %d, should have been %d", difficulty, newDifficulty)) - } - return newDifficulty, newCount, nil -} - -// Add some coins for a POW well done -func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.AccAddress, newDifficulty uint64, newCount uint64) sdk.Error { - _, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.NewInt64Coin(k.config.Denomination, k.config.Reward)}) - if ckErr != nil { - return ckErr - } - k.SetLastDifficulty(ctx, newDifficulty) - k.SetLastCount(ctx, newCount) - return nil -} diff --git a/docs/examples/democoin/x/pow/keeper_test.go b/docs/examples/democoin/x/pow/keeper_test.go deleted file mode 100644 index c8d5406f9cfb..000000000000 --- a/docs/examples/democoin/x/pow/keeper_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package pow - -import ( - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - 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" - auth "github.com/cosmos/cosmos-sdk/x/auth" - bank "github.com/cosmos/cosmos-sdk/x/bank" -) - -// possibly share this kind of setup functionality between module testsuites? -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { - db := dbm.NewMemDB() - capKey := sdk.NewKVStoreKey("capkey") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - - return ms, capKey -} - -func TestPowKeeperGetSet(t *testing.T) { - ms, capKey := setupMultiStore() - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount) - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - config := NewConfig("pow", int64(1)) - ck := bank.NewBaseKeeper(am) - keeper := NewKeeper(capKey, config, ck, DefaultCodespace) - - err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)}) - require.Nil(t, err) - - genesis := ExportGenesis(ctx, keeper) - require.Nil(t, err) - require.Equal(t, genesis, Genesis{uint64(1), uint64(0)}) - - res, err := keeper.GetLastDifficulty(ctx) - require.Nil(t, err) - require.Equal(t, res, uint64(1)) - - keeper.SetLastDifficulty(ctx, 2) - - res, err = keeper.GetLastDifficulty(ctx) - require.Nil(t, err) - require.Equal(t, res, uint64(2)) -} diff --git a/docs/examples/democoin/x/pow/mine.go b/docs/examples/democoin/x/pow/mine.go deleted file mode 100644 index 3c24c1264728..000000000000 --- a/docs/examples/democoin/x/pow/mine.go +++ /dev/null @@ -1,46 +0,0 @@ -package pow - -import ( - "encoding/hex" - "math" - "strconv" - - "github.com/tendermint/tendermint/crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// generate the mine message -func GenerateMsgMine(sender sdk.AccAddress, count uint64, difficulty uint64) MsgMine { - nonce, hash := mine(sender, count, difficulty) - return NewMsgMine(sender, difficulty, count, nonce, hash) -} - -func hash(sender sdk.AccAddress, count uint64, nonce uint64) []byte { - var bytes []byte - bytes = append(bytes, []byte(sender)...) - countBytes := strconv.FormatUint(count, 16) - bytes = append(bytes, countBytes...) - nonceBytes := strconv.FormatUint(nonce, 16) - bytes = append(bytes, nonceBytes...) - hash := crypto.Sha256(bytes) - // uint64, so we just use the first 8 bytes of the hash - // this limits the range of possible difficulty values (as compared to uint256), but fine for proof-of-concept - ret := make([]byte, hex.EncodedLen(len(hash))) - hex.Encode(ret, hash) - return ret[:16] -} - -func mine(sender sdk.AccAddress, count uint64, difficulty uint64) (uint64, []byte) { - target := math.MaxUint64 / difficulty - for nonce := uint64(0); ; nonce++ { - hash := hash(sender, count, nonce) - hashuint, err := strconv.ParseUint(string(hash), 16, 64) - if err != nil { - panic(err) - } - if hashuint < target { - return nonce, hash - } - } -} diff --git a/docs/examples/democoin/x/pow/types.go b/docs/examples/democoin/x/pow/types.go deleted file mode 100644 index 80bad3a584d1..000000000000 --- a/docs/examples/democoin/x/pow/types.go +++ /dev/null @@ -1,81 +0,0 @@ -package pow - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "math" - "strconv" - - "github.com/tendermint/tendermint/crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// MsgMine - mine some coins with PoW -type MsgMine struct { - Sender sdk.AccAddress `json:"sender"` - Difficulty uint64 `json:"difficulty"` - Count uint64 `json:"count"` - Nonce uint64 `json:"nonce"` - Proof []byte `json:"proof"` -} - -// enforce the msg type at compile time -var _ sdk.Msg = MsgMine{} - -// NewMsgMine - construct mine message -func NewMsgMine(sender sdk.AccAddress, difficulty uint64, count uint64, nonce uint64, proof []byte) MsgMine { - return MsgMine{sender, difficulty, count, nonce, proof} -} - -// nolint -func (msg MsgMine) Route() string { return "pow" } -func (msg MsgMine) Type() string { return "mine" } -func (msg MsgMine) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } -func (msg MsgMine) String() string { - return fmt.Sprintf("MsgMine{Sender: %s, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof) -} - -// validate the mine message -func (msg MsgMine) ValidateBasic() sdk.Error { - // check hash - var data []byte - // hash must include sender, so no other users can race the tx - data = append(data, []byte(msg.Sender)...) - countBytes := strconv.FormatUint(msg.Count, 16) - // hash must include count so proof-of-work solutions cannot be replayed - data = append(data, countBytes...) - nonceBytes := strconv.FormatUint(msg.Nonce, 16) - data = append(data, nonceBytes...) - hash := crypto.Sha256(data) - hashHex := make([]byte, hex.EncodedLen(len(hash))) - hex.Encode(hashHex, hash) - hashHex = hashHex[:16] - if !bytes.Equal(hashHex, msg.Proof) { - return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("hashHex: %s, proof: %s", hashHex, msg.Proof)) - } - - // check proof below difficulty - // difficulty is linear - 1 = all hashes, 2 = half of hashes, 3 = third of hashes, etc - target := math.MaxUint64 / msg.Difficulty - hashUint, err := strconv.ParseUint(string(msg.Proof), 16, 64) - if err != nil { - return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("proof: %s", msg.Proof)) - } - if hashUint >= target { - return ErrNotBelowTarget(DefaultCodespace, fmt.Sprintf("hashuint: %d, target: %d", hashUint, target)) - } - - return nil -} - -// get the mine message sign bytes -func (msg MsgMine) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} diff --git a/docs/examples/democoin/x/pow/types_test.go b/docs/examples/democoin/x/pow/types_test.go deleted file mode 100644 index 44f79899dd9f..000000000000 --- a/docs/examples/democoin/x/pow/types_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package pow - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestNewMsgMine(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - msg := MsgMine{addr, 0, 0, 0, []byte("")} - equiv := NewMsgMine(addr, 0, 0, 0, []byte("")) - require.Equal(t, msg, equiv, "%s != %s", msg, equiv) -} - -func TestMsgMineType(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - msg := MsgMine{addr, 0, 0, 0, []byte("")} - require.Equal(t, msg.Route(), "pow") -} - -func TestMsgMineValidation(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - otherAddr := sdk.AccAddress([]byte("another")) - count := uint64(0) - - for difficulty := uint64(1); difficulty < 1000; difficulty += 100 { - - count++ - nonce, proof := mine(addr, count, difficulty) - msg := MsgMine{addr, difficulty, count, nonce, proof} - err := msg.ValidateBasic() - require.Nil(t, err, "error with difficulty %d - %+v", difficulty, err) - - msg.Count++ - err = msg.ValidateBasic() - require.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg) - - msg.Count-- - msg.Nonce++ - err = msg.ValidateBasic() - require.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg) - - msg.Nonce-- - msg.Sender = otherAddr - err = msg.ValidateBasic() - require.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg) - } -} - -func TestMsgMineString(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - msg := MsgMine{addr, 0, 0, 0, []byte("abc")} - res := msg.String() - require.Equal(t, res, "MsgMine{Sender: cosmos1wdjkuer9wgh76ts6, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}") -} - -func TestMsgMineGetSignBytes(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - msg := MsgMine{addr, 1, 1, 1, []byte("abc")} - res := msg.GetSignBytes() - require.Equal(t, string(res), `{"count":1,"difficulty":1,"nonce":1,"proof":"YWJj","sender":"cosmos1wdjkuer9wgh76ts6"}`) -} - -func TestMsgMineGetSigners(t *testing.T) { - addr := sdk.AccAddress([]byte("sender")) - msg := MsgMine{addr, 1, 1, 1, []byte("abc")} - res := msg.GetSigners() - require.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]") -} diff --git a/docs/examples/democoin/x/simplestake/client/cli/commands.go b/docs/examples/democoin/x/simplestake/client/cli/commands.go deleted file mode 100644 index 8717a5f3ba93..000000000000 --- a/docs/examples/democoin/x/simplestake/client/cli/commands.go +++ /dev/null @@ -1,101 +0,0 @@ -package cli - -import ( - "encoding/hex" - "fmt" - "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/docs/examples/democoin/x/simplestake" - sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/tendermint/tendermint/crypto/ed25519" -) - -const ( - flagStake = "stake" - flagValidator = "validator" -) - -// simple bond tx -func BondTxCmd(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "bond", - Short: "Bond to a validator", - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(cdc) - - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - stakeString := viper.GetString(flagStake) - if len(stakeString) == 0 { - return fmt.Errorf("specify coins to bond with --stake") - } - - valString := viper.GetString(flagValidator) - if len(valString) == 0 { - return fmt.Errorf("specify pubkey to bond to with --validator") - } - - stake, err := sdk.ParseCoin(stakeString) - if err != nil { - return err - } - - // TODO: bech32 ... - rawPubKey, err := hex.DecodeString(valString) - if err != nil { - return err - } - var pubKeyEd ed25519.PubKeyEd25519 - copy(pubKeyEd[:], rawPubKey) - - msg := simplestake.NewMsgBond(from, stake, pubKeyEd) - - // Build and sign the transaction, then broadcast to a Tendermint - // node. - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } - - cmd.Flags().String(flagStake, "", "Amount of coins to stake") - cmd.Flags().String(flagValidator, "", "Validator address to stake") - - return cmd -} - -// simple unbond tx -func UnbondTxCmd(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "unbond", - Short: "Unbond from a validator", - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - cliCtx := context.NewCLIContext(). - WithCodec(cdc) - - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - msg := simplestake.NewMsgUnbond(from) - - // Build and sign the transaction, then broadcast to a Tendermint - // node. - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } - - return cmd -} diff --git a/docs/examples/democoin/x/simplestake/codec.go b/docs/examples/democoin/x/simplestake/codec.go deleted file mode 100644 index 7813fd64234f..000000000000 --- a/docs/examples/democoin/x/simplestake/codec.go +++ /dev/null @@ -1,11 +0,0 @@ -package simplestake - -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - -// Register concrete types on codec codec -func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgBond{}, "simplestake/BondMsg", nil) - cdc.RegisterConcrete(MsgUnbond{}, "simplestake/UnbondMsg", nil) -} diff --git a/docs/examples/democoin/x/simplestake/errors.go b/docs/examples/democoin/x/simplestake/errors.go deleted file mode 100644 index a0fc6e1accd0..000000000000 --- a/docs/examples/democoin/x/simplestake/errors.go +++ /dev/null @@ -1,38 +0,0 @@ -package simplestake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// simple stake errors reserve 300 ~ 399. -const ( - DefaultCodespace sdk.CodespaceType = moduleName - - // simplestake errors reserve 300 - 399. - CodeEmptyValidator sdk.CodeType = 300 - CodeInvalidUnbond sdk.CodeType = 301 - CodeEmptyStake sdk.CodeType = 302 - CodeIncorrectStakingToken sdk.CodeType = 303 -) - -// nolint -func ErrIncorrectStakingToken(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeIncorrectStakingToken, "") -} -func ErrEmptyValidator(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeEmptyValidator, "") -} -func ErrInvalidUnbond(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidUnbond, "") -} -func ErrEmptyStake(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeEmptyStake, "") -} - -// ----------------------------- -// Helpers - -// nolint: unparam -func newError(codespace sdk.CodespaceType, code sdk.CodeType, msg string) sdk.Error { - return sdk.NewError(codespace, code, msg) -} diff --git a/docs/examples/democoin/x/simplestake/handler.go b/docs/examples/democoin/x/simplestake/handler.go deleted file mode 100644 index 104058eec2c1..000000000000 --- a/docs/examples/democoin/x/simplestake/handler.go +++ /dev/null @@ -1,33 +0,0 @@ -package simplestake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// NewHandler returns a handler for "simplestake" type messages. -func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg.(type) { - case MsgBond: - return handleMsgBond() - case MsgUnbond: - return handleMsgUnbond() - default: - return sdk.ErrUnknownRequest("No match for message type.").Result() - } - } -} - -func handleMsgBond() sdk.Result { - // Removed ValidatorSet from result because it does not get used. - // TODO: Implement correct bond/unbond handling - return sdk.Result{ - Code: sdk.CodeOK, - } -} - -func handleMsgUnbond() sdk.Result { - return sdk.Result{ - Code: sdk.CodeOK, - } -} diff --git a/docs/examples/democoin/x/simplestake/keeper.go b/docs/examples/democoin/x/simplestake/keeper.go deleted file mode 100644 index b757dd66de4c..000000000000 --- a/docs/examples/democoin/x/simplestake/keeper.go +++ /dev/null @@ -1,135 +0,0 @@ -package simplestake - -import ( - "github.com/tendermint/tendermint/crypto" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" -) - -const stakingToken = "stake" - -const moduleName = "simplestake" - -// simple stake keeper -type Keeper struct { - ck bank.Keeper - - key sdk.StoreKey - cdc *codec.Codec - codespace sdk.CodespaceType -} - -func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper { - cdc := codec.New() - codec.RegisterCrypto(cdc) - return Keeper{ - key: key, - cdc: cdc, - ck: bankKeeper, - codespace: codespace, - } -} - -func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo { - store := ctx.KVStore(k.key) - bz := store.Get(addr) - if bz == nil { - return bondInfo{} - } - var bi bondInfo - err := k.cdc.UnmarshalBinaryLengthPrefixed(bz, &bi) - if err != nil { - panic(err) - } - return bi -} - -func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.AccAddress, bi bondInfo) { - store := ctx.KVStore(k.key) - bz, err := k.cdc.MarshalBinaryLengthPrefixed(bi) - if err != nil { - panic(err) - } - store.Set(addr, bz) -} - -func (k Keeper) deleteBondInfo(ctx sdk.Context, addr sdk.AccAddress) { - store := ctx.KVStore(k.key) - store.Delete(addr) -} - -// register a bond with the keeper -func (k Keeper) Bond(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { - if stake.Denom != stakingToken { - return 0, ErrIncorrectStakingToken(k.codespace) - } - - _, _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake}) - if err != nil { - return 0, err - } - - bi := k.getBondInfo(ctx, addr) - if bi.isEmpty() { - bi = bondInfo{ - PubKey: pubKey, - Power: 0, - } - } - - bi.Power = bi.Power + stake.Amount.Int64() - - k.setBondInfo(ctx, addr, bi) - return bi.Power, nil -} - -// register an unbond with the keeper -func (k Keeper) Unbond(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) { - bi := k.getBondInfo(ctx, addr) - if bi.isEmpty() { - return nil, 0, ErrInvalidUnbond(k.codespace) - } - k.deleteBondInfo(ctx, addr) - - returnedBond := sdk.NewInt64Coin(stakingToken, bi.Power) - - _, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) - if err != nil { - return bi.PubKey, bi.Power, err - } - - return bi.PubKey, bi.Power, nil -} - -// FOR TESTING PURPOSES ------------------------------------------------- - -func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) { - if stake.Denom != stakingToken { - return 0, ErrIncorrectStakingToken(k.codespace) - } - - bi := k.getBondInfo(ctx, addr) - if bi.isEmpty() { - bi = bondInfo{ - PubKey: pubKey, - Power: 0, - } - } - - bi.Power = bi.Power + stake.Amount.Int64() - - k.setBondInfo(ctx, addr, bi) - return bi.Power, nil -} - -func (k Keeper) unbondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) { - bi := k.getBondInfo(ctx, addr) - if bi.isEmpty() { - return nil, 0, ErrInvalidUnbond(k.codespace) - } - k.deleteBondInfo(ctx, addr) - - return bi.PubKey, bi.Power, nil -} diff --git a/docs/examples/democoin/x/simplestake/keeper_test.go b/docs/examples/democoin/x/simplestake/keeper_test.go deleted file mode 100644 index 974cf50e9ecf..000000000000 --- a/docs/examples/democoin/x/simplestake/keeper_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package simplestake - -import ( - "fmt" - - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - 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" -) - -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { - db := dbm.NewMemDB() - authKey := sdk.NewKVStoreKey("authkey") - capKey := sdk.NewKVStoreKey("capkey") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms, authKey, capKey -} - -func TestKeeperGetSet(t *testing.T) { - ms, authKey, capKey := setupMultiStore() - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) - stakeKeeper := NewKeeper(capKey, bank.NewBaseKeeper(accountKeeper), DefaultCodespace) - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - addr := sdk.AccAddress([]byte("some-address")) - - bi := stakeKeeper.getBondInfo(ctx, addr) - require.Equal(t, bi, bondInfo{}) - - privKey := ed25519.GenPrivKey() - - bi = bondInfo{ - PubKey: privKey.PubKey(), - Power: int64(10), - } - fmt.Printf("Pubkey: %v\n", privKey.PubKey()) - stakeKeeper.setBondInfo(ctx, addr, bi) - - savedBi := stakeKeeper.getBondInfo(ctx, addr) - require.NotNil(t, savedBi) - fmt.Printf("Bond Info: %v\n", savedBi) - require.Equal(t, int64(10), savedBi.Power) -} - -func TestBonding(t *testing.T) { - ms, authKey, capKey := setupMultiStore() - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - - accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountKeeper) - stakeKeeper := NewKeeper(capKey, bankKeeper, DefaultCodespace) - addr := sdk.AccAddress([]byte("some-address")) - privKey := ed25519.GenPrivKey() - pubKey := privKey.PubKey() - - _, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr) - require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) - - _, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10)) - require.Nil(t, err) - - power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10)) - require.Nil(t, err) - require.Equal(t, int64(20), power) - - pk, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr) - require.Nil(t, err) - require.Equal(t, pubKey, pk) - - _, _, err = stakeKeeper.unbondWithoutCoins(ctx, addr) - require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace)) -} diff --git a/docs/examples/democoin/x/simplestake/msgs.go b/docs/examples/democoin/x/simplestake/msgs.go deleted file mode 100644 index f3edb4ce0d84..000000000000 --- a/docs/examples/democoin/x/simplestake/msgs.go +++ /dev/null @@ -1,81 +0,0 @@ -package simplestake - -import ( - "encoding/json" - - "github.com/tendermint/tendermint/crypto" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//_________________________________________________________---- - -// simple bond message -type MsgBond struct { - Address sdk.AccAddress `json:"address"` - Stake sdk.Coin `json:"coins"` - PubKey crypto.PubKey `json:"pub_key"` -} - -func NewMsgBond(addr sdk.AccAddress, stake sdk.Coin, pubKey crypto.PubKey) MsgBond { - return MsgBond{ - Address: addr, - Stake: stake, - PubKey: pubKey, - } -} - -//nolint -func (msg MsgBond) Route() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgBond) Type() string { return "bond" } -func (msg MsgBond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } - -// basic validation of the bond message -func (msg MsgBond) ValidateBasic() sdk.Error { - if msg.Stake.IsZero() { - return ErrEmptyStake(DefaultCodespace) - } - - if msg.PubKey == nil { - return sdk.ErrInvalidPubKey("MsgBond.PubKey must not be empty") - } - - return nil -} - -// get bond message sign bytes -func (msg MsgBond) GetSignBytes() []byte { - bz, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(bz) -} - -//_______________________________________________________________ - -// simple unbond message -type MsgUnbond struct { - Address sdk.AccAddress `json:"address"` -} - -func NewMsgUnbond(addr sdk.AccAddress) MsgUnbond { - return MsgUnbond{ - Address: addr, - } -} - -//nolint -func (msg MsgUnbond) Route() string { return moduleName } //TODO update "stake/createvalidator" -func (msg MsgUnbond) Type() string { return "unbond" } -func (msg MsgUnbond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} } -func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil } - -// get unbond message sign bytes -func (msg MsgUnbond) GetSignBytes() []byte { - bz, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return bz -} diff --git a/docs/examples/democoin/x/simplestake/msgs_test.go b/docs/examples/democoin/x/simplestake/msgs_test.go deleted file mode 100644 index 4eb4509d8935..000000000000 --- a/docs/examples/democoin/x/simplestake/msgs_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package simplestake - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestBondMsgValidation(t *testing.T) { - privKey := ed25519.GenPrivKey() - cases := []struct { - valid bool - msgBond MsgBond - }{ - {true, NewMsgBond(sdk.AccAddress{}, sdk.NewInt64Coin("mycoin", 5), privKey.PubKey())}, - {false, NewMsgBond(sdk.AccAddress{}, sdk.NewInt64Coin("mycoin", 0), privKey.PubKey())}, - } - - for i, tc := range cases { - err := tc.msgBond.ValidateBasic() - if tc.valid { - require.Nil(t, err, "%d: %+v", i, err) - } else { - require.NotNil(t, err, "%d", i) - } - } -} diff --git a/docs/examples/democoin/x/simplestake/types.go b/docs/examples/democoin/x/simplestake/types.go deleted file mode 100644 index 937d36d6506e..000000000000 --- a/docs/examples/democoin/x/simplestake/types.go +++ /dev/null @@ -1,15 +0,0 @@ -package simplestake - -import "github.com/tendermint/tendermint/crypto" - -type bondInfo struct { - PubKey crypto.PubKey - Power int64 -} - -func (bi bondInfo) isEmpty() bool { - if bi == (bondInfo{}) { - return true - } - return false -} diff --git a/docs/examples/democoin/x/sketchy/handler.go b/docs/examples/democoin/x/sketchy/handler.go deleted file mode 100644 index f059b679cdc8..000000000000 --- a/docs/examples/democoin/x/sketchy/handler.go +++ /dev/null @@ -1,24 +0,0 @@ -package sketchy - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -/* -This is just an example to demonstrate a "sketchy" third-party handler module, -to demonstrate the "object capability" model for security. - -Since nothing is passed in via arguments to the NewHandler constructor, -it cannot affect the handling of other transaction types. -*/ - -// Handle all "sketchy" type messages. -func NewHandler() sdk.Handler { - - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - // There's nothing accessible from ctx or msg (even using reflection) - // that can mutate the state of the application. - return sdk.Result{} - } - -} diff --git a/docs/examples/kvstore/kvstore b/docs/examples/kvstore/kvstore deleted file mode 100755 index 6de98cc56379..000000000000 Binary files a/docs/examples/kvstore/kvstore and /dev/null differ diff --git a/docs/examples/kvstore/main.go b/docs/examples/kvstore/main.go deleted file mode 100644 index d7aa80834a5a..000000000000 --- a/docs/examples/kvstore/main.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/spf13/viper" - - "github.com/tendermint/tendermint/abci/server" - "github.com/tendermint/tendermint/libs/cli" - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func main() { - - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main") - - rootDir := viper.GetString(cli.HomeFlag) - db, err := dbm.NewGoLevelDB("basecoind", filepath.Join(rootDir, "data")) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Capabilities key to access the main KVStore. - var capKeyMainStore = sdk.NewKVStoreKey("main") - - // Create BaseApp. - var baseApp = bam.NewBaseApp("kvstore", logger, db, decodeTx) - - // Set mounts for BaseApp's MultiStore. - baseApp.MountStores(capKeyMainStore) - - // Set a handler Route. - baseApp.Router().AddRoute("kvstore", Handler(capKeyMainStore)) - - // Load latest version. - if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Start the ABCI server - srv, err := server.NewServer("0.0.0.0:26658", "socket", baseApp) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - err = srv.Start() - if err != nil { - cmn.Exit(err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - err = srv.Stop() - if err != nil { - cmn.Exit(err.Error()) - } - }) - return -} - -// KVStore Handler -func Handler(storeKey sdk.StoreKey) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - dTx, ok := msg.(kvstoreTx) - if !ok { - panic("Handler should only receive kvstoreTx") - } - - // tx is already unmarshalled - key := dTx.key - value := dTx.value - - store := ctx.KVStore(storeKey) - store.Set(key, value) - - return sdk.Result{ - Code: 0, - Log: fmt.Sprintf("set %s=%s", key, value), - } - } -} diff --git a/docs/examples/kvstore/tx.go b/docs/examples/kvstore/tx.go deleted file mode 100644 index 9c9cb8955677..000000000000 --- a/docs/examples/kvstore/tx.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "bytes" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// An sdk.Tx which is its own sdk.Msg. -type kvstoreTx struct { - key []byte - value []byte - bytes []byte -} - -func (tx kvstoreTx) Route() string { - return "kvstore" -} - -func (tx kvstoreTx) Type() string { - return "kvstore" -} - -func (tx kvstoreTx) GetMsgs() []sdk.Msg { - return []sdk.Msg{tx} -} - -func (tx kvstoreTx) GetMemo() string { - return "" -} - -func (tx kvstoreTx) GetSignBytes() []byte { - return sdk.MustSortJSON(tx.bytes) -} - -// Should the app be calling this? Or only handlers? -func (tx kvstoreTx) ValidateBasic() sdk.Error { - return nil -} - -func (tx kvstoreTx) GetSigners() []sdk.AccAddress { - return nil -} - -func (tx kvstoreTx) GetSignatures() []auth.StdSignature { - return nil -} - -// takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has -// all the signatures and can be used to authenticate. -func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx sdk.Tx - - split := bytes.Split(txBytes, []byte("=")) - if len(split) == 1 { - k := split[0] - tx = kvstoreTx{k, k, txBytes} - } else if len(split) == 2 { - k, v := split[0], split[1] - tx = kvstoreTx{k, v, txBytes} - } else { - return nil, sdk.ErrTxDecode("too many =") - } - - return tx, nil -} diff --git a/docs/gaia/deploy-testnet.md b/docs/gaia/deploy-testnet.md index 4a511c503bdf..c617e2052bc8 100644 --- a/docs/gaia/deploy-testnet.md +++ b/docs/gaia/deploy-testnet.md @@ -33,8 +33,8 @@ 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.stake.params.bond_denom denom, the default is STAKE -gaiad add-genesis-account $(gaiacli keys show validator -a) 1000STAKE,1000validatorToken +# 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 # Generate the transaction that creates your validator gaiad gentx --name validator diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index ba2e8c8c014b..5982c30d4eda 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -12,7 +12,17 @@ Must specify these options: --chain-id when --trust-node is false you must choose whether you wish to verify lite client proofs. If you trust the node which you are querying, you can simply pass `--trust-node=true` - otherwise you'll need to specify `--chain-id`. ::: -`gaiacli` is the command line interface to manage accounts and transactions on Cosmos testnets. Here is a list of useful `gaiacli` commands, including usage examples. +`gaiacli` is the command line interface to manage accounts and transactions on Cosmos testnets. +Its configuration file resides in `$HOME/.gaiacli/config/config.toml` and can be edited either +by hand or via the `gaiacli config` command: + +```bash +gaiacli config chain-id gaia-9004 +``` + +For more information on the command usage, refer to its help screen: `gaiacli config --help`. + +Here is a list of useful `gaiacli` commands, including usage examples. ### Keys @@ -87,15 +97,63 @@ Note that this is the Tendermint signing key, _not_ the operator key you will us We strongly recommend _NOT_ using the same passphrase for multiple keys. The Tendermint team and the Interchain Foundation will not be responsible for the loss of funds. ::: -#### Multisig public keys +#### Generate multisig public keys You can generate and print a multisig public key by typing: ```bash -gaiacli show --multisig-threshold K name1 name2 name3 [...] +gaiacli keys add --multisig=name1,name2,name3[...] --multisig-threshold=K new_key_name ``` -`K` is the minimum weight, e.g. minimum number of private keys that must have signed the transactions that carry the generated public key. +`K` is the minimum number of private keys that must have signed the +transactions that carry the public key's address as signer. + +The `--multisig` flag must contain the name of public keys that will be combined into a +public key that will be generated and stored as `new_key_name` in the local database. +All names supplied through `--multisig` must already exist in the local database. Unless +the flag `--nosort` is set, the order in which the keys are supplied on the command line +does not matter, i.e. the following commands generate two identical keys: + +```bash +gaiacli keys add --multisig=foo,bar,baz --multisig-threshold=2 multisig_address +gaiacli keys add --multisig=baz,foo,bar --multisig-threshold=2 multisig_address +``` + +Multisig addresses can also be generated on-the-fly and printed through the which command: + +```bash +gaiacli keys show --multisig-threshold K name1 name2 name3 [...] +``` + +For more information regarding how to generate, sign and broadcast transactions with a +multi signature account see [Multisig Transactions](#multisig-transactions). + +### Fees & Gas + +Each transaction may either supply fees or gas prices, but not both. Most users +will typically provide fees as this is the cost you will end up incurring for +the transaction being included in the ledger. + +Validator's have a minimum gas price (multi-denom) configuration and they use +this value when when determining if they should include the transaction in a block +during `CheckTx`, where `gasPrices >= minGasPrices`. Note, your transaction must +supply fees that match all the denominations the validator requires. + +__Note__: With such a mechanism in place, validators may start to prioritize +txs by `gasPrice` in the mempool, so providing higher fees or gas prices may yield +higher tx priority. + +e.g. + +```bash +gaiacli tx send ... --fees=100photino +``` + +or + +```bash +gaiacli tx send ... --gas-prices=0.000001stake +``` ### Account @@ -134,7 +192,7 @@ The `--amount` flag accepts the format `--amount=`. ::: tip Note You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag. -If you pass `--gas=simulate`, the gas limit will be automatically estimated. +If you pass `--gas=auto`, the gas supply will be automatically estimated before executing the transaction. Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.0. ::: @@ -182,7 +240,7 @@ gaiacli tx sign \ unsignedSendTx.json > signedSendTx.json ``` -You can validate the transaction's signagures by typing the following: +You can validate the transaction's signatures by typing the following: ```bash gaiacli tx sign --validate-signatures signedSendTx.json @@ -214,6 +272,11 @@ And for using multiple `tags`: gaiacli query txs --tags=':&:' ``` +The pagination is supported as well via `page` and `limit`: +```bash +gaiacli query txs --tags=':' --page=1 --limit=20 +``` + ::: tip Note The action tag always equals the message type returned by the `Type()` function of the relevant message. @@ -221,7 +284,7 @@ The action tag always equals the message type returned by the `Type()` function You can find a list of available `tags` on each of the SDK modules: - [Common tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/types/tags.go#L57-L63) -- [Staking tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/stake/tags/tags.go#L8-L24) +- [Staking tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/staking/tags/tags.go#L8-L24) - [Governance tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/gov/tags/tags.go#L8-L22) - [Slashing tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/slashing/handler.go#L52) - [Distribution tags](https://github.com/cosmos/cosmos-sdk/blob/develop/x/distribution/tags/tags.go#L8-L17) @@ -277,13 +340,13 @@ On the upcoming mainnet, you can delegate `atom` to a validator. These [delegato You can query the list of all validators of a specific chain: ```bash -gaiacli query stake validators +gaiacli query staking validators ``` If you want to get the information of a single validator you can check it with: ```bash -gaiacli query stake validator +gaiacli query staking validator ``` #### Bond Tokens @@ -291,7 +354,7 @@ gaiacli query stake validator On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator (_i.e._ delegate): ```bash -gaiacli tx stake delegate \ +gaiacli tx staking delegate \ --amount=10steak \ --validator= \ --from= \ @@ -317,15 +380,13 @@ Don't use more `steak` thank you have! You can always get more by using the [Fau Once submitted a delegation to a validator, you can see it's information by using the following command: ```bash -gaiacli query stake delegation \ - --address-delegator= \ - --validator= +gaiacli query staking delegation ``` Or if you want to check all your current delegations with disctinct validators: ```bash -gaiacli query stake delegations +gaiacli query staking delegations ``` You can also get previous delegation(s) status by adding the `--height` flag. @@ -335,7 +396,7 @@ You can also get previous delegation(s) status by adding the `--height` flag. If for any reason the validator misbehaves, or you just want to unbond a certain amount of tokens, use this following command. You can unbond a specific `shares-amount` (eg:`12.1`\) or a `shares-fraction` (eg:`0.25`) with the corresponding flags. ```bash -gaiacli tx stake unbond \ +gaiacli tx staking unbond \ --validator= \ --shares-fraction=0.5 \ --from= \ @@ -349,21 +410,19 @@ The unbonding will be automatically completed when the unbonding period has pass Once you begin an unbonding-delegation, you can see it's information by using the following command: ```bash -gaiacli query stake unbonding-delegation \ - --address-delegator= \ - --validator= \ +gaiacli query staking unbonding-delegation ``` Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli query stake unbonding-delegations +gaiacli query staking unbonding-delegations ``` Additionally, as you can get all the unbonding-delegations from a particular validator: ```bash - gaiacli query stake unbonding-delegations-from +gaiacli query staking unbonding-delegations-from ``` To get previous unbonding-delegation(s) status on past blocks, try adding the `--height` flag. @@ -373,7 +432,7 @@ To get previous unbonding-delegation(s) status on past blocks, try adding the `- A redelegation is a type delegation that allows you to bond illiquid tokens from one validator to another: ```bash -gaiacli tx stake redelegate \ +gaiacli tx staking redelegate \ --addr-validator-source= \ --addr-validator-dest= \ --shares-fraction=50 \ @@ -390,22 +449,19 @@ The redelegation will be automatically completed when the unbonding period has p Once you begin an redelegation, you can see it's information by using the following command: ```bash -gaiacli query stake redelegation \ - --address-delegator= \ - --addr-validator-source= \ - --addr-validator-dest= \ +gaiacli query staking redelegation ``` Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli query stake redelegations +gaiacli query staking redelegations ``` Additionally, as you can get all the outgoing redelegations from a particular validator: ```bash - gaiacli query stake redelegations-from + gaiacli query staking redelegations-from ``` To get previous redelegation(s) status on past blocks, try adding the `--height` flag. @@ -415,7 +471,7 @@ To get previous redelegation(s) status on past blocks, try adding the `--height` Parameters define high level settings for staking. You can get the current values by using: ```bash -gaiacli query stake parameters +gaiacli query staking params ``` With the above command you will get the values for: @@ -431,14 +487,14 @@ All these values will be subject to updates though a `governance` process by `Pa A staking `Pool` defines the dynamic parameters of the current state. You can query them with the following command: ```bash -gaiacli query stake pool +gaiacli query staking pool ``` With the `pool` command you will get the values for: -- Loose and bonded tokens +- Not-bonded and bonded tokens - Token supply -- Current anual inflation and the block in which the last inflation was processed +- Current annual inflation and the block in which the last inflation was processed - Last recorded bonded shares ##### Query Delegations To Validator @@ -498,6 +554,12 @@ gaiacli query gov proposals You can also query proposals filtered by `voter` or `depositor` by using the corresponding flags. +To query for the proposer of a given governance proposal: + +```bash +gaiacli query gov proposer +``` + #### Increase deposit In order for a proposal to be broadcasted to the network, the amount deposited must be above a `minDeposit` value (default: `10 steak`). If the proposal you previously created didn't meet this requirement, you can still increase the total amount deposited to activate it. Once the minimum deposit is reached, the proposal enters voting period: @@ -560,8 +622,174 @@ gaiacli query gov tally To check the current governance parameters run: +```bash +gaiacli query gov params +``` + +To query subsets of the governance parameters run: + ```bash gaiacli query gov param voting gaiacli query gov param tallying gaiacli query gov param deposit ``` + +### Fee Distribution + +#### Query distribution parameters + +To check the current distribution parameters, run: + +```bash +gaiacli query distr params +``` + +#### Query outstanding rewards + +To check the current outstanding (un-withdrawn) rewards, run: + +```bash +gaiacli query distr outstanding-rewards +``` + +#### Query validator commission + +To check the current outstanding commission for a validator, run: + +```bash +gaiacli query distr commission +``` + +#### Query validator slashes + +To check historical slashes for a validator, run: + +```bash +gaiacli query distr slashes +``` + +#### Query delegator rewards + +To check current rewards for a delegation (were they to be withdrawn), run: + +```bash +gaiacli query distr rewards +``` + +### Multisig transactions + +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 +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 +by a distinct party, the user holding `p1` would require to import both `p2` and `p3` in order to +generate the multisig account public key: + +``` +gaiacli keys add \ + --pubkey=cosmospub1addwnpepqtd28uwa0yxtwal5223qqr5aqf5y57tc7kk7z8qd4zplrdlk5ez5kdnlrj4 \ + p2 + +gaiacli keys add \ + --pubkey=cosmospub1addwnpepqgj04jpm9wrdml5qnss9kjxkmxzywuklnkj0g3a3f8l5wx9z4ennz84ym5t \ + p3 + +gaiacli keys add \ + --multisig-threshold=2 + --multisig=p1,p2,p3 + p1p2p3 +``` + +A new multisig public key `p1p2p3` has been stored, and its address will be +used as signer of multisig transactions: + +```bash +gaiacli keys show --address p1p2p3 +``` + +The first step to create a multisig transaction is to initiate it on behalf +of the multisig address created above: + +```bash +gaiacli tx send \ + --from= \ + --to=cosmos1570v2fq3twt0f0x02vhxpuzc9jc4yl30q2qned \ + --amount=10stake \ + --generate-only > unsignedTx.json +``` + +The file `unsignedTx.json` contains the unsigned transaction encoded in JSON. +`p1` can now sign the transaction with its own private key: + +```bash +gaiacli tx sign \ + --multisig= \ + --name=p1 \ + --output-document=p1signature.json \ + unsignedTx.json +``` + +Once the signature is generated, `p1` transmits both `unsignedTx.json` and +`p1signature.json` to `p2` or `p3`, which in turn will generate their +respective signature: + +```bash +gaiacli tx sign \ + --multisig= \ + --name=p2 \ + --output-document=p2signature.json \ + unsignedTx.json +``` + +`p1p2p3` is a 2-of-3 multisig key, therefore one additional signature +is sufficient. Any the key holders can now generate the multisig +transaction by combining the required signature files: + +```bash +gaiacli tx multisign \ + unsignedTx.json \ + p1p2p3 \ + p1signature.json p2signature.json > signedTx.json +``` + +The transaction can now be sent to the node: + +```bash +gaiacli tx broadcast signedTx.json +``` + +## Shells completion scripts + +Completion scripts for popular UNIX shell interpreters such as `Bash` and `Zsh` +can be generated through the `completion` command, which is available for both +`gaiad` and `gaiacli`. + +If you want to generate `Bash` completion scripts run the following command: + +```bash +gaiad completion > gaiad_completion +gaiacli completion > gaiacli_completion +``` + +If you want to generate `Zsh` completion scripts run the following command: + +```bash +gaiad completion --zsh > gaiad_completion +gaiacli completion --zsh > gaiacli_completion +``` + +::: tip Note +On most UNIX systems, such scripts may be loaded in `.bashrc` or +`.bash_profile` to enable Bash autocompletion: + +```bash +echo '. gaiad_completion' >> ~/.bashrc +echo '. gaiacli_completion' >> ~/.bashrc +``` + +Refer to the user's manual of your interpreter provided by your +operating system for information on how to enable shell autocompletion. +::: diff --git a/docs/gaia/installation.md b/docs/gaia/installation.md index 32a2524b26db..59d6d35a7c26 100644 --- a/docs/gaia/installation.md +++ b/docs/gaia/installation.md @@ -14,7 +14,7 @@ echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile ``` ::: tip -**Go 1.11+** is required for the Cosmos SDK. +**Go 1.11.4+** is required for the Cosmos SDK. ::: ### Install the binaries @@ -28,7 +28,7 @@ mkdir -p $GOPATH/src/github.com/cosmos cd $GOPATH/src/github.com/cosmos git clone https://github.com/cosmos/cosmos-sdk cd cosmos-sdk && git checkout master -make get_tools && make get_vendor_deps && make install +make tools && make get_vendor_deps && make install ``` > *NOTE*: If you have issues at this step, please check that you have the latest stable version of GO installed. @@ -42,4 +42,4 @@ $ gaiacli version ### Next -Now you can [join the public testnet](./join-testnet.md) or [create you own private testnet](./private-testnet.md) \ No newline at end of file +Now you can [join the public testnet](./join-testnet.md) or [create you own testnet](./deploy-testnet.md) diff --git a/docs/gaia/join-testnet.md b/docs/gaia/join-testnet.md index b09b8fc1cf4e..2cb2fedf87c6 100644 --- a/docs/gaia/join-testnet.md +++ b/docs/gaia/join-testnet.md @@ -1,4 +1,4 @@ -# Join Public Testnet +# Join the public testnet ::: tip Current Testnet See the [testnet repo](https://github.com/cosmos/testnets) for diff --git a/docs/gaia/validators/validator-setup.md b/docs/gaia/validators/validator-setup.md index 99858f429244..cac1508d0402 100644 --- a/docs/gaia/validators/validator-setup.md +++ b/docs/gaia/validators/validator-setup.md @@ -1,4 +1,4 @@ -# Run a Validator on Public Testnet +# Run a Validator on the public testnet ::: tip Information on how to join the current testnet (`genesis.json` file and seeds) is held [in our `testnet` repo](https://github.com/cosmos/testnets/tree/master/latest). Please check there if you are looking to join our latest testnet. @@ -6,17 +6,17 @@ Information on how to join the current testnet (`genesis.json` file and seeds) i __Note__: This documentation is only intended for validators of the **public testnet** -Before setting up your validator node, make sure you've already gone through the [Full Node Setup](/docs/getting-started/full-node.md) guide. +Before setting up your validator node, make sure you've already gone through the [Full Node Setup](../join-testnet.md) guide. ## What is a Validator? -[Validators](/validators/overview.md) are responsible for committing new blocks to the blockchain through voting. A validator's stake is slashed if they become unavailable or sign blocks at the same height. Please read about [Sentry Node Architecture](/validators/validator-faq.md#how-can-validators-protect-themselves-from-denial-of-service-attacks) to protect your node from DDOS attacks and to ensure high-availability. +[Validators](./overview.md) are responsible for committing new blocks to the blockchain through voting. A validator's stake is slashed if they become unavailable or sign blocks at the same height. Please read about [Sentry Node Architecture](./validator-faq.md#how-can-validators-protect-themselves-from-denial-of-service-attacks) to protect your node from DDOS attacks and to ensure high-availability. ::: danger Warning -If you want to become a validator for the Hub's `mainnet`, you should [research security](/validators/security.md). +If you want to become a validator for the Hub's `mainnet`, you should [research security](./security.md). ::: -You may want to skip the next section if you have already [set up a full-node](/join-testnet.md). +You may want to skip the next section if you have already [set up a full-node](../join-testnet.md). ## Create Your Validator @@ -29,12 +29,12 @@ gaiad tendermint show-validator Next, craft your `gaiad gentx` command: ::: warning Note -Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)! +Don't use more `STAKE` thank you have! You can always get more by using the [Faucet](https://faucet.cosmos.network/)! ::: ```bash -gaiacli tx stake create-validator \ - --amount=5steak \ +gaiacli tx staking create-validator \ + --amount=5STAKE \ --pubkey=$(gaiad tendermint show-validator) \ --moniker="choose a moniker" \ --chain-id= \ @@ -91,8 +91,8 @@ A `gentx` is a JSON file carrying a self-delegation. All genesis transactions ar 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 stake create-validator \ - --amount=5steak \ +gaiacli tx staking create-validator \ + --amount=5STAKE \ --pubkey=$(gaiad tendermint show-validator) \ --moniker="choose a moniker" \ --chain-id= \ @@ -140,7 +140,7 @@ Once you've collected all genesis transactions in `~/.gaiad/config/gentx`, you c gaiad collect-gentxs ``` -__Note:__ The accounts from which you delegate in the `gentx` transactions need to possess staking tokens in the genesis file, otherwise `collect-gentx` will fail. +__Note:__ The accounts from which you delegate in the `gentx` transactions need to possess stake tokens in the genesis file, otherwise `collect-gentx` will fail. The previous command will collect all genesis transactions and finalise `genesis.json`. To verify the correctness of the configuration and start the node run: @@ -155,7 +155,7 @@ You can edit your validator's public description. This info is to identify your The `--identity` can be used as to verify identity with systems like Keybase or UPort. When using with Keybase `--identity` should be populated with a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile. ```bash -gaiacli tx stake edit-validator +gaiacli tx staking edit-validator --moniker="choose a moniker" \ --website="https://cosmos.network" \ --identity=6A0D65E29A4CBC8E \ @@ -177,7 +177,7 @@ __Note__: The `commission-rate` value must adhere to the following invariants: View the validator's information with this command: ```bash -gaiacli query stake validator +gaiacli query staking validator ``` ## Track Validator Signing Information diff --git a/docs/gaia/what-is-gaia.md b/docs/gaia/what-is-gaia.md new file mode 100644 index 000000000000..cd5df8cab5ea --- /dev/null +++ b/docs/gaia/what-is-gaia.md @@ -0,0 +1,22 @@ +# What is Gaia? + +`gaia` is the name of the Cosmos SDK application for the Cosmos Hub. It comes with 2 main entrypoints: + +- `gaiad`: The Gaia Daemon, runs a full-node of the `gaia` application. +- `gaiacli`: The Gaia command-line interface, which enables interraction with a Gaia full-node. + +`gaia` is built on the Cosmos SDK using the following modules: + +- `x/auth`: Accounts and signatures. +- `x/bank`: Token transfers. +- `x/staking`: Staking logic. +- `x/mint`: Inflation logic. +- `x/distribution`: Fee distribution logic. +- `x/slashing`: Slashing logic. +- `x/gov`: Governance logic. +- `x/ibc`: Inter-blockchain transfers. +- `x/params`: Handles app-level parameters. + +>About the Cosmos Hub: The Cosmos Hub is the first Hub to be launched in the Cosmos Network. The role of a Hub is to facilitate transfers between blockchains. If a blockchain connects to a Hub via IBC, it automatically gains access to all the other blockchains that are connected to it. The Cosmos Hub is a public Proof-of-Stake chain. Its staking token is called the Atom. + +Next, learn how to [install Gaia](./installation.md). diff --git a/docs/intro/README.md b/docs/intro/README.md index 07ebb4eac718..58e842785243 100644 --- a/docs/intro/README.md +++ b/docs/intro/README.md @@ -10,34 +10,4 @@ It is based on two major principles: - **Capabilities:** The SDK is inspired by capabilities-based security, and informed by years of wrestling with blockchain state-machines. Most developers will need to access other 3rd party modules when building their own modules. Given that the Cosmos-SDK is an open framework, some of the modules may be malicious, which means there is a need for security principles to reason about inter-module interactions. These principles are based on object-cababilities. In practice, this means that instead of having each module keep an access control list for other modules, each module implements special objects called keepers that can be passed to other modules to grant a pre-defined set of capabilities. For example, if an instance of module A's keepers is passed to module B, the latter will be able to call a restricted set of module A's functions. The capabilities of each keeper are defined by the module's developer, and it's the developer's job to understand and audit the safety of foreign code from 3rd party modules based on the capabilities they are passing into each third party module. For a deeper look at capabilities, jump to [this section](./ocap.md). -## Learn more about the SDK - -- [SDK application architecture](./sdk-app-architecture.md) -- [SDK security paradigm: ocap](./ocap.md) - -## Creating a new SDK project - -To create a new project, you can either: - -- Fork [this repo](https://github.com/cosmos/sdk-application-tutorial/). Do not forget to remove the `nameservice` module from the various files if you don't need it. -- Copy the `docs/examples/basecoin` directory. -- Use community tools! More info to come. - -## SDK Directory Structure - -The SDK is laid out in the following directories: - -- `baseapp`: Defines the template for a basic [ABCI](https://github.com/tendermint/tendermint/tree/master/abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node. -- `client`: CLI and REST server tooling for interacting with SDK application. -- `docs/examples`: Examples of how to build working standalone applications. -- `server`: The full node server for running an SDK application on top of - Tendermint. -- `store`: The database of the SDK - a Merkle multistore supporting multiple types of underling Merkle key-value stores. -- `types`: Common types in SDK applications. -- `x`: Extensions to the core, where all messages and handlers are defined. - -## Languages - -The Cosmos-SDK is currently written in [Golang](https://golang.org/), though the -framework could be implemented similarly in other languages. -Contact us for information about funding an implementation in another language. +### Next, learn more about the [SDK Application Architecture](./sdk-app-architecture.md) \ No newline at end of file diff --git a/docs/intro/ocap.md b/docs/intro/ocap.md index cea7c108dcf2..444377e99548 100644 --- a/docs/intro/ocap.md +++ b/docs/intro/ocap.md @@ -77,26 +77,14 @@ var sumValue := externalModule.ComputeSumValue(*account) ``` In the Cosmos SDK, you can see the application of this principle in the -[basecoin examples folder](../examples/basecoin). +[gaia app](../gaia/app/app.go). ```go -// File: cosmos-sdk/docs/examples/basecoin/app/init_handlers.go -package app - -import ( - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/sketchy" -) - -func (app *BasecoinApp) initRouterHandlers() { - - // All handlers must be added here. - // The order matters. - app.router.AddRoute("bank", bank.NewHandler(app.accountKeeper)) - app.router.AddRoute("sketchy", sketchy.NewHandler()) -} +// register message routes +app.Router(). + AddRoute(bank.RouterKey, bank.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)) ``` - -In the Basecoin example, the sketchy handler isn't provided an account -mapper, which does provide the bank handler with the capability (in -conjunction with the context of a transaction run). diff --git a/docs/intro/sdk-app-architecture.md b/docs/intro/sdk-app-architecture.md index 4b302bd33439..17c75836da92 100644 --- a/docs/intro/sdk-app-architecture.md +++ b/docs/intro/sdk-app-architecture.md @@ -1,12 +1,67 @@ # SDK Application Architecture -## Parts of a SDK blockchain +## State machine -A blockchain application is just a [replicated deterministic state machine](https://en.wikipedia.org/wiki/State_machine_replication). As a developer, you just have to define the state machine (i.e. what the state, a starting state and messages that trigger state transitions), and [*Tendermint*](https://tendermint.com/docs/introduction/introduction.html) will handle replication over the network for you. +At its core, a blockchain application is a [replicated deterministic state machine](https://en.wikipedia.org/wiki/State_machine_replication). ->Tendermint is an application-agnostic engine that is responsible for handling the *networking* and *consensus* layers of your blockchain. In practice, this means that Tendermint is reponsible for propagating and ordering transaction bytes. Tendermint Core relies on an eponymous Byzantine-Fault-Tolerant (BFT) algorithm to reach consensus on the order of transactions. For more on Tendermint, click [here](https://tendermint.com/docs/introduction/introduction.html). +A state machine is a computer science concept whereby a machine can have multiple states, but only one at any given time. There is a state, which describes the current state of the system, and transactions, that trigger state transitions. -Tendermint passes transactions from the network to the application through an interface called the [ABCI](https://github.com/tendermint/tendermint/tree/master/abci. If you look at the architecture of the blockchain node you are building, it looks like the following: +Given a state S and a transaction T, the state machine will return a new state S'. + +``` ++--------+ +--------+ +| | | | +| S +---------------->+ S' | +| | apply(T) | | ++--------+ +--------+ +``` + +In practice, the transactions are bundled in blocks to make the process more efficient. Given a state S and a block of transactions B, the state machine will return a new state S'. + +``` ++--------+ +--------+ +| | | | +| S +----------------------------> | S' | +| | For each T in B: apply(T) | | ++--------+ +--------+ +``` + +In a blockchain context, the state machine is deterministic. This means that if you start at a given state and replay the same sequence of transactions, you will always end up with the same final state. + +The Cosmos SDK gives you maximum flexibility to define the state of your application, transaction types and state-transition functions. The process of building the state-machine with the SDK will be described more in depth in the following sections. But first, let us see how it is replicated using **Tendermint**. + +## Tendermint + +As a developer, you just have to define the state machine using the Cosmos-SDK, and [*Tendermint*](https://tendermint.com/docs/introduction/introduction.html) will handle replication over the network for you. + + +``` + ^ +-------------------------------+ ^ + | | | | Built with Cosmos SDK + | | State-machine = Application | | + | | | v + | +-------------------------------+ + | | | ^ +Blockchain node | | Consensus | | + | | | | + | +-------------------------------+ | Tendermint Core + | | | | + | | Networking | | + | | | | + v +-------------------------------+ v +``` + + +Tendermint is an application-agnostic engine that is responsible for handling the *networking* and *consensus* layers of your blockchain. In practice, this means that Tendermint is reponsible for propagating and ordering transaction bytes. Tendermint Core relies on an eponymous Byzantine-Fault-Tolerant (BFT) algorithm to reach consensus on the order of transactions. For more on Tendermint, click [here](https://tendermint.com/docs/introduction/introduction.html). + +Tendermint consensus algorithm works with a set of special nodes called *Validators*. Validators are responsible for adding blocks of transactions to the blockchain. At any given block, there is a validator set V. A validator in V is chosen by the algorithm to be the proposer of the next block. This block is considered valid if more than two thirds of V signed a *[prevote](https://tendermint.com/docs/spec/consensus/consensus.html#prevote-step-height-h-round-r)* and a *[precommit](https://tendermint.com/docs/spec/consensus/consensus.html#precommit-step-height-h-round-r)* on it, and if all the transactions that it contains are valid. The validator set can be changed by rules written in the state-machine. For a deeper look at the algorithm, click [here](https://tendermint.com/docs/introduction/what-is-tendermint.html#consensus-overview). + + +The main part of a Cosmos SDK application is a blockchain daemon that is run by each node in the network locally. If less than one third of the *validator set* is byzantine (i.e. malicious), then each node should obtain the same result when querying the state at the same time. + +## ABCI + +Tendermint passes transactions from the network to the application through an interface called the [ABCI](https://github.com/tendermint/tendermint/tree/master/abci), which the application must implement. ``` +---------------------+ @@ -26,37 +81,16 @@ Tendermint passes transactions from the network to the application through an in +---------------------+ ``` -Fortunately, you do not have to implement the ABCI interface. The Cosmos SDK provides a boilerplate implementation of it in the form of [baseapp](#baseapp). - -## BaseApp - -Implements an ABCI App using a [MultiStore](../reference/store) for persistence and a Router to handle transactions. -The goal is to provide a secure interface between the store and the extensible state machine while defining as little about that state machine as possible (staying true to the ABCI). - -For more on `baseapp`, please go to the [baseapp reference](../reference/baseapp.md). - -## Modules - -The power of the SDK lies in its modularity. SDK blockchains are built out of customizable and interoperable modules. These modules are contained in the `x/` folder. - -In addition to the already existing modules in `x/`, that anyone can use in their app, the SDK lets you build your own custom modules. In other words, building a SDK blockchain consists in importing some modules and building others (the ones you need that do not exist yet!). - -Some core modules include: - -- `x/auth`: Used to manage accounts and signatures. -- `x/bank`: Used to enable tokens and token transfers. -- `x/staking` + `x/slashing`: Used to build Proof-Of-Stake blockchains. - -## Basecoin - -Basecoin is the first complete application in the stack. Complete applications require extensions to the core modules of the SDK to actually implement handler functionality. - -Basecoin implements a `BaseApp` state machine using the `x/auth` and `x/bank` modules, which define how transaction signers are authenticated and how coins are transferred. It should also use `x/ibc` and probably a simple staking extension. +Note that Tendermint only handles transaction bytes. It has no knowledge of what these bytes realy mean. All Tendermint does is to order them deterministically. It is the job of the application to give meaning to these bytes. Tendermint passes the bytes to the application via the ABCI, and expects a return code to inform it if the message was succesful or not. -Basecoin and the native `x/` extensions use go-amino for all serialization needs, including for transactions and accounts. +Here are the most important messages of the ABCI: -## SDK tutorial +- `CheckTx`: When a transaction is received by Tendermint Core, it is passed to the application to check its validity. A special handler called the "Ante Handler" is used to execute a series of validation steps such as checking for sufficient fees and validating the signatures. If the transaction is valid, the transaction is added to the [mempool](https://tendermint.com/docs/spec/reactors/mempool/functionality.html#mempool-functionality) and relayed to peer nodes. Note that transactions are not processed (i.e. no modification of the state occurs) with `CheckTx` since they have not been included in a block yet. +- `DeliverTx`: When a [valid block](https://tendermint.com/docs/spec/blockchain/blockchain.html#validation) is received by Tendermint Core, each transaction in the given block is passed to the application via `DeliverTx` to be processed. It is during this stage where the state transitions occur. The "Ante Handler" executes again along with the actual handlers for each message in the transaction. + - `BeginBlock`/`EndBlock`: These messages are executed at the beginning and the end of each block, whether the block contains transaction or not. It is useful to trigger automatic execution of logic. Proceed with caution though, as computationally expensive loops could slow down your blockchain, or even freeze it if the loop is infinite. -If you want to learn more about how to build an SDK application and get a deepeer understanding of the concepts presented above in the process, please check out the [SDK Application Tutorial](https://github.com/cosmos/sdk-application-tutorial). +For a more detailed view of the ABCI methods and types, click [here](https://tendermint.com/docs/spec/abci/abci.html#overview). +Any application built on Tendermint needs to implement the ABCI interface in order to communicate with the underlying local Tendermint engine. Fortunately, you do not have to implement the ABCI interface. The Cosmos SDK provides a boilerplate implementation of it in the form of [baseapp](./sdk-design.md#baseapp). +### Next, let us go into the [high-level design principles of the SDK](./sdk-design.md) diff --git a/docs/intro/sdk-design.md b/docs/intro/sdk-design.md new file mode 100644 index 000000000000..4202b7b2fdb0 --- /dev/null +++ b/docs/intro/sdk-design.md @@ -0,0 +1,88 @@ +# Cosmos SDK design overview + +The Cosmos SDK is a framework that facilitates the development of secure state-machines on top of Tendermint. At its core, the SDK is a boilerplate implementation of the ABCI in Golang. It comes with a `multistore` to persist data and a `router` to handle transactions. + +Here is a simplified view of how transactions are handled by an application built on top of the Cosmos SDK when transferred from Tendermint via `DeliverTx` +(the `CheckTx` process is the same without enforcing state changes): + +1. Decode transactions received from the Tendermint consensus engine (remember that Tendermint only deals with `[]bytes`). +2. Extract messages from transactions and do basic sanity checks. +3. Route each message to the appropriate module so that it can be processed. +4. Commit the state changes. + +The application also enables you to generate transactions, encode them and pass them to the underlying Tendermint engine to broadcast them. + +## `baseapp` + +`baseApp` is the boilerplate implementation of the ABCI of the Cosmos SDK. It comes with a `router` to route transactions to their respective module. The main `app.go` file of your application will define your custom `app` type that will embed `baseapp`. This way, your custom `app` type will automatically inherit all the ABCI methods of `baseapp`. See an example of this in the [SDK application tutorial](https://github.com/cosmos/sdk-application-tutorial/blob/master/app.go#L27). + +The goal of `baseapp` to provide a secure interface between the store and the extensible state machine while defining as little about that state machine as possible (staying true to the ABCI). + +For more on `baseapp`, please click [here](../concepts/baseapp.md). + +## Multistore + + The Cosmos SDK provides a multistore for persisting state. The multistore allows developers to declare any number of [`KVStores`](https://github.com/blocklayerhq/chainkit). These `KVStores` only accept the `[]byte` type as value and therefore any custom structure needs to be marshalled using [go-amino](https://github.com/tendermint/go-amino) before being stored. + +The multistore abstraction is used to divide the state in distinct compartments, each managed by its own module. For more on the multistore, click [here](../concepts/store.md) + +## Modules + +The power of the Cosmos SDK lies in its modularity. SDK applications are built by aggregating a collection of interoperable modules. Each module defines a subset of the state and contains its own message/transaction processor, while the SDK is responsible for routing each message to its respective module. + +``` + + + | + | Transaction relayed from Tendermint + | via DeliverTx + | + | + +---------------------v--------------------------+ + | APPLICATION | + | | + | Using baseapp's methods: Decode the Tx, | + | extract and route the message(s) | + | | + +---------------------+--------------------------+ + | + | + | + +---------------------------+ + | + | + | + | Message routed to the correct + | module to be processed + | + | ++----------------+ +---------------+ +----------------+ +------v----------+ +| | | | | | | | +| AUTH MODULE | | BANK MODULE | | STAKING MODULE | | GOV MODULE | +| | | | | | | | +| | | | | | | Handles message,| +| | | | | | | Updates state | +| | | | | | | | ++----------------+ +---------------+ +----------------+ +------+----------+ + | + | + | + | + +--------------------------+ + | + | Return result to Tendermint + | (0=Ok, 1=Err) + v +``` + +Each module can be seen as a little state-machine. Developers need to define the subset of the state handled by the module, as well as custom message types that modify the state (*Note:* Messages are extracted from transactions in `baseapp`'s methods). In general, each module declares its own `KVStore` in the multistore to persist the subset of the state it defines. Most developers will need to access other 3rd party modules when building their own modules. Given that the Cosmos-SDK is an open framework, some of the modules may be malicious, which means there is a need for security principles to reason about inter-module interactions. These principles are based on [object-cababilities](./ocap.md). In practice, this means that instead of having each module keep an access control list for other modules, each module implements special objects called keepers that can be passed to other modules to grant a pre-defined set of capabilities. + +SDK modules are defined in the `.x/` folder of the SDK. Some core modules include: + +- `x/auth`: Used to manage accounts and signatures. +- `x/bank`: Used to enable tokens and token transfers. +- `x/staking` + `x/slashing`: Used to build Proof-Of-Stake blockchains. + +In addition to the already existing modules in `x/`, that anyone can use in their app, the SDK lets you build your own custom modules. You can check an [example of that in the tutorial](https://cosmos.network/docs/tutorial/keeper.html). + + +### Next, learn more about the security model of the Cosmos SDK, [ocap](./ocap.md) diff --git a/docs/modules/README.md b/docs/modules/README.md index 53e02687ea78..665a26a78b67 100644 --- a/docs/modules/README.md +++ b/docs/modules/README.md @@ -6,9 +6,9 @@ See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank). # Stake -The `x/stake` module is for Cosmos Delegated-Proof-of-Stake. +The `x/staking` module is for Cosmos Delegated-Proof-of-Stake. -See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/stake). +See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/staking). See the [specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/staking) diff --git a/docs/spec/SPEC-SPEC.md b/docs/spec/SPEC-SPEC.md new file mode 100644 index 000000000000..e53fe74b3240 --- /dev/null +++ b/docs/spec/SPEC-SPEC.md @@ -0,0 +1,45 @@ +# Specification of Specifications + +This file intends to outline the common structure for specifications within +this directory. + +## Tense + +For consistency, specs should be written in passive present tense. + +## 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 + + - `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 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 + +### Notation for key-value mapping + +Within `state.md` the following notation `->` should be used to describe key to +value mapping: + +``` +key -> value +``` + +to represent byte concatenation the `|` may be used. In addition, encoding +type may be specified, for example: + +``` +0x00 | addressBytes | address2Bytes -> amino(value_object) +``` + +Additionally, index mappings may be specified by mapping to the `nil` value, for example: + +``` +0x01 | address2Bytes | addressBytes -> nil +``` diff --git a/docs/spec/ics/ics-030-signed-messages.md b/docs/spec/_ics/ics-030-signed-messages.md similarity index 100% rename from docs/spec/ics/ics-030-signed-messages.md rename to docs/spec/_ics/ics-030-signed-messages.md diff --git a/docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.pdf b/docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.pdf new file mode 100644 index 000000000000..b9995386957c Binary files /dev/null and b/docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.pdf differ diff --git a/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex b/docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.tex similarity index 83% rename from docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex rename to docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.tex index 2a4c2ba3965c..b6bb6b3265a2 100644 --- a/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.tex +++ b/docs/spec/_proposals/f1-fee-distribution/f1_fee_distr.tex @@ -2,7 +2,7 @@ \usepackage{hyperref} %opening -\title{F1 Fee Distribution Draft-01} +\title{F1 Fee Distribution Draft-02} \author{Dev Ojha} \begin{document} @@ -12,7 +12,7 @@ \begin{abstract} In a proof of stake blockchain, validators need to split the rewards gained from transaction fees each block. Furthermore, these fees must be fairly distributed to each of a validator's constituent delegators. They accrue this reward throughout the entire time they are delegated, and they have a special operation to withdraw accrued rewards. - The F1 fee distribution scheme works for any algorithm to split funds between validators each block, with minimal iteration, and the only approximations being due to finite decimal precision. Per block there is a single iteration over the validator set, which enables only rewarding validators who signed a given block. No iteration is required to delegate, and withdrawing only requires iterating over all of that validators slashes since delegation it began. State usage is minimal as well, one state update per validator per block, and one state record per delegator. + The F1 fee distribution scheme works for any algorithm to split funds between validators each block, with minimal iteration, and the only approximations being due to finite decimal precision. Per block there is a single iteration over the validator set, to enable reward algorithms that differ by validator. No iteration is required to delegate, or withdraw. The state usage is one state update per validator per block, and one state entry per active delegation. It can optionally handle arbitrary inflation schemes, and auto-bonding of rewards. \end{abstract} \section{F1 Fee Distribution} @@ -22,13 +22,11 @@ \subsection{Context} Transaction fees get rewarded to validators based on the incentive scheme of the underlying proof of stake model. The fee distribution problem occurs in proof of stake blockchains supporting delegation, as there is a need to distribute a validator's fee rewards to its delegators. The trivial solution of just giving the rewards to each delegator every block is too expensive to perform on-chain. -So instead fee distribution algorithms have delegators perform an explicit withdraw transaction, which when performed yields the same total amount of fees as if they had received them at every block. +So instead fee distribution algorithms have delegators perform a withdraw action, which when performed yields the same total amount of fees as if they had received them at every block. This details F1, an approximation-free, slash-tolerant fee distribution algorithm which allows validator commission-rates, inflation rates, and fee proportions, which can all efficiently change per validator, every block. -The algorithm requires iterating over the validators every block, and withdraws require iterating over all of the corresponding validator's slashes whilst the delegator was bonded. -The former iteration is cheap, due to staking logic already requiring iteration over all validators, which causes the expensive state-reads to be cached. -The number of slashes is expected to be 0 or 1 for most validators, -so the latter term also meets the blockchain's efficiency needs. +The algorithm requires iterating over the bonded validators every block, and withdraws require no iteration. +This is cheap, due to staking logic already requiring iteration over all validators, which causes the expensive state-reads to be cached. The key point of how F1 works is that it tracks how much rewards a delegator with 1 stake for a given validator would be entitled to if it had bonded at block 0 until the latest block. When a delegator bonds at block $b$, the amount of rewards a delegator with 1 stake would have if bonded at block 0 until block $b$ is also persisted to state. @@ -98,17 +96,21 @@ \subsection{Slashing} \label{ssec:slashing} Slashing is distinct from withdrawals, since it lowers the stake of all of the delegator's by a fixed percentage. Since no one is charged gas for slashes, a slash cannot iterate over all delegators. -Thus we can no longer just multiply by $x$ over the difference in stake. +Thus we can no longer just multiply by $x$ over the difference in stake. +This section describes a simple solution that should suffice for most chains needs. An asymptotically optimal solution is provided in section 2.4. +TODO: Consider removing this section in favor of just using the current section 2.4? + The solution here is to instead store each period created by a slash in the validators state. Then when withdrawing, you must iterate over all slashes between when you started and ended. -Suppose you delegated at period $0$, a y\% slash occured at period $2$, and your withdrawal is period $4$. -Then you receive funds from $0$ to $2$ as normal. -The equations for funds you receive for $2$ to $4$ now uses $(1 - y)x$ for your stake instead of just $x$ stake. +Suppose you delegated at period $0$, a y\% slash occured at period $2$, and your withdrawal creates period $4$. +Then you receive funds from periods $0$ to $2$ as normal. +The equations for funds you receive for periods $2$ to $4$ now uses $(1 - y)x$ for your stake instead of just $x$ stake. When there are multiple slashes, you just account for the accumulated slash factor. -In practice this will not really be an efficiency hit, as we can expect most validators to have no slashes. -Validators that get slashed a lot will naturally lose their delegators. -A malicious validator that gets itself slashed many times would increase the gas to withdraw linearly, but the economic loss of funds due to the slashes should far out-weigh the extra overhead the honest withdrawer must pay for due to the gas. +In practice this will not really be an efficiency hit, as the number of slashes is expected to be 0 or 1 for most validators. +Validators that get slashed more will naturally lose their delegators. +A malicious validator that gets itself slashed many times would increase the gas to withdraw linearly, but the economic loss of funds due to the slashes is expected to far out-weigh the extra overhead the honest withdrawer must pay for due to the gas. +(TODO: frame that above sentence in terms of griefing factors, as thats more correct) \subsection{Inflation} Inflation is the idea that we want every staked coin to create more staking tokens as time progresses. @@ -158,7 +160,7 @@ \subsection{Inflation} Thus every block, each validator just has to add the total amount of fees (The $R_i$ term) that goes to delegates to some per-period term. When creating a new period, $n_{start(f)}$ can be cached in state, and the product is already stored in the previous periods state entry. -You then get the next period's $n_{start(f)}$ from the current tm-power entry. +You then get the next period's $n_{start(f)}$ from the consensus' power entry for this validator. This is thus extremely efficient per block. When withdrawing, you take the difference as before, @@ -166,20 +168,20 @@ \subsection{Inflation} $(\prod_0^{begin\ bonding\ period}1 + x)$ is known, since its included in the state entry for when you bonded. You then divide the entitled fees by $(\prod_0^{begin\ bonding\ period}1 + x)$ to normalize it to being the amount of rewards you're entitled to from 1 stake at that block to now. Then as before, you multiply by the amount of stake you had initially bonded. -TODO: (Does the difference equating to that make sense, or should it be shown explicitly) +\\TODO: (Does the difference equating to that make sense, or should it be shown explicitly) +\\TODO: Does this need to explain how the originally bonded tokens are refunded, or is that clear? -Note that the inflation function could vary per block, +The inflation function could vary per block, and per validator if ever a need rose. If the inflation rate is the same for everyone then there can be a single global store for the entries corresponding to the product of inflations. Inflation creation can trivially be epoched as long as inflation isn't required within the epoch, through changes to the $inflation$ function. -Again note that this process is extremely efficient. - \subsection{Withdrawing with no iteration over slashes} -TODO: Fill this out. -Core idea: you use the same mechanism as previously, but you just make that blocks $x_j$ term negative. -(So a $20\%$ slash would be equivalent to an inflation on that validator of $-20\%$) -This foregoes the constant inflation per validator, may or may not be worth it depending on expected number of slashes +Notice that a slash is the same as a negative inflation rate for a validator in one block. +For example a $20\%$ slash is equivalent to a $-20\%$ inflation for a validator in a block. +Given correctness of auto-bonding inflation with different inflation rates per-validator, +it follows that handling slashes can be correctly done by simply subtracting the validators inflation factor in that block to be the negative of the slash factor. +This significantly simplifies the withdrawal procedure. \subsection{Auto bonding fees} TODO: Fill this out. @@ -197,28 +199,22 @@ \subsection{Jailing / being kicked out of the validator set} So you simply don't update the "total accrued fees this period" variable for jailed / non-bonded validators. Withdrawing requires \textit{no} special casing here! -\section{State pruning} -You will notice that in the main scheme there was no note for pruning entries from state. -We can in fact prune quite effectively. +\section{State Requirements} +State entries can be pruned quite effectively. Suppose for the sake of exposition that there is at most one delegation / withdrawal to a particular validator in any given block. Then each delegation is responsible for one addition to state. Only the next period, and this delegator's withdrawal could depend on this entry. Thus once this delegator withdraws, this state entry can be pruned. For the entry created by the delegator's withdrawal, that is only required by the creation of the next period. Thus once the next period is created, that withdrawal's period can be deleted. -This can be easily adapted to the case where there are multiple delegations / withdrawals per block. -Keep a counter per state entry for how many delegations need to be cleared. -(So 1 for each delegation in that block which created that period, 0 for each withdrawal) -When creating a new period, check that the previous period (which had to be read anyway) doesn't have a count of 0. -If it does have a count of 0, delete it. -When withdrawing, decrement the period which created this delegation's counter by 1. -If that counter is now 0, delete that period. +This can be easily adapted to the case where there are multiple delegations / withdrawals per block, by maintaining a reference count in each period starting state entry. The slash entries for a validator can only be pruned when all of that validator's delegators have their bonding period starting after the slash. This seems ineffective to keep track of, thus it is not worth it. Each slash should instead remain in state until the validator unbonds and all delegators have their fees withdrawn. \section{Implementers Considerations} +TODO: Convert this section into a proper conclusion This is an extremely simple scheme with many nice benefits. \begin{itemize} @@ -238,11 +234,11 @@ \section{TO DOs} \begin{itemize} \item A global fee pool can be described. - \item Determine if auto-bonding fees is compatible with inflation and comission rates in conjunction - \item mention storage optimization in the uniform inflation and iteration over slashing case: only have one storage location w/ refcount of the product of inflation results - \item Remove iteration over slashes by the same normalization trick used in inflation + \item Mention storage optimization for how to prune slashing entries in the uniform inflation and iteration over slashing case \item Add equation numbers \item perhaps re-organize so that the no iteration + \item Section on decimal precision considerations (would unums help?), and mitigating errors in calculation with floats and decimals. -- This probably belongs in a corrollary markdown file in the implementation + \item Consider indicating that the withdraw action need not be a tx type and could instead happen 'transparently' when more coins are needed, if a chain desired this for UX / p2p efficiency. \end{itemize} diff --git a/docs/spec/other/bech32.md b/docs/spec/addresses/bech32.md similarity index 100% rename from docs/spec/other/bech32.md rename to docs/spec/addresses/bech32.md diff --git a/docs/spec/auth/README.md b/docs/spec/auth/README.md index 6db6db226c06..be6647a801e9 100644 --- a/docs/spec/auth/README.md +++ b/docs/spec/auth/README.md @@ -16,14 +16,15 @@ This module will be used in the Cosmos Hub. 1. **[State](state.md)** 1. [Accounts](state.md#accounts) 1. [Account Interface](state.md#account-interface) - 1. [Base Account](state.md#baseaccount) - 1. [Vesting Account](state.md#vestingaccount) + 2. [Base Account](state.md#baseaccount) + 3. [Vesting Account](state.md#vestingaccount) 1. **[Types](types.md)** 1. [StdFee](types.md#stdfee) - 1. [StdSignature](types.md#stdsignature) - 1. [StdTx](types.md#stdtx) - 1. [StdSignDoc](types.md#stdsigndoc) + 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)** diff --git a/docs/spec/auth/gas_fees.md b/docs/spec/auth/gas_fees.md new file mode 100644 index 000000000000..fbe21039461a --- /dev/null +++ b/docs/spec/auth/gas_fees.md @@ -0,0 +1,28 @@ +# Gas & Fees + +Fees serve two purposes for an operator of the network. + +Fees limit the growth of the state stored by every full node and allow for +general purpose censorship of transactions of little economic value. Fees +are best suited as an anti-spam mechanism where validators are disinterested in +the use of the network and identities of users. + +Fees are determined by the gas limits and gas prices transactions provide. +Operators should set minimum gas prices when starting their nodes. They must set +the unit costs of gas in each token denomination they wish to support: + +`gaiad start ... --minimum-gas-prices=0.00001steak,0.05photinos` + +When adding transactions to mempool or gossipping transactions, validators check +if the transaction's gas prices, which are determined by the provided fees, meet +any of the validator's minimum gas prices. In other words, a transaction must +provide a fee of at least one denomination that matches a validator's minimum +gas price. + +Tendermint does not currently provide fee based mempool prioritization, and fee +based mempool filtering is local to node and not part of consensus. But with +minimum gas prices set, such a mechanism could be implemented by node operators. + +Because the market value for tokens will fluctuate, validators are expected to +dynamically adjust their minimum gas prices to a level that would encourage the +use of the network. diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index b8785619b2aa..a0c64fdd5e95 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -1,7 +1,5 @@ # Vesting - - - [Vesting](#vesting) - [Intro and Requirements](#intro-and-requirements) - [Vesting Account Types](#vesting-account-types) @@ -10,37 +8,27 @@ - [Continuously Vesting Accounts](#continuously-vesting-accounts) - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts) - [Transferring/Sending](#transferringsending) - - [Continuously Vesting Accounts](#continuously-vesting-accounts-1) - - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-1) - - [Keepers/Handlers](#keepershandlers) + - [Keepers/Handlers](#keepershandlers) - [Delegating](#delegating) - - [Continuously Vesting Accounts](#continuously-vesting-accounts-2) - - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-2) - - [Keepers/Handlers](#keepershandlers-1) + - [Keepers/Handlers](#keepershandlers-1) - [Undelegating](#undelegating) - - [Continuously Vesting Accounts](#continuously-vesting-accounts-3) - - [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-3) - - [Keepers/Handlers](#keepershandlers-2) + - [Keepers/Handlers](#keepershandlers-2) - [Keepers & Handlers](#keepers--handlers) - - [Initializing at Genesis](#initializing-at-genesis) + - [Genesis Initialization](#genesis-initialization) - [Examples](#examples) - [Simple](#simple) - [Slashing](#slashing) - [Glossary](#glossary) - - ## Intro and Requirements -This paper specifies vesting account implementation for the Cosmos Hub. +This specification describes the vesting account implementation for the Cosmos Hub. The requirements for this vesting account is that it should be initialized -during genesis with a starting balance `X` coins and a vesting end time `T`. +during genesis with a starting balance `X` and a vesting end time `T`. -The owner of this account should be able to delegate to validators -and vote with locked coins, however they cannot send locked coins to other -accounts until those coins have been unlocked. When it comes to governance, it -is yet undefined if we want to allow a vesting account to be able to deposit -vesting coins into proposals. +The owner of this account should be able to delegate to and undelegate from +validators, however they cannot send locked coins to other accounts until those +coins have been fully vested. In addition, a vesting account vests all of its coin denominations at the same rate. This may be subject to change. @@ -56,15 +44,17 @@ order to make such a distinction. // implement. type VestingAccount interface { Account - AssertIsVestingAccount() // existence implies that account is vesting - // Calculates the amount of coins that can be sent to other accounts given - // the current time. - SpendableCoins(Context) Coins - // Performs delegation accounting. - TrackDelegation(amount) - // Performs undelegation accounting. - TrackUndelegation(amount) + GetVestedCoins(Time) Coins + GetVestingCoins(Time) Coins + + // Delegation and undelegation accounting that returns the resulting base + // coins amount. + TrackDelegation(Time, Coins) + TrackUndelegation(Coins) + + GetStartTime() int64 + GetEndTime() int64 } // BaseVestingAccount implements the VestingAccount interface. It contains all @@ -74,28 +64,41 @@ type BaseVestingAccount struct { OriginalVesting Coins // coins in account upon initialization DelegatedFree Coins // coins that are vested and delegated - EndTime Time // when the coins become unlocked + DelegatedVesting Coins // coins that vesting and delegated + + EndTime int64 // when the coins become unlocked } // ContinuousVestingAccount implements the VestingAccount interface. It // continuously vests by unlocking coins linearly with respect to time. type ContinuousVestingAccount struct { - BaseAccount BaseVestingAccount - DelegatedVesting Coins // coins that vesting and delegated - StartTime Time // when the coins start to vest + StartTime int64 // when the coins start to vest } // DelayedVestingAccount implements the VestingAccount interface. It vests all // coins after a specific time, but non prior. In other words, it keeps them // locked until a specified time. type DelayedVestingAccount struct { - BaseAccount BaseVestingAccount } ``` +In order to facilitate less ad-hoc type checking and assertions and to support +flexibility in account usage, the existing `Account` interface is updated to contain +the following: + +```go +type Account interface { + // ... + + // Calculates the amount of coins that can be sent to other accounts given + // the current time. + SpendableCoins(Time) Coins +} +``` + ## Vesting Account Specification Given a vesting account, we define the following in the proceeding operations: @@ -105,19 +108,19 @@ Given a vesting account, we define the following in the proceeding operations: - `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis. - `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account. - `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account. -- `BC`: The number of `OV` coins less any coins that are transferred, which can be negative, or delegated (`DV + DF`). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account. +- `BC`: The number of `OV` coins less any coins that are transferred (which can be negative or delegated). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account. ### Determining Vesting & Vested Amounts It is important to note that these values are computed on demand and not on a -mandatory per-block basis. +mandatory per-block basis (e.g. `BeginBlocker` or `EndBlocker`). #### Continuously Vesting Accounts -To determine the amount of coins that are vested for a given block `B`, the +To determine the amount of coins that are vested for a given block time `T`, the following is performed: -1. Compute `X := B.Time - StartTime` +1. Compute `X := T - StartTime` 2. Compute `Y := EndTime - StartTime` 3. Compute `V' := OV * (X / Y)` 4. Compute `V := OV - V'` @@ -126,100 +129,89 @@ Thus, the total amount of _vested_ coins is `V'` and the remaining amount, `V`, is _vesting_. ```go -func (cva ContinuousVestingAccount) GetVestedCoins(b Block) Coins { - // We must handle the case where the start time for a vesting account has - // been set into the future or when the start of the chain is not exactly - // known. - if b.Time < va.StartTime { +func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins { + if t <= cva.StartTime { + // We must handle the case where the start time for a vesting account has + // been set into the future or when the start of the chain is not exactly + // known. return ZeroCoins + } else if t >= cva.EndTime { + return cva.OriginalVesting } - x := b.Time - cva.StartTime + x := t - cva.StartTime y := cva.EndTime - cva.StartTime return cva.OriginalVesting * (x / y) } -func (cva ContinuousVestingAccount) GetVestingCoins(b Block) Coins { - return cva.OriginalVesting - cva.GetVestedCoins(b) +func (cva ContinuousVestingAccount) GetVestingCoins(t Time) Coins { + return cva.OriginalVesting - cva.GetVestedCoins(t) } ``` #### Delayed/Discrete Vesting Accounts Delayed vesting accounts are easier to reason about as they only have the full -amount vesting up until a certain time, then they all become vested (unlocked). +amount vesting up until a certain time, then all the coins become vested (unlocked). +This does not include any unlocked coins the account may have initially. ```go -func (dva DelayedVestingAccount) GetVestedCoins(b Block) Coins { - if b.Time >= dva.EndTime { +func (dva DelayedVestingAccount) GetVestedCoins(t Time) Coins { + if t >= dva.EndTime { return dva.OriginalVesting } return ZeroCoins } -func (dva DelayedVestingAccount) GetVestingCoins(b Block) Coins { - return cva.OriginalVesting - cva.GetVestedCoins(b) +func (dva DelayedVestingAccount) GetVestingCoins(t Time) Coins { + return dva.OriginalVesting - dva.GetVestedCoins(t) } ``` ### Transferring/Sending -#### Continuously Vesting Accounts - -At any given time, a continuous vesting account may transfer: `min((BC + DV) - V, BC)`. +At any given time, a vesting account may transfer: `min((BC + DV) - V, BC)`. In other words, a vesting account may transfer the minimum of the base account balance and the base account balance plus the number of currently delegated vesting coins less the number of coins vested so far. ```go -func (cva ContinuousVestingAccount) SpendableCoins() Coins { - bc := cva.GetCoins() - return min((bc + cva.DelegatedVesting) - cva.GetVestingCoins(), bc) -} -``` - -##### Delayed/Discrete Vesting Accounts - -A delayed vesting account may send any coins it has received. In addition, if it -has fully vested, it can send any of it's vested coins. - -```go -func (dva DelayedVestingAccount) SpendableCoins() Coins { - bc := dva.GetCoins() - return bc - dva.GetVestingCoins() +func (va VestingAccount) SpendableCoins(t Time) Coins { + bc := va.GetCoins() + return min((bc + va.DelegatedVesting) - va.GetVestingCoins(t), bc) } ``` -##### Keepers/Handlers +#### Keepers/Handlers The corresponding `x/bank` keeper should appropriately handle sending coins based on if the account is a vesting account or not. ```go -func SendCoins(from Account, to Account amount Coins) { +func SendCoins(t Time, from Account, to Account, amount Coins) { + bc := from.GetCoins() + if isVesting(from) { - sc := from.SpendableCoins() - } else { - sc := from.GetCoins() + sc := from.SpendableCoins(t) + assert(amount <= sc) } - if amount <= sc { - from.SetCoins(sc - amount) - to.SetCoins(amount) - // save accounts... - } + newCoins := bc - amount + assert(newCoins >= 0) + + from.SetCoins(bc - amount) + to.SetCoins(amount) + + // save accounts... } ``` ### Delegating -#### Continuously Vesting Accounts - -For a continuous vesting account attempting to delegate `D` coins, the following -is performed: +For a vesting account attempting to delegate `D` coins, the following is performed: 1. Verify `BC >= D > 0` 2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting) @@ -229,98 +221,66 @@ is performed: 6. Set `BC -= D` ```go -func (cva ContinuousVestingAccount) TrackDelegation(amount Coins) { - x := min(max(cva.GetVestingCoins() - cva.DelegatedVesting, 0), amount) +func (va VestingAccount) TrackDelegation(t Time, amount Coins) { + x := min(max(va.GetVestingCoins(t) - va.DelegatedVesting, 0), amount) y := amount - x - cva.DelegatedVesting += x - cva.DelegatedFree += y + va.DelegatedVesting += x + va.DelegatedFree += y + va.SetCoins(va.GetCoins() - amount) } ``` -##### Delayed/Discrete Vesting Accounts - -For a delayed vesting account, it can only delegate with received coins and -coins that are fully vested so we only need to update `DF`. +#### Keepers/Handlers ```go -func (dva DelayedVestingAccount) TrackDelegation(amount Coins) { - dva.DelegatedFree += amount -} -``` +func DelegateCoins(t Time, from Account, amount Coins) { + bc := from.GetCoins() + assert(amount <= bc) -##### Keepers/Handlers - -```go -func DelegateCoins(from Account, amount Coins) { - // canDelegate checks different semantics for continuous and delayed vesting - // accounts - if isVesting(from) && canDelegate(from) { - sc := from.GetCoins() - - if amount <= sc { - from.TrackDelegation(amount) - from.SetCoins(sc - amount) - // save account... - } + if isVesting(from) { + from.TrackDelegation(t, amount) } else { - sc := from.GetCoins() - - if amount <= sc { - from.SetCoins(sc - amount) - // save account... - } + from.SetCoins(sc - amount) } + + // save account... } ``` ### Undelegating -#### Continuously Vesting Accounts - -For a continuous vesting account attempting to undelegate `D` coins, the -following is performed: +For a vesting account attempting to undelegate `D` coins, the following is performed: 1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check) -2. Compute `Y := min(DF, D)` (portion of `D` that should become free, prioritizing free coins) -3. Compute `X := D - Y` (portion of `D` that should remain vesting) -4. Set `DV -= X` -5. Set `DF -= Y` +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) +4. Set `DF -= X` +5. Set `DV -= Y` 6. Set `BC += D` ```go func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) { - y := min(cva.DelegatedFree, amount) - x := amount - y + x := min(cva.DelegatedFree, amount) + y := amount - x - cva.DelegatedVesting -= x - cva.DelegatedFree -= y + cva.DelegatedFree -= x + cva.DelegatedVesting -= y + cva.SetCoins(cva.GetCoins() + amount) } ``` **Note**: If a delegation is slashed, the continuous vesting account will end up -with excess an `DV` amount, even after all its coins have vested. This is because +with an excess `DV` amount, even after all its coins have vested. This is because undelegating free coins are prioritized. -##### Delayed/Discrete Vesting Accounts - -For a delayed vesting account, it only needs to add back the `DF` amount since -the account is fully vested. - -```go -func (dva DelayedVestingAccount) TrackUndelegation(amount Coins) { - dva.DelegatedFree -= amount -} -``` - -##### Keepers/Handlers +#### Keepers/Handlers ```go func UndelegateCoins(to Account, amount Coins) { if isVesting(to) { if to.DelegatedFree + to.DelegatedVesting >= amount { to.TrackUndelegation(amount) - AddCoins(to, amount) // save account ... } } else { @@ -333,7 +293,7 @@ func UndelegateCoins(to Account, amount Coins) { ## Keepers & Handlers The `VestingAccount` implementations reside in `x/auth`. However, any keeper in -a module (e.g. staking in `x/stake`) wishing to potentially utilize any vesting +a module (e.g. staking in `x/staking`) wishing to potentially utilize any vesting coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`) opposed to `SendCoins` and `SubtractCoins`. @@ -344,40 +304,41 @@ unlocked coin amount. See the above specification for full implementation details. -## Initializing at Genesis +## Genesis Initialization -To initialize both vesting accounts and base accounts, the `GenesisAccount` -struct will include an `EndTime`. Accounts meant to be of type `BaseAccount` will -have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into -BaseAccounts and VestingAccounts as appropriate. +To initialize both vesting and non-vesting accounts, the `GenesisAccount` struct will +include new fields: `Vesting`, `StartTime`, and `EndTime`. Accounts meant to be +of type `BaseAccount` or any non-vesting type will have `Vesting = false`. The +genesis initialization logic (e.g. `initFromGenesisState`) will have to parse +and return the correct accounts accordingly based off of these new fields. ```go type GenesisAccount struct { - Address sdk.AccAddress - GenesisCoins sdk.Coins - EndTime int64 + // ... + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting"` + DelegatedFree sdk.Coins `json:"delegated_free"` + DelegatedVesting sdk.Coins `json:"delegated_vesting"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` } -func initChainer() { - for genAcc in GenesisAccounts { - baseAccount := BaseAccount{ - Address: genAcc.Address, - Coins: genAcc.GenesisCoins, - } - - if genAcc.EndTime != 0 { - vestingAccount := ContinuousVestingAccount{ - BaseAccount: baseAccount, - OriginalVesting: genAcc.GenesisCoins, - StartTime: RequestInitChain.Time, - EndTime: genAcc.EndTime, - } +func ToAccount(gacc GenesisAccount) Account { + bacc := NewBaseAccount(gacc) - AddAccountToState(vestingAccount) + if gacc.OriginalVesting > 0 { + if ga.StartTime != 0 && ga.EndTime != 0 { + // return a continuous vesting account + } else if ga.EndTime != 0 { + // return a delayed vesting account } else { - AddAccountToState(baseAccount) + // invalid genesis vesting account provided + panic() } } + + return bacc } ``` @@ -397,30 +358,30 @@ V' = 0 ``` 1. Immediately receives 1 coin - ``` + ```text BC = 11 ``` 2. Time passes, 2 coins vest - ``` + ```text V = 8 V' = 2 ``` 3. Delegates 4 coins to validator A - ``` + ```text DV = 4 BC = 7 ``` 4. Sends 3 coins - ``` + ```text BC = 4 ``` 5. More time passes, 2 more coins vest - ``` + ```text V = 6 V' = 4 ``` 6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate. - ``` + ```text BC = 2 ``` @@ -429,34 +390,34 @@ V' = 0 Same initial starting conditions as the simple example. 1. Time passes, 5 coins vest - ``` + ```text V = 5 V' = 5 ``` 2. Delegate 5 coins to validator A - ``` + ```text DV = 5 BC = 5 ``` 3. Delegate 5 coins to validator B - ``` + ```text DF = 5 BC = 0 ``` 4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins 5. Undelegate from validator A (2.5 coins) - ``` + ```text DF = 5 - 2.5 = 2.5 BC = 0 + 2.5 = 2.5 ``` 6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate. - ``` + ```text DV = 5 - 2.5 = 2.5 DF = 2.5 - 2.5 = 0 BC = 2.5 + 5 = 7.5 ``` -Notice how we have an excess amount of `DV`. + Notice how we have an excess amount of `DV`. ## Glossary diff --git a/docs/spec/bank/transactions.md b/docs/spec/bank/messages.md similarity index 96% rename from docs/spec/bank/transactions.md rename to docs/spec/bank/messages.md index 7cb512405e49..fd9331763e27 100644 --- a/docs/spec/bank/transactions.md +++ b/docs/spec/bank/messages.md @@ -1,4 +1,4 @@ -## Transactions +## Messages ### MsgSend diff --git a/docs/spec/bank/tags.md b/docs/spec/bank/tags.md new file mode 100644 index 000000000000..103c598390dc --- /dev/null +++ b/docs/spec/bank/tags.md @@ -0,0 +1,12 @@ +# Tags + +The bank module emits the following events/tags: + +## Handlers + +### MsgSend + +| Key | Value | +|-----------|---------------------------| +| sender | {senderAccountAddress} | +| recipient | {recipientAccountAddress} | diff --git a/docs/spec/distribution/f1_reference_counting.md b/docs/spec/distribution/f1_reference_counting.md new file mode 100644 index 000000000000..2563be872f71 --- /dev/null +++ b/docs/spec/distribution/f1_reference_counting.md @@ -0,0 +1,18 @@ +## Reference Counting in F1 Fee Distribution + +In F1 fee distribution, in order to calculate the rewards a delegator ought to receive when they +withdraw their delegation, we must read the terms of the summation of rewards divided by tokens from +the period which they ended when they delegated, and the final period (created when they withdraw). + +Additionally, as slashes change the amount of tokens a delegation will have (but we calculate this lazily, +only when a delegator un-delegates), we must calculate rewards in separate periods before / after any slashes +which occurred in between when a delegator delegated and when they withdrew their rewards. Thus slashes, like +delegations, reference the period which was ended by the slash event. + +All stored historical rewards records for periods which are no longer referenced by any delegations +or any slashes can thus be safely removed, as they will never be read (future delegations and future +slashes will always reference future periods). This is implemented by tracking a `ReferenceCount` +along with each historical reward storage entry. Each time a new object (delegation or slash) +is created which might need to reference the historical record, the reference count is incremented. +Each time one object which previously needed to reference the historical record is deleted, the reference +count is decremented. If the reference count hits zero, the historical record is deleted. diff --git a/docs/spec/distribution/hooks.md b/docs/spec/distribution/hooks.md index 8c2b4b91a10a..fac39fb89db9 100644 --- a/docs/spec/distribution/hooks.md +++ b/docs/spec/distribution/hooks.md @@ -2,7 +2,7 @@ ## Create or modify delegation distribution - - triggered-by: `stake.TxDelegate`, `stake.TxBeginRedelegate`, `stake.TxBeginUnbonding` + - triggered-by: `staking.MsgDelegate`, `staking.MsgBeginRedelegate`, `staking.MsgUndelegate` The pool of a new delegator bond will be 0 for the height at which the bond was added, or the withdrawal has taken place. This is achieved by setting @@ -10,7 +10,7 @@ added, or the withdrawal has taken place. This is achieved by setting ## Commission rate change - - triggered-by: `stake.TxEditValidator` + - triggered-by: `staking.MsgEditValidator` If a validator changes its commission rate, all commission on fees must be simultaneously withdrawn using the transaction `TxWithdrawValidator`. @@ -19,7 +19,7 @@ Additionally the change and associated height must be recorded in a ## Change in Validator State - - triggered-by: `stake.Slash`, `stake.UpdateValidator` + - triggered-by: `staking.Slash`, `staking.UpdateValidator` Whenever a validator is slashed or enters/leaves the validator group all of the validator entitled reward tokens must be simultaneously withdrawn from diff --git a/docs/spec/distribution/transactions.md b/docs/spec/distribution/messages.md similarity index 99% rename from docs/spec/distribution/transactions.md rename to docs/spec/distribution/messages.md index 821c4ba352e5..32de590f6ec3 100644 --- a/docs/spec/distribution/transactions.md +++ b/docs/spec/distribution/messages.md @@ -1,4 +1,4 @@ -# Transactions +# Messages ## MsgWithdrawDelegationRewardsAll @@ -24,7 +24,7 @@ func GetDelegatorRewardsAll(delegatorAddr sdk.AccAddress, height int64) DecCoins // collect all entitled rewards withdraw = 0 - pool = stake.GetPool() + pool = staking.GetPool() feePool = GetFeePool() for delegation = range delegations delInfo = GetDelegationDistInfo(delegation.DelegatorAddr, @@ -55,7 +55,7 @@ func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.Acc height = GetHeight() // get all distribution scenarios - pool = stake.GetPool() + pool = staking.GetPool() feePool = GetFeePool() delInfo = GetDelegationDistInfo(delegatorAddr, validatorAddr) diff --git a/docs/spec/distribution/tags.md b/docs/spec/distribution/tags.md new file mode 100644 index 000000000000..c526ae0c27b3 --- /dev/null +++ b/docs/spec/distribution/tags.md @@ -0,0 +1,30 @@ +# Tags + +The distribution module emits the following events/tags: + +## Handlers + +### MsgSetWithdrawAddress + +| Key | Value | +|-----------|---------------------------| +| delegator | {delegatorAccountAddress} | + +### MsgWithdrawDelegatorRewardsAll + +| Key | Value | +|-----------|---------------------------| +| delegator | {delegatorAccountAddress} | + +### MsgWithdrawDelegatorReward + +| Key | Value | +|------------------|---------------------------| +| delegator | {delegatorAccountAddress} | +| source-validator | {srcOperatorAddress} | + +### MsgWithdrawValidatorRewardsAll + +| Key | Value | +|------------------|----------------------| +| source-validator | {srcOperatorAddress} | diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/messages.md similarity index 99% rename from docs/spec/governance/transactions.md rename to docs/spec/governance/messages.md index 68966bcfc45c..17004e4b87df 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/messages.md @@ -1,6 +1,6 @@ # Implementation (2/2) -## Transactions +## Messages ### Proposal Submission diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index abe2dc41b48d..f28a309a4486 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -160,14 +160,14 @@ And the pseudocode for the `ProposalProcessingQueue`: // Tally voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal for each (voterAddress, vote) in voterIterator - delegations = stakeKeeper.getDelegations(voterAddress) // get all delegations for current voter + delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter for each delegation in delegations // make sure delegation.Shares does NOT include shares being unbonded tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares proposal.updateTally(vote, delegation.Shares) - _, isVal = stakeKeeper.getValidator(voterAddress) + _, isVal = stakingKeeper.getValidator(voterAddress) if (isVal) tmpValMap(voterAddress).Vote = vote diff --git a/docs/spec/governance/tags.md b/docs/spec/governance/tags.md new file mode 100644 index 000000000000..6c97a2b49db4 --- /dev/null +++ b/docs/spec/governance/tags.md @@ -0,0 +1,38 @@ +# Tags + +The governance module emits the following events/tags: + +## EndBlocker + +| Key | Value | +|-----------------|------------------------------------------------------| +| proposal-result | proposal-passed\|proposal-rejected\|proposal-dropped | + +## Handlers + +### MsgSubmitProposal + +| Key | Value | +|-------------------------|--------------------------| +| action | proposal-vote | +| proposer | {proposerAccountAddress} | +| proposal-id | {proposalID} | +| voting-period-start [0] | {proposalID} | + +* [0] Tag only emitted if the voting period starts during the submission. + +### MsgVote + +| Key | Value | +|-------------|-----------------------| +| action | proposal-vote | +| voter | {voterAccountAddress} | +| proposal-id | {proposalID} | + +### MsgDeposit + +| Key | Value | +|-------------|---------------------------| +| action | proposal-vote | +| depositor | {depositorAccountAddress} | +| proposal-id | {proposalID} | diff --git a/docs/spec/inflation/end_block.md b/docs/spec/inflation/end_block.md deleted file mode 100644 index 49408a3f06f4..000000000000 --- a/docs/spec/inflation/end_block.md +++ /dev/null @@ -1,52 +0,0 @@ -# End Block - -Validator provisions are minted on an hourly basis (the first block of a new -hour). The annual target of between 7% and 20%. The long-term target ratio of -bonded tokens to unbonded tokens is 67%. - -The target annual inflation rate is recalculated for each provisions cycle. The -inflation is also subject to a rate change (positive or negative) depending on -the distance from the target ratio (67%). The maximum rate change possible is -defined to be 13% per year, however the annual inflation is capped as between -7% and 20%. - -Within the inflation module the tokens are created, and fed to the distribution -module to be further processed and distributed similarly to fee distribution (with -the exception that there are no special rewards for the block proposer) - -Note that params are global params (TODO: link to the global params spec) - -``` -EndBlock(): - - //process provisions - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - precision = 10000 - - time = BFTTime() // time is in seconds - if time > GetInflationLastTime() + 3600 - SetInflationLastTime(InflationLastTime + 3600) - inflation = nextInflation(hrsPerYr).Round(precision) - SetInflation(inflation) - - provisions = inflation * (pool.TotalSupply() / hrsPerYr) - pool.LooseTokens += provisions - - distribution.AddInflation(provisions) - -nextInflation(hrsPerYr rational.Rat): - - bondedRatio = pool.BondedPool / pool.TotalSupply() - - inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange - inflationRateChange = inflationRateChangePerYear / hrsPerYr - - inflation = GetInflation() + inflationRateChange - switch inflation - case > params.InflationMax - return params.InflationMax - case < params.InflationMin - return params.InflationMin - default - return inflation -``` diff --git a/docs/spec/inflation/state.md b/docs/spec/inflation/state.md deleted file mode 100644 index c16286804a65..000000000000 --- a/docs/spec/inflation/state.md +++ /dev/null @@ -1,21 +0,0 @@ -## State - -### Inflation - - key: `0x00` - - value: `amino(Inflation)` - -The current annual inflation rate. - -```golang -type Inflation sdk.Dec -``` - -### InflationLastTime - - key: `0x01` - - value: `amino(InflationLastTime)` - -The last unix time which the inflation was processed for. - -```golang -type InflationLastTime int64 -``` diff --git a/docs/spec/other/other.md b/docs/spec/other/other.md deleted file mode 100644 index 0ba8e612caed..000000000000 --- a/docs/spec/other/other.md +++ /dev/null @@ -1,2 +0,0 @@ -- reserve pool -- AiB vesting diff --git a/docs/spec/reserve-pool/TODO.md b/docs/spec/reserve-pool/TODO.md new file mode 100644 index 000000000000..b3232fef32fd --- /dev/null +++ b/docs/spec/reserve-pool/TODO.md @@ -0,0 +1,5 @@ +TODO + +The reserve pool is the pool of collected funds for use by governance taken via the `CommunityTax`. +Currently with the SDK, tokens collected by the CommunityTax are accounted for but unspendable. + diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index dee91eb33078..2240930f8e80 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -19,15 +19,12 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste 1. **[Overview](overview.md)** 1. **[State](state.md)** 1. [SigningInfo](state.md#signing-info) - 1. [SlashingPeriod](state.md#slashing-period) -1. **[Transactions](transactions.md)** +2. **[Transactions](transactions.md)** 1. [Unjail](transactions.md#unjail) -1. **[Hooks](hooks.md)** +3. **[Hooks](hooks.md)** 1. [Validator Bonded](hooks.md#validator-bonded) - 1. [Validator Unbonded](hooks.md#validator-unbonded) - 1. [Validator Slashed](hooks.md#validator-slashed) -1. **[Begin Block](begin-block.md)** +4. **[Begin Block](begin-block.md)** 1. [Evidence handling](begin-block.md#evidence-handling) - 1. [Uptime tracking](begin-block.md#uptime-tracking) -1. **[Future Improvements](future-improvements.md)** + 2. [Uptime tracking](begin-block.md#uptime-tracking) +5. **[Future Improvements](future-improvements.md)** 1. [State cleanup](future-improvements.md#state-cleanup) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 43691b38d211..4bc677ded534 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -59,7 +59,7 @@ for redel in redels { } ``` -We then slash the validator: +We then slash the validator and tombstone them: ``` curVal := validator @@ -70,17 +70,21 @@ slashAmount -= slashAmountUnbondings slashAmount -= slashAmountRedelegations curVal.Shares = max(0, curVal.Shares - slashAmount) + +signInfo = SigningInfo.Get(val.Address) +signInfo.JailedUntil = MAX_TIME +signInfo.Tombstoned = true +SigningInfo.Set(val.Address, signInfo) ``` This ensures that offending validators are punished the same amount whether they act as a single validator with X stake or as N validators with collectively X -stake. - -The amount slashed for all double signature infractions committed within a single slashing period is capped as described in [state-machine.md](state-machine.md). +stake. The amount slashed for all double signature infractions committed within a +single slashing period is capped as described in [overview.md](overview.md) under Tombstone Caps. ## Uptime tracking -At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: +At the beginning of each block, we update the signing info for each validator and check if they've dipped below the liveness threshhold over the tracked window. If so, they will be slashed by `LivenessSlashAmount` and will be Jailed for `LivenessJailPeriod`. Liveness slashes do NOT lead to a tombstombing. ``` height := block.Height @@ -114,9 +118,7 @@ for val in block.Validators: signInfo.IndexOffset = 0 signInfo.MissedBlocksCounter = 0 clearMissedBlockBitArray() - slash & unbond the validator + slash & jail the validator SigningInfo.Set(val.Address, signInfo) ``` - -The amount slashed for downtime slashes is *not* capped by the slashing period in which they are committed, although they do reset it (since the validator is unbonded). diff --git a/docs/spec/slashing/future-improvements.md b/docs/spec/slashing/future-improvements.md deleted file mode 100644 index 84be139e8cf9..000000000000 --- a/docs/spec/slashing/future-improvements.md +++ /dev/null @@ -1,4 +0,0 @@ -## State Cleanup - -Once no evidence for a given slashing period can possibly be valid (the end time plus the unbonding period is less than the current time), -old slashing periods should be cleaned up. This will be implemented post-launch. diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/hooks.md index 1888c1d2f847..74207891fb84 100644 --- a/docs/spec/slashing/hooks.md +++ b/docs/spec/slashing/hooks.md @@ -4,10 +4,8 @@ In this section we describe the "hooks" - slashing module code that runs when ot ### Validator Bonded -Upon successful bonding of a validator (a given validator entering the "bonded" state, -which may happen on delegation, on unjailing, etc), we create a new `SlashingPeriod` structure for the -now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), -and `SlashedSoFar` of `0`: +Upon successful first-time bonding of a new validator, we create a new `ValidatorSigningInfo` structure for the +now-bonded validator, which `StartHeight` of the current block. ``` onValidatorBonded(address sdk.ValAddress) @@ -18,52 +16,11 @@ onValidatorBonded(address sdk.ValAddress) StartHeight : CurrentHeight, IndexOffset : 0, JailedUntil : time.Unix(0, 0), + Tombstone : false, MissedBloskCounter : 0 } setValidatorSigningInfo(signingInfo) } - - slashingPeriod = SlashingPeriod{ - ValidatorAddr : address, - StartHeight : CurrentHeight, - EndHeight : 0, - SlashedSoFar : 0, - } - setSlashingPeriod(slashingPeriod) return ``` - -### Validator Unbonded - -When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`: - -``` -onValidatorUnbonded(address sdk.ValAddress) - - slashingPeriod = getSlashingPeriod(address, CurrentHeight) - slashingPeriod.EndHeight = CurrentHeight - setSlashingPeriod(slashingPeriod) - - return -``` - -### Validator Slashed - -When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator -address and the time of infraction, cap the fraction slashed as `max(SlashFraction, SlashedSoFar)` -(which may be `0`), and update the `SlashingPeriod` with the increased `SlashedSoFar`: - -``` -beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64) - - slashingPeriod = getSlashingPeriod(address, infractionHeight) - totalToSlash = max(slashingPeriod.SlashedSoFar, fraction) - slashingPeriod.SlashedSoFar = totalToSlash - setSlashingPeriod(slashingPeriod) - - remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash - fraction = remainderToSlash - - continue with slashing -``` diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/messages.md similarity index 84% rename from docs/spec/slashing/transactions.md rename to docs/spec/slashing/messages.md index c33e96047d61..342f3468bded 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/messages.md @@ -1,6 +1,6 @@ -## Transactions +## Messages -In this section we describe the processing of transactions for the `slashing` module. +In this section we describe the processing of messages for the `slashing` module. ### Unjail @@ -22,6 +22,8 @@ handleMsgUnjail(tx TxUnjail) fail with "Validator not jailed, cannot unjail" info = getValidatorSigningInfo(operator) + if info.Tombstoned + fail with "Tombstoned validator cannot be unjailed" if block time < info.JailedUntil fail with "Validator still jailed, cannot unjail until period has expired" diff --git a/docs/spec/slashing/overview.md b/docs/spec/slashing/overview.md index aa42f6193985..63f0e494f856 100644 --- a/docs/spec/slashing/overview.md +++ b/docs/spec/slashing/overview.md @@ -6,18 +6,14 @@ At any given time, there are any number of validators registered in the state ma 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. -### Slashing period +### 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 *slashing period*, in which the amount by which a validator can be slashed is capped at the punishment for the worst violation. For example, -if you misconfigure your HSM and double-sign a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately jailed, -so that you have a chance to reconfigure your setup). This will still be quite expensive and desirable to avoid, but slashing periods somewhat blunt -the economic impact of unintentional misconfiguration. +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 +a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately tombstombed). This will still be quite expensive and desirable +to avoid, but tombstone caps somewhat blunt the economic impact of unintentional misconfiguration. -Unlike the unbonding period, the slashing period doesn't have a fixed length. A new slashing period starts whenever a validator is bonded and ends -whenever the validator is unbonded (which will happen if the validator is jailed). The amount of tokens slashed relative to validator power for infractions -committed within the slashing period, whenever they are discovered, is capped at the punishment for the worst infraction -(which for the Cosmos Hub at launch will be double-signing a block). +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 @@ -25,44 +21,22 @@ committed within the slashing period, whenever they are discovered, is capped at *[* : timeline start *]* : timeline end -*<* : slashing period start -*>* : slashing period end *Cn* : infraction `n` committed *Dn* : infraction `n` discovered *Vb* : validator bonded *Vu* : validator unbonded -*Single infraction* +*Single Double Sign Infraction* <-----------------> [----------C1----D1,Vu-----] A single infraction is committed then later discovered, at which point the validator is unbonded and slashed at the full amount for the infraction. -*Multiple infractions* +*Multiple Double Sign Infractions* <---------------------------> [----------C1--C2---C3---D1,D2,D3Vu-----] -Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction. - -*Multiple infractions after rebonding* - - -<--------------------------->                        <-------------> -[----------C1--C2---C3---D1,D2,D3Vu---Vb---C4----D4,Vu--] - -Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction. -The validator then unjails themself and rebonds, then commits a fourth infraction - which is discovered and punished at the full amount, since a new slashing period started -when they unjailed and rebonded. - -### Safety note - -Slashing is capped fractionally per period, but the amount of total bonded stake associated with any given validator can change (by an unbounded amount) over that period. - -For example, with MaxFractionSlashedPerPeriod = `0.5`, if a validator is initially slashed at `0.4` near the start of a period when they have 100 stake bonded, -then later slashed at `0.4` when they have `1000` stake bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) - -whereas if they had `1000` stake bonded initially, the first offense would have been slashed for `400` stake and the total amount slashed would have been `400 + 100 = 500`. - -This means that any slashing events which utilize the slashing period (are capped-per-period) **must also** jail the validator when the infraction is discovered. -Otherwise it would be possible for a validator to slash themselves intentionally at a low bond, then increase their bond but no longer be at stake since they would have already hit the `SlashedSoFar` cap. +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 diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 9a9a188ff2d2..556b9c3cc914 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -40,6 +40,7 @@ type ValidatorSigningInfo struct { IndexOffset int64 // Offset into the signed block bit array JailedUntilHeight int64 // Block height until which the validator is jailed, // or sentinel value of 0 for not jailed + Tombstoned bool // Whether a validator is tombstoned or not MissedBlocksCounter int64 // Running counter of missed blocks } @@ -49,32 +50,5 @@ Where: * `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). * `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). * `JailedUntil` is set whenever the candidate is jailed due to downtime +* `Tombstoned` is set once a validator's first double sign evidence comes in * `MissedBlocksCounter` is a counter kept to avoid unnecessary array reads. `MissedBlocksBitArray.Sum() == MissedBlocksCounter` always. - -## Slashing Period - -A slashing period is a start and end block height associated with a particular validator, -within which only the "worst infraction counts" (see the [Overview](overview.md)): the total -amount of slashing for infractions committed within the period (and discovered whenever) is -capped at the penalty for the worst offense. - -This period starts when a validator is first bonded and ends when a validator is slashed & jailed -for any reason. When the validator rejoins the validator set (perhaps through unjailing themselves, -and perhaps also changing signing keys), they enter into a new period. - -Slashing periods are indexed in the store as follows: - -- SlashingPeriod: ` 0x03 | ValTendermintAddr | StartHeight -> amino(slashingPeriod) ` - -This allows us to look up slashing period by a validator's address, the only lookup necessary, -and iterate over start height to efficiently retrieve the most recent slashing period(s) -or those beginning after a given height. - -```go -type SlashingPeriod struct { - ValidatorAddr sdk.ValAddress // Tendermint address of the validator - StartHeight int64 // Block height at which slashing period begin - EndHeight int64 // Block height at which slashing period ended - SlashedSoFar sdk.Rat // Fraction slashed so far, cumulative -} -``` diff --git a/docs/spec/slashing/tags.md b/docs/spec/slashing/tags.md new file mode 100644 index 000000000000..a9f2e59d3a8b --- /dev/null +++ b/docs/spec/slashing/tags.md @@ -0,0 +1,12 @@ +# Tags + +The slashing module emits the following events/tags: + +## Handlers + +### MsgUnjail + +| Key | Value | +|-----------|----------------------------| +| action | validator-unjailed | +| validator | {validatorOperatorAddress} | diff --git a/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.pdf b/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.pdf deleted file mode 100644 index 0f7443104dec..000000000000 Binary files a/docs/spec/spec-proposals/f1-fee-distribution/f1_fee_distr.pdf and /dev/null differ diff --git a/docs/spec/staking/TODO.md b/docs/spec/staking/TODO.md new file mode 100644 index 000000000000..efef4991c07d --- /dev/null +++ b/docs/spec/staking/TODO.md @@ -0,0 +1,7 @@ + + - `state.md` needs updates to include + - LastValidatorPower + - LastTotalPower + - state for the queues: UnbondingDelegation, UnbondingValidator, Redelegation + - introduce `state_transitions.md` to describe validator/delegator state + transitions which are called from transactions diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index e1d769ed4632..a6cb30765817 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -1,68 +1,62 @@ # End-Block -## Unbonding Validator Queue - -For all unbonding validators that have finished their unbonding period, this switches their validator.Status -from sdk.Unbonding to sdk.Unbonded if they still have any delegation left. Otherwise, it deletes it from state. - -```golang -validatorQueue(currTime time.Time): - // unbonding validators are in ordered queue from oldest to newest - for all unbondingValidators whose CompleteTime < currTime: - validator = GetValidator(unbondingValidator.ValidatorAddr) - if validator.DelegatorShares == 0 { - RemoveValidator(unbondingValidator) - } else { - validator.Status = sdk.Unbonded - SetValidator(unbondingValidator) - } - return -``` +Each abci end block call, the operations to update queues and validator set +changes are specified to execute. ## Validator Set Changes -The Tendermint validator set may be updated by state transitions that run at -the end of every block. The Tendermint validator set may be changed by -validators either being jailed due to inactivity/unexpected behaviour (covered -in slashing) or changed in validator power. Determining which validator set -changes must be made occurs during staking transactions (and slashing -transactions) - during end-block the already accounted changes are applied and -the changes cleared - -```golang -EndBlock() ValidatorSetChanges - vsc = GetValidTendermintUpdates() - ClearTendermintUpdates() - return vsc -``` - -## CompleteUnbonding - -Complete the unbonding and transfer the coins to the delegate. Realize any -slashing that occurred during the unbonding period. - -```golang -unbondingQueue(currTime time.Time): - // unbondings are in ordered queue from oldest to newest - for all unbondings whose CompleteTime < currTime: - validator = GetValidator(unbonding.ValidatorAddr) - AddCoins(unbonding.DelegatorAddr, unbonding.Balance) - removeUnbondingDelegation(unbonding) - return -``` - -## CompleteRedelegation - -Note that unlike CompleteUnbonding slashing of redelegating shares does not -take place during completion. Slashing on redelegated shares takes place -actively as a slashing occurs. The redelegation completion queue serves simply to -clean up state, as redelegations older than an unbonding period need not be kept, -as that is the max time that their old validator's evidence can be used to slash them. - -```golang -redelegationQueue(currTime time.Time): - // redelegations are in ordered queue from oldest to newest - for all redelegations whose CompleteTime < currTime: - removeRedelegation(redelegation) - return -``` +The staking validator set is updated during this process by state transitions +that run at the end of every block. As a part of this process any updated +validators are also returned back to Tendermint for inclusion in the Tendermint +validator set which is responsible for validating Tendermint messages at the +consensus layer. Operations are as following: + + - the new validator set is taken as the top `params.MaxValidators` number of + validators retrieved from the ValidatorsByPower index + - the previous validator set is compared with the new validator set + - missing validators begin unbonding + - new validator are instantly bonded + +In all cases, any validators leaving or entering the bonded validator set or +changing balances and staying within the bonded validator set incur an update +message which is passed back to Tendermint. + +## Queues + +Within staking, certain state-transitions are not instantaneous but take place +over a duration of time (typically the unbonding period). When these +transitions are mature certain operations must take place in order to complete +the state operation. This is achieved through the use of queues which are +checked/processed at the end of each block. + +### Unbonding Validators + +When a validator is kicked out of the bonded validator set (either through +being jailed, or not having sufficient bonded tokens) it begins the unbonding +process along with all its delegations begin unbonding (while still being +delegated to this validator). At this point the validator is said to be an +unbonding validator, whereby it will mature to become an "unbonded validator" +after the unbonding period has passed. + +Each block the validator queue is to be checked for mature unbonding +validators. For all unbonding validators that have finished their unbonding +period, the validator.Status is switched from sdk.Unbonding to sdk.Unbonded. +If at this switch they do not have any delegation left the validator object +instead just deleted from state. + +### Unbonding Delegations + +Complete the unbonding of all mature `UnbondingDelegations.Entries` within the +`UnbondingDelegations` queue with the following procedure: + - transfer the balance coins to the delegator's wallet address + - remove the mature entry from `UnbondingDelegation.Entries` + - remove the `UnbondingDelegation` object from the store if there are no + remaining entries. + +### Redelegations + +Complete the unbonding of all mature `Redelegation.Entries` within the +`Redelegations` queue with the following procedure: + - remove the mature entry from `Redelegation.Entries` + - remove the `Redelegation` object from the store if there are no + remaining entries. diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/hooks.md index 3644155a67ad..85fcc69cc4ce 100644 --- a/docs/spec/staking/hooks.md +++ b/docs/spec/staking/hooks.md @@ -1,19 +1,25 @@ -## Receiver Hooks +# Hooks -The staking module allow for the following hooks to be registered with staking events: +Other modules may register operations to execute when a certain event has +occurred within staking. These events can be registered to execute either +right `Before` or `After` the staking event (as per the hook name). The +following hooks can registered with staking: -``` golang -// event hooks for staking validator object -type StakingHooks interface { - OnValidatorCreated(ctx Context, address ValAddress) // Must be called when a validator is created - OnValidatorModified(ctx Context, address ValAddress) // Must be called when a validator's state changes - OnValidatorRemoved(ctx Context, address ConsAddress, operator ValAddress) // Must be called when a validator is deleted - - OnValidatorBonded(ctx Context, address ConsAddress) // called when a validator is bonded - OnValidatorBeginUnbonding(ctx Context, address ConsAddress, operator ValAddress) // called when a validator begins unbonding - - OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is created - OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation's shares are modified - OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is removed -} -``` + - `AfterValidatorCreated(Context, ValAddress)` + - called when a validator is created + - `BeforeValidatorModified(Context, ValAddress)` + - called when a validator's state is changed + - `AfterValidatorRemoved(Context, ConsAddress, ValAddress)` + - called when a validator is deleted + - `AfterValidatorBonded(Context, ConsAddress, ValAddress)` + - called when a validator is bonded + - `AfterValidatorBeginUnbonding(Context, ConsAddress, ValAddress)` + - called when a validator begins unbonding + - `AfterValidatorPowerDidChange(Context, ConsAddress, ValAddress)` + - called at EndBlock when a validator's power is changed + - `BeforeDelegationCreated(Context, AccAddress, ValAddress)` + - called when a delegation is created + - `BeforeDelegationSharesModified(Context, AccAddress, ValAddress)` + - called when a delegation's shares are modified + - `BeforeDelegationRemoved(Context, AccAddress, ValAddress)` + - called when a delegation is removed diff --git a/docs/spec/staking/messages.md b/docs/spec/staking/messages.md new file mode 100644 index 000000000000..7af6e6cccb2f --- /dev/null +++ b/docs/spec/staking/messages.md @@ -0,0 +1,167 @@ +# Messages + +In this section we describe the processing of the staking messages and the +corresponding updates to the state. All created/modified state objects +specified by each message are defined within [state.md](state.md). + +## MsgCreateValidator + +A validator is created using the `MsgCreateValidator` message. + +```golang +type MsgCreateValidator struct { + Description Description + Commission Commission + + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + PubKey crypto.PubKey + Delegation sdk.Coin +} +``` + +This message is expected to fail if: + + - another validator with this operator address is already registered + - another validator with this pubkey is already registered + - the initial self-delegation tokens are of a denom not specified as the + bonding denom + - the commission parameters are faulty, namely: + - `MaxRate` is either > 1 or < 0 + - the initial `Rate` is either negative or > `MaxRate` + - the initial `MaxChangeRate` is either negative or > `MaxRate` + - the description fields are too large + +This message creates and stores the `Validator` object at appropriate indexes. +Additionally a self-delegation is made with the initial tokens delegation +tokens `Delegation`. The validator always starts as unbonded but may be bonded +in the first end-block. + + +## MsgEditValidator + +The `Description`, `CommissionRate` of a validator can be updated using the +`MsgEditCandidacy`. + +```golang +type MsgEditCandidacy struct { + Description Description + ValidatorAddr sdk.ValAddress + CommissionRate sdk.Dec +} +``` + +This message is expected to fail if: + + - the initial `CommissionRate` is either negative or > `MaxRate` + - the `CommissionRate` has already been updated within the previous 24 hours + - the `CommissionRate` is > `MaxChangeRate` + - the description fields are too large + +This message stores the updated `Validator` object. + +## MsgDelegate + +Within this message the delegator provides coins, and in return receives +some amount of their validator's (newly created) delegator-shares that are +assigned to `Delegation.Shares`. + +```golang +type MsgDelegate struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + Delegation sdk.Coin +} +``` + +This message is expected to fail if: + + - the validator is does not exist + - the validator is jailed + +If an existing `Delegation` object for provided addresses does not already +exist than it is created as part of this message otherwise the existing +`Delegation` is updated to include the newly received shares. + +## MsgBeginUnbonding + +The begin unbonding message allows delegators to undelegate their tokens from +validator. + +```golang +type MsgBeginUnbonding struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + SharesAmount sdk.Dec +} +``` + +This message is expected to fail if: + + - the delegation doesn't exist + - the validator doesn't exist + - the delegation has less shares than `SharesAmount` + +When this message is processed the following actions occur: + - validator's `DelegatorShares` and the delegation's `Shares` are both reduced + by the message `SharesAmount` + - calculate the token worth of the shares remove that amount tokens held + within the validator + - with those removed tokens, if the validator is: + - bonded - add them to an entry in `UnbondingDelegation` (create + `UnbondingDelegation` if it doesn't exist) with a completion time a full + unbonding period from the current time. Update pool shares to reduce + BondedTokens and increase NotBondedTokens by token worth of the shares. + - unbonding - add them to an entry in `UnbondingDelegation` (create + `UnbondingDelegation` if it doesn't exist) with the same completion time + as the validator (`UnbondingMinTime`). + - unbonded - then send the coins the message `DelegatorAddr` + - if there are no more `Shares` in the delegation, then the delegation object + is removed from the store + - under this situation if the delegation is the validator's self-delegation + then also jail the validator. + +## MsgBeginRedelegate + +The redelegation command allows delegators to instantly switch validators. Once +the unbonding period has passed, the redelegation is automatically completed in +the EndBlocker. + +```golang +type MsgBeginRedelegate struct { + DelegatorAddr sdk.AccAddress + ValidatorSrcAddr sdk.ValAddress + ValidatorDstAddr sdk.ValAddress + SharesAmount sdk.Dec +} +``` + +This message is expected to fail if: + + - the delegation doesn't exist + - the source or destination validators don't exist + - the delegation has less shares than `SharesAmount` + - the source validator has a receiving redelegation which + is not matured (aka. the redelegation may be transitive) + +When this message is processed the following actions occur: + - the source validator's `DelegatorShares` and the delegations `Shares` are + both reduced by the message `SharesAmount` + - calculate the token worth of the shares remove that amount tokens held + within the source validator. + - if the source validator is: + - bonded - add an entry to the `Redelegation` (create + `Redelegation` if it doesn't exist) with a completion time a full + unbonding period from the current time. Update pool shares to reduce + BondedTokens and increase NotBondedTokens by token worth of the shares + (this may be effectively reversed in the next step however). + - unbonding - add an entry to the `Redelegation` (create `Redelegation` if + it doesn't exist) with the same completion time as the validator + (`UnbondingMinTime`). + - unbonded - no action required in this step + - Delegate the token worth to the destination validator, possibly moving + tokens back to the bonded state. + - if there are no more `Shares` in the source delegation, then the source + delegation object is removed from the store + - under this situation if the delegation is the validator's self-delegation + then also jail the validator. diff --git a/docs/spec/staking/README.md b/docs/spec/staking/overview.md similarity index 63% rename from docs/spec/staking/README.md rename to docs/spec/staking/overview.md index 16ce96a05de3..02a0b3b02bbb 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/overview.md @@ -20,19 +20,25 @@ 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)** - 1. Params - 1. Pool - 2. Validators - 3. Delegations -2. **[Transactions](transactions.md)** - 1. Create-Validator - 2. Edit-Validator - 3. Repeal-Revocation - 4. Delegate - 5. Unbond - 6. Redelegate -3. **[Validator Set Changes](valset-changes.md)** - 1. Validator set updates - 2. Slashing - 3. Automatic Unbonding + 1. **[State](state.md)** + - Pool + - Params + - Validator + - Delegation + - UnbondingDelegation + - Redelegation + 2. **[Messages](messages.md)** + - MsgCreateValidator + - MsgEditValidator + - MsgDelegate + - MsgBeginUnbonding + - MsgBeginRedelegate + 3. **[End-Block](end_block.md)** + - Validator Set Changes + - Queues + - Unbonding Validators + - Unbonding Delegations + - Redelegations + 4. **[Hooks](hooks.md)** + 5. **[Tags](tags.md)** + diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 6568293cc3f0..5e8f4d327769 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -1,89 +1,88 @@ -## State +# State -### Pool +## Pool -The pool is a space for all dynamic global state of the Cosmos Hub. It tracks -information about the total amounts of Atoms in all states, moving Atom -inflation information, etc. +The pool tracks the total amounts of tokens (each staking denom is tracked +separately) and their state (bonded or loose). + +Note: `NotBondedTokens` _includes_ both tokens in an `unbonding` state as well +as fully `unbonded` state. - Pool: `0x01 -> amino(pool)` ```golang type Pool struct { - LooseTokens int64 // tokens not associated with any bonded validator - BondedTokens int64 // reserve of bonded tokens + NotBondedTokens sdk.Int // tokens not associated with any bonded validator + BondedTokens sdk.Int // reserve of bonded tokens } ``` -### Params +## Params -Params is global data structure that stores system parameters and defines -overall functioning of the stake module. +Params is a module-wide configuration structure that stores system parameters +and defines overall functioning of the staking module. - - Params: `0x00 -> amino(params)` + - Params: `Paramsspace("staking") -> amino(params)` ```golang type Params struct { - MaxValidators uint16 // maximum number of validators - BondDenom string // bondable coin denomination + UnbondingTime time.Duration // time duration of unbonding + MaxValidators uint16 // maximum number of validators + BondDenom string // bondable coin denomination } ``` -### Validator - -Validators are identified according to the `OperatorAddr`, an SDK validator -address for the operator of the validator. +## Validator -Validators also have a `ConsPubKey`, the public key of the validator used in -Tendermint consensus. The validator can be retrieved from it's `ConsPubKey` -once it can be converted into the corresponding `ConsAddr`. Validators are -indexed in the store using the following maps: +Validators objects should be primarily stored and accessed by the +`OperatorAddr`, an SDK validator address for the operator of the validator. Two +additional indexes are maintained in order to fulfill required lookups for +slashing and validator-set updates. -- Validators: `0x02 | OperatorAddr -> amino(validator)` -- ValidatorsByConsAddr: `0x03 | ConsAddr -> OperatorAddr` -- ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> OperatorAddr` +- Validators: `0x21 | OperatorAddr -> amino(validator)` +- ValidatorsByConsAddr: `0x22 | ConsAddr -> OperatorAddr` +- ValidatorsByPower: `0x23 | BigEndian(Tokens) | OperatorAddr -> OperatorAddr` `Validators` is the primary index - it ensures that each operator can have only one associated validator, where the public key of that validator can change in the future. Delegators can refer to the immutable operator of the validator, without concern for the changing public key. -`ValidatorsByPubKey` is a secondary index that enables lookups for slashing. +`ValidatorByConsAddr` is a secondary index that enables lookups for slashing. When Tendermint reports evidence, it provides the validator address, so this -map is needed to find the operator. +map is needed to find the operator. Note that the `ConsAddr` corresponds to the +address which can be derived from the validator's `ConsPubKey`. `ValidatorsByPower` is a secondary index that provides a sorted list of -potential validators to quickly determine the current active set. For instance, -the first 100 validators in this list can be returned with every EndBlock. +potential validators to quickly determine the current active set. Note +that all validators where `Jailed` is true are not stored within this index. -The `Validator` holds the current state and some historical actions of the -validator. +Each validator's state is stored in a `Validator` struct: ```golang type Validator struct { + OperatorAddr sdk.ValAddress // address of the validator's operator; bech encoded in JSON ConsPubKey crypto.PubKey // Tendermint consensus pubkey of validator Jailed bool // has the validator been jailed? Status sdk.BondStatus // validator status (bonded/unbonding/unbonded) - Tokens sdk.Dec // delegated tokens (incl. self-delegation) + Tokens sdk.Int // delegated tokens (incl. self-delegation) DelegatorShares sdk.Dec // total shares issued to a validator's delegators - SlashRatio sdk.Dec // increases each time the validator is slashed - Description Description // description terms for the validator + Description Description // description terms for the validator // Needed for ordering vals in the by-power key - BondHeight int64 // earliest height as a bonded validator - BondIntraTxCounter int16 // block-local tx index of validator change + UnbondingHeight int64 // if unbonding, height at which this validator has begun unbonding + UnbondingMinTime time.Time // if unbonding, min time for the validator to complete unbonding - CommissionInfo CommissionInfo // info about the validator's commission + Commission Commission // info about the validator's commission } -type CommissionInfo struct { - Rate sdk.Dec // the commission rate of fees charged to any delegators - Max sdk.Dec // maximum commission rate which this validator can ever charge - ChangeRate sdk.Dec // maximum daily increase of the validator commission - ChangeToday sdk.Dec // commission rate change today, reset each day (UTC time) - LastChange int64 // unix timestamp of last commission change +type Commission struct { + Rate sdk.Dec // the commission rate charged to delegators + MaxRate sdk.Dec // maximum commission rate which this validator can ever charge + MaxChangeRate sdk.Dec // maximum daily increase of the validator commission + UpdateTime time.Time // the last time the commission rate was changed } type Description struct { @@ -94,12 +93,12 @@ type Description struct { } ``` -### Delegation +## Delegation Delegations are identified by combining `DelegatorAddr` (the address of the delegator) -with the `OperatorAddr` Delegators are indexed in the store as follows: +with the `ValidatorAddr` Delegators are indexed in the store as follows: -- Delegation: ` 0x0A | DelegatorAddr | OperatorAddr -> amino(delegation)` +- Delegation: ` 0x31 | DelegatorAddr | ValidatorAddr -> amino(delegation)` Atom holders may delegate coins to validators; under this circumstance their funds are held in a `Delegation` data structure. It is owned by one @@ -108,68 +107,86 @@ the transaction is the owner of the bond. ```golang type Delegation struct { - Shares sdk.Dec // delegation shares received - Height int64 // last height bond updated + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + Shares sdk.Dec // delegation shares received } ``` -### UnbondingDelegation +## UnbondingDelegation -Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is detected. +Shares in a `Delegation` can be unbonded, but they must for some time exist as +an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is +detected. `UnbondingDelegation` are indexed in the store as: -- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | OperatorAddr -> +- UnbondingDelegation: ` 0x32 | DelegatorAddr | ValidatorAddr -> amino(unbondingDelegation)` -- UnbondingDelegationByValOwner: ` 0x0C | OperatorAddr | DelegatorAddr | OperatorAddr -> +- UnbondingDelegationsFromValidator: ` 0x33 | ValidatorAddr | DelegatorAddr -> nil` - The first map here is used in queries, to lookup all unbonding delegations for - a given delegator, while the second map is used in slashing, to lookup all - unbonding delegations associated with a given validator that need to be - slashed. +The first map here is used in queries, to lookup all unbonding delegations for +a given delegator, while the second map is used in slashing, to lookup all +unbonding delegations associated with a given validator that need to be +slashed. A UnbondingDelegation object is created every time an unbonding is initiated. -The unbond must be completed with a second transaction provided by the -delegation owner after the unbonding period has passed. ```golang type UnbondingDelegation struct { - Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding - CompleteTime int64 // unix time to complete redelegation + DelegatorAddr sdk.AccAddress // delegator + ValidatorAddr sdk.ValAddress // validator unbonding from operator addr + Entries []UnbondingDelegationEntry // unbonding delegation entries +} + +type UnbondingDelegationEntry struct { + CreationHeight int64 // height which the unbonding took place + CompletionTime time.Time // unix time for unbonding completion + InitialBalance sdk.Coin // atoms initially scheduled to receive at completion + Balance sdk.Coin // atoms to receive at completion } ``` -### Redelegation +## Redelegation -Shares in a `Delegation` can be rebonded to a different validator, but they must -for some time exist as a `Redelegation`, where shares can be reduced if Byzantine -behavior is detected. This is tracked as moving a delegation from a `FromOperatorAddr` -to a `ToOperatorAddr`. +The bonded tokens worth of a `Delegation` may be instantly redelegated from a +source validator to a different validator (destination validator). However when +this occurs they must be tracked in a `Redelegation` object, whereby their +shares can be slashed if their tokens have contributed to a Byzantine fault +committed by the source validator. `Redelegation` are indexed in the store as: - - Redelegations: `0x0D | DelegatorAddr | FromOperatorAddr | ToOperatorAddr -> - amino(redelegation)` - - RedelegationsBySrc: `0x0E | FromOperatorAddr | ToOperatorAddr | - DelegatorAddr -> nil` - - RedelegationsByDst: `0x0F | ToOperatorAddr | FromOperatorAddr | DelegatorAddr - -> nil` + - Redelegations: `0x34 | DelegatorAddr | ValidatorSrcAddr | ValidatorDstAddr -> amino(redelegation)` + - RedelegationsBySrc: `0x35 | ValidatorSrcAddr | ValidatorDstAddr | DelegatorAddr -> nil` + - RedelegationsByDst: `0x36 | ValidatorDstAddr | ValidatorSrcAddr | DelegatorAddr -> nil` The first map here is used for queries, to lookup all redelegations for a given -delegator. The second map is used for slashing based on the `FromOperatorAddr`, -while the third map is for slashing based on the ToValOwnerAddr. +delegator. The second map is used for slashing based on the `ValidatorSrcAddr`, +while the third map is for slashing based on the `ValidatorDstAddr`. -A redelegation object is created every time a redelegation occurs. The -redelegation must be completed with a second transaction provided by the -delegation owner after the unbonding period has passed. The destination -delegation of a redelegation may not itself undergo a new redelegation until -the original redelegation has been completed. +A redelegation object is created every time a redelegation occurs. To prevent +"redelegation hopping" redelegations may not occure under the situation that: + - the (re)delegator already has another unmature redelegation in progress + with a destination to a validator (let's call it `Validator X`) + - and, the (re)delegator is attempting to create a _new_ redelegation + where the source validator for this new redelegation is `Validator-X`. ```golang type Redelegation struct { - SourceShares sdk.Dec // amount of source shares redelegating - DestinationShares sdk.Dec // amount of destination shares created at redelegation - CompleteTime int64 // unix time to complete redelegation + DelegatorAddr sdk.AccAddress // delegator + ValidatorSrcAddr sdk.ValAddress // validator redelegation source operator addr + ValidatorDstAddr sdk.ValAddress // validator redelegation destination operator addr + Entries []RedelegationEntry // redelegation entries +} + +type RedelegationEntry struct { + CreationHeight int64 // height which the redelegation took place + CompletionTime time.Time // unix time for redelegation completion + InitialBalance sdk.Coin // initial balance when redelegation started + Balance sdk.Coin // current balance (current value held in destination validator) + SharesSrc sdk.Dec // amount of source-validator shares removed by redelegation + SharesDst sdk.Dec // amount of destination-validator shares created by redelegation } ``` diff --git a/docs/spec/staking/tags.md b/docs/spec/staking/tags.md new file mode 100644 index 000000000000..c2efd3b37747 --- /dev/null +++ b/docs/spec/staking/tags.md @@ -0,0 +1,58 @@ +# Tags + +The staking module emits the following events/tags: + +## EndBlocker + +| Key | Value | +|-----------------------|-------------------------------------------| +| action | complete-unbonding\|complete-redelegation | +| delegator | {delegatorAccountAddress} | +| source-validator | {srcOperatorAddress} | +| destination-validator | {dstOperatorAddress} | + +## Handlers + +### MsgCreateValidator + +| Key | Value | +|-----------------------|----------------------| +| destination-validator | {dstOperatorAddress} | +| moniker | {validatorMoniker} | +| identity | {validatorIdentity} | + +### MsgEditValidator + +| Key | Value | +|-----------------------|----------------------| +| destination-validator | {dstOperatorAddress} | +| moniker | {validatorMoniker} | +| identity | {validatorIdentity} | + +### MsgDelegate + +| Key | Value | +|-----------------------|-------------------------------------------| +| delegator | {delegatorAccountAddress} | +| destination-validator | {dstOperatorAddress} | + +### MsgBeginRedelegate + +| Key | Value | +|-----------------------|-------------------------------------------| +| delegator | {delegatorAccountAddress} | +| source-validator | {srcOperatorAddress} | +| destination-validator | {dstOperatorAddress} | +| end-time [0] | {delegationFinishTime} | + +* [0] Time is formatted in the RFC3339 standard + +### MsgUndelegate + +| Key | Value | +|------------------|---------------------------| +| delegator | {delegatorAccountAddress} | +| source-validator | {srcOperatorAddress} | +| end-time [0] | {delegationFinishTime} | + +* [0] Time is formatted in the RFC3339 standard diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md deleted file mode 100644 index ee5b2dc938d8..000000000000 --- a/docs/spec/staking/transactions.md +++ /dev/null @@ -1,311 +0,0 @@ -# Transaction Overview - -In this section we describe the processing of the transactions and the -corresponding updates to the state. Transactions: - -* TxCreateValidator -* TxEditValidator -* TxDelegation -* TxStartUnbonding -* TxRedelegate - -Other important state changes: - -* Update Validators - -Other notes: - -* `tx` denotes a reference to the transaction being processed -* `sender` denotes the address of the sender of the transaction -* `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and - modify objects from the store -* `sdk.Dec` refers to a decimal type specified by the SDK. - -## TxCreateValidator - -* triggers: `distribution.CreateValidatorDistribution` - -A validator is created using the `TxCreateValidator` transaction. - -```golang -type TxCreateValidator struct { - Description Description - Commission Commission - - DelegatorAddr sdk.AccAddress - ValidatorAddr sdk.ValAddress - PubKey crypto.PubKey - Delegation sdk.Coin -} - -createValidator(tx TxCreateValidator): - ok := validatorExists(tx.ValidatorAddr) - if ok return err // only one validator per address - - ok := validatorByPubKeyExists(tx.PubKey) - if ok return err // only one validator per public key - - err := validateDenom(tx.Delegation.Denom) - if err != nil return err // denomination must be valid - - validator := NewValidator(tx.ValidatorAddr, tx.PubKey, tx.Description) - - err := setInitialCommission(validator, tx.Commission, blockTime) - if err != nil return err // must be able to set initial commission correctly - - // set the validator and public key - setValidator(validator) - setValidatorByPubKeyIndex(validator) - - // delegate coins from tx.DelegatorAddr to the validator - err := delegate(tx.DelegatorAddr, tx.Delegation, validator) - if err != nil return err // must be able to set delegation correctly - - tags := createTags(tx) - return tags -``` - -## TxEditValidator - -If either the `Description`, `Commission`, or the `ValidatorAddr` need to be -updated, the `TxEditCandidacy` transaction should be sent from the operator -account: - -```golang -type TxEditCandidacy struct { - Description Description - ValidatorAddr sdk.ValAddress - CommissionRate sdk.Dec -} - -editCandidacy(tx TxEditCandidacy): - validator, ok := getValidator(tx.ValidatorAddr) - if !ok return err // validator must exist - - // Attempt to update the validator's description. The description provided - // must be valid. - description, err := updateDescription(validator, tx.Description) - if err != nil return err - - // a validator is not required to update it's commission rate - if tx.CommissionRate != nil { - // Attempt to update a validator's commission rate. The rate provided - // must be valid. It's rate can only be updated once a day. - err := updateValidatorCommission(validator, tx.CommissionRate) - if err != nil return err - } - - // set the validator and public key - setValidator(validator) - - tags := createTags(tx) - return tags -``` - -### TxDelegate - - - triggers: `distribution.CreateOrModDelegationDistribution` - -Within this transaction the delegator provides coins, and in return receives -some amount of their validator's delegator-shares that are assigned to -`Delegation.Shares`. - -```golang -type TxDelegate struct { - DelegatorAddr sdk.Address - ValidatorAddr sdk.Address - Amount sdk.Coin -} - -delegate(tx TxDelegate): - pool = getPool() - if validator.Status == Jailed return - - delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr) - if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr) - - validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool) - delegation.Shares += issuedDelegatorShares - - setDelegation(delegation) - updateValidator(validator) - setPool(pool) - return -``` - -### TxStartUnbonding - -Delegator unbonding is defined with the following transaction: - -```golang -type TxStartUnbonding struct { - DelegatorAddr sdk.Address - ValidatorAddr sdk.Address - Shares string -} - -startUnbonding(tx TxStartUnbonding): - delegation, found = getDelegatorBond(store, sender, tx.PubKey) - if !found == nil return - - if bond.Shares < tx.Shares - return ErrNotEnoughBondShares - - validator, found = GetValidator(tx.ValidatorAddr) - if !found { - return err - - bond.Shares -= tx.Shares - - revokeCandidacy = false - if bond.Shares.IsZero() { - - if bond.DelegatorAddr == validator.Operator && validator.Jailed == false - revokeCandidacy = true - - removeDelegation( bond) - else - bond.Height = currentBlockHeight - setDelegation(bond) - - pool = GetPool() - validator, pool, returnAmount = validator.removeDelShares(pool, tx.Shares) - setPool( pool) - - unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio) - setUnbondingDelegation(unbondingDelegation) - - if revokeCandidacy - validator.Jailed = true - - validator = updateValidator(validator) - - if validator.Status == Unbonded && validator.DelegatorShares == 0 { - removeValidator(validator.Operator) - - return -``` - -### TxRedelegation - -The redelegation command allows delegators to instantly switch validators. Once -the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. - -```golang -type TxRedelegate struct { - DelegatorAddr Address - ValidatorFrom Validator - ValidatorTo Validator - Shares sdk.Dec - CompletedTime int64 -} - -redelegate(tx TxRedelegate): - - pool = getPool() - delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Operator) - if delegation == nil - return - - if delegation.Shares < tx.Shares - return - delegation.shares -= Tx.Shares - validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares) - setPool(pool) - - redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom, - tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime) - setRedelegation(redelegation) - return -``` - -### Update Validators - -Within many transactions the validator set must be updated based on changes in -power to a single validator. This process also updates the Tendermint-Updates -store for use in end-block when validators are either added or kicked from the -Tendermint. - -```golang -updateBondedValidators(newValidator Validator) (updatedVal Validator) - - kickCliffValidator = false - oldCliffValidatorAddr = getCliffValidator(ctx) - - // add the actual validator power sorted store - maxValidators = GetParams(ctx).MaxValidators - iterator = ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount = 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - setCliffValidator(ctx, validator, GetPool(ctx)) - iterator.Close() - break - - // either retrieve the original validator from the store, - // or under the situation that this is the "new validator" just - // use the validator provided because it has not yet been updated - // in the main validator store - - operatorAddr = iterator.Value() - if bytes.Equal(operatorAddr, newValidator.Operator) { - validator = newValidator - else - validator = getValidator(operatorAddr) - - // if not previously a validator (and unjailed), - // kick the cliff validator / bond this new validator - if validator.Status() != Bonded && !validator.Jailed { - kickCliffValidator = true - - validator = bondValidator(ctx, store, validator) - if bytes.Equal(operatorAddr, newValidator.Operator) { - updatedVal = validator - - bondedValidatorsCount++ - iterator.Next() - - // perform the actual kicks - if oldCliffValidatorAddr != nil && kickCliffValidator { - validator = getValidator(store, oldCliffValidatorAddr) - unbondValidator(ctx, store, validator) - return - -// perform all the store operations for when a validator status becomes unbonded -unbondValidator(ctx Context, store KVStore, validator Validator) - pool = GetPool(ctx) - - // set the status - validator, pool = validator.UpdateStatus(pool, Unbonded) - setPool(ctx, pool) - - // save the now unbonded validator record - setValidator(validator) - - // add to accumulated changes for tendermint - setTendermintUpdates(validator.abciValidatorZero) - - // also remove from the bonded validators index - removeValidatorsBonded(validator) -} - -// perform all the store operations for when a validator status becomes bonded -bondValidator(ctx Context, store KVStore, validator Validator) Validator - pool = GetPool(ctx) - - // set the status - validator, pool = validator.UpdateStatus(pool, Bonded) - setPool(ctx, pool) - - // save the now bonded validator record to the three referenced stores - setValidator(validator) - setValidatorsBonded(validator) - - // add to accumulated changes for tendermint - setTendermintUpdates(validator.abciValidator) - - return validator -``` diff --git a/scripts/Makefile b/scripts/Makefile index 90cb84231106..910c6f68db22 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -34,10 +34,10 @@ cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3) go_install = $(call go_get,$(1),$(2),$(3)) && cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && $(GO) install ### -# get_tools +# tools ### -all: get_tools -get_tools: $(GOPATH)/bin/dep $(GOPATH)/bin/gometalinter $(GOPATH)/bin/statik $(GOPATH)/bin/goimports +all: tools +tools: $(GOPATH)/bin/dep $(GOPATH)/bin/gometalinter $(GOPATH)/bin/statik $(GOPATH)/bin/goimports $(GOPATH)/bin/dep: $(call go_get,golang,dep,22125cfaa6ddc71e145b1535d4b7ee9744fefff2) @@ -53,5 +53,4 @@ $(GOPATH)/bin/statik: $(GOPATH)/bin/goimports: go get golang.org/x/tools/cmd/goimports -.PHONY: all get_tools - +.PHONY: all diff --git a/scripts/install/install_sdk_arm.sh b/scripts/install/install_sdk_arm.sh index 24caa51f238b..951e2830c2a3 100644 --- a/scripts/install/install_sdk_arm.sh +++ b/scripts/install/install_sdk_arm.sh @@ -4,7 +4,7 @@ BRANCH=master REPO=github.com/cosmos/cosmos-sdk -GO_VERSION=1.11.2 +GO_VERSION=1.11.4 sudo apt-get update -y sudo apt-get upgrade -y @@ -31,7 +31,7 @@ cd $GOPATH/src/$REPO # build & install master git checkout $BRANCH -LEDGER_ENABLED=false make get_tools +LEDGER_ENABLED=false make tools LEDGER_ENABLED=false make get_vendor_deps LEDGER_ENABLED=false make install diff --git a/scripts/install/install_sdk_bsd.sh b/scripts/install/install_sdk_bsd.sh index df416dca902e..fda0eb1e1dc2 100644 --- a/scripts/install/install_sdk_bsd.sh +++ b/scripts/install/install_sdk_bsd.sh @@ -16,7 +16,7 @@ set BRANCH=master set REPO=github.com/cosmos/cosmos-sdk -set GO_VERSION=1.11.2 +set GO_VERSION=1.11.4 sudo pkg update @@ -47,7 +47,7 @@ cd $GOPATH/src/$REPO # build & install master git checkout $BRANCH -gmake get_tools +gmake tools gmake get_vendor_deps gmake install gmake install_examples diff --git a/scripts/install/install_sdk_ubuntu.sh b/scripts/install/install_sdk_ubuntu.sh index 264599960e9d..3ac23ccaeebd 100644 --- a/scripts/install/install_sdk_ubuntu.sh +++ b/scripts/install/install_sdk_ubuntu.sh @@ -7,7 +7,7 @@ BRANCH=master REPO=github.com/cosmos/cosmos-sdk -GO_VERSION=1.11.2 +GO_VERSION=1.11.4 sudo apt-get update -y sudo apt-get upgrade -y @@ -34,7 +34,7 @@ cd $GOPATH/src/$REPO # build & install master git checkout $BRANCH -LEDGER_ENABLED=false make get_tools +LEDGER_ENABLED=false make tools LEDGER_ENABLED=false make get_vendor_deps LEDGER_ENABLED=false make install diff --git a/scripts/multisim.sh b/scripts/multisim.sh index b8d30772a483..e27035983dde 100755 --- a/scripts/multisim.sh +++ b/scripts/multisim.sh @@ -3,7 +3,8 @@ seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391 \ 11 22 44 77 99 2020 3232 123123 124124 582582 18931893 29892989 30123012 47284728 37827) blocks=$1 -testname=$2 +period=$2 +testname=$3 echo "Running multi-seed simulation with seeds ${seeds[@]}" echo "Running $blocks blocks per seed" @@ -22,7 +23,7 @@ sim() { file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -u +"%Y-%m-%dT%H:%M:%S+00:00").stdout" echo "Writing stdout to $file..." go test ./cmd/gaia/app -run $testname -SimulationEnabled=true -SimulationNumBlocks=$blocks \ - -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -SimulationPeriod=5 -v -timeout 24h > $file + -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -SimulationPeriod=$period -v -timeout 24h > $file } i=0 diff --git a/server/config/config.go b/server/config/config.go index 239097b13737..dc9314729ab6 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -7,13 +7,15 @@ import ( ) const ( - defaultMinimumFees = "" + defaultMinGasPrices = "" ) // BaseConfig defines the server's basic configuration type BaseConfig struct { - // Tx minimum fee - MinFees string `mapstructure:"minimum_fees"` + // The minimum gas prices a validator is willing to accept for processing a + // transaction. A transaction's fees must meet the minimum of each denomination + // specified in this config (e.g. 0.01photino,0.0001stake). + MinGasPrices string `mapstructure:"minimum-gas-prices"` } // Config defines the server's top level configuration @@ -21,17 +23,27 @@ type Config struct { BaseConfig `mapstructure:",squash"` } -// SetMinimumFee sets the minimum fee. -func (c *Config) SetMinimumFees(fees sdk.Coins) { c.MinFees = fees.String() } +// SetMinGasPrices sets the validator's minimum gas prices. +func (c *Config) SetMinGasPrices(gasPrices sdk.DecCoins) { + c.MinGasPrices = gasPrices.String() +} -// SetMinimumFee sets the minimum fee. -func (c *Config) MinimumFees() sdk.Coins { - fees, err := sdk.ParseCoins(c.MinFees) +// GetMinGasPrices returns the validator's minimum gas prices based on the set +// configuration. +func (c *Config) GetMinGasPrices() sdk.DecCoins { + gasPrices, err := sdk.ParseDecCoins(c.MinGasPrices) if err != nil { - panic(fmt.Sprintf("invalid minimum fees: %v", err)) + panic(fmt.Sprintf("invalid minimum gas prices: %v", err)) } - return fees + + return gasPrices } // DefaultConfig returns server's default configuration. -func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} } +func DefaultConfig() *Config { + return &Config{ + BaseConfig{ + MinGasPrices: defaultMinGasPrices, + }, + } +} diff --git a/server/config/config_test.go b/server/config/config_test.go index d68d84415eb2..c2f8832756b8 100644 --- a/server/config/config_test.go +++ b/server/config/config_test.go @@ -10,11 +10,11 @@ import ( func TestDefaultConfig(t *testing.T) { cfg := DefaultConfig() - require.True(t, cfg.MinimumFees().IsZero()) + require.True(t, cfg.GetMinGasPrices().IsZero()) } func TestSetMinimumFees(t *testing.T) { cfg := DefaultConfig() - cfg.SetMinimumFees(sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(100))}) - require.Equal(t, "100foo", cfg.MinFees) + cfg.SetMinGasPrices(sdk.DecCoins{sdk.NewDecCoin("foo", 5)}) + require.Equal(t, "5.000000000000000000foo", cfg.MinGasPrices) } diff --git a/server/config/toml.go b/server/config/toml.go index 3c60fbdf93fb..7486e171f9f7 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -13,28 +13,31 @@ const defaultConfigTemplate = `# This is a TOML config file. ##### main base config options ##### -# Validators reject any tx from the mempool with less than the minimum fee per gas. -minimum_fees = "{{ .BaseConfig.MinFees }}" +# The minimum gas prices a validator is willing to accept for processing a +# transaction. A transaction's fees must meet the minimum of each denomination +# specified in this config (e.g. 0.01photino,0.0001stake). +minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}" ` var configTemplate *template.Template func init() { var err error - tmpl := template.New("cosmosConfigFileTemplate") + tmpl := template.New("gaiaConfigFileTemplate") if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil { panic(err) } } -// ParseConfig retrieves the default environment configuration for Cosmos. +// ParseConfig retrieves the default environment configuration for Gaia. func ParseConfig() (*Config, error) { conf := DefaultConfig() err := viper.Unmarshal(conf) return conf, err } -// WriteConfigFile renders config using the template and writes it to configFilePath. +// WriteConfigFile renders config using the template and writes it to +// configFilePath. func WriteConfigFile(configFilePath string, config *Config) { var buffer bytes.Buffer diff --git a/server/export.go b/server/export.go index aa30597da910..16187fa664e5 100644 --- a/server/export.go +++ b/server/export.go @@ -33,7 +33,7 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C return err } - if emptyState { + if emptyState || appExporter == nil { fmt.Println("WARNING: State is not initialized. Returning genesis file.") genesisFile := path.Join(home, "config", "genesis.json") genesis, err := ioutil.ReadFile(genesisFile) diff --git a/server/mock/app.go b/server/mock/app.go index 2b0b8ed5e049..e2f94c1f1883 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -26,7 +26,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { } // Capabilities key to access the main KVStore. - capKeyMainStore := sdk.NewKVStoreKey("main") + capKeyMainStore := sdk.NewKVStoreKey(bam.MainStoreKey) // Create BaseApp. baseApp := bam.NewBaseApp("kvstore", logger, db, decodeTx) diff --git a/server/mock/store.go b/server/mock/store.go index ec963a1bc22c..3aecc1734407 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -50,7 +50,7 @@ func (ms multiStore) LastCommitID() sdk.CommitID { panic("not implemented") } -func (ms multiStore) SetPruning(s sdk.PruningStrategy) { +func (ms multiStore) SetPruning(opts sdk.PruningOptions) { panic("not implemented") } diff --git a/server/start.go b/server/start.go index cf39ff71b62a..0f09f773664b 100644 --- a/server/start.go +++ b/server/start.go @@ -15,12 +15,13 @@ import ( "github.com/tendermint/tendermint/proxy" ) +// Tendermint full-node start flags const ( flagWithTendermint = "with-tendermint" flagAddress = "address" flagTraceStore = "trace-store" flagPruning = "pruning" - flagMinimumFees = "minimum_fees" + FlagMinGasPrices = "minimum-gas-prices" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -47,7 +48,10 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file") cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything") - cmd.Flags().String(flagMinimumFees, "", "Minimum fees validator will accept for transactions") + cmd.Flags().String( + FlagMinGasPrices, "", + "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.0001stake)", + ) // add support for all Tendermint-specific command line options tcmd.AddNodeFlags(cmd) @@ -114,10 +118,11 @@ func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { return nil, err } + UpgradeOldPrivValFile(cfg) // create & start tendermint node tmNode, err := node.NewNode( cfg, - pvm.LoadOrGenFilePV(cfg.PrivValidatorFile()), + pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(app), node.DefaultGenesisDocProviderFunc(cfg), diff --git a/server/tm_cmds.go b/server/tm_cmds.go index 33994b0cbab0..93a9caa6b23f 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -6,16 +6,24 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/cosmos/cosmos-sdk/codec" - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/p2p" pvm "github.com/tendermint/tendermint/privval" + tversion "github.com/tendermint/tendermint/version" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + versionString = `Tendermint: %s +ABCI: %s +BlockProtocol: %d +P2PProtocol: %d +` +) + // ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout func ShowNodeIDCmd(ctx *Context) *cobra.Command { return &cobra.Command{ @@ -41,10 +49,12 @@ func ShowValidatorCmd(ctx *Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cfg := ctx.Config - privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorFile()) - valPubKey := privValidator.PubKey + UpgradeOldPrivValFile(cfg) + privValidator := pvm.LoadOrGenFilePV( + cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) + valPubKey := privValidator.GetPubKey() - if viper.GetBool(client.FlagJson) { + if viper.GetString(cli.OutputFlag) == "json" { return printlnJSON(valPubKey) } @@ -57,7 +67,8 @@ func ShowValidatorCmd(ctx *Context) *cobra.Command { return nil }, } - cmd.Flags().Bool(client.FlagJson, false, "get machine parseable output") + + cmd.Flags().StringP(cli.OutputFlag, "o", "text", "Output format (text|json)") return &cmd } @@ -67,11 +78,14 @@ func ShowAddressCmd(ctx *Context) *cobra.Command { Use: "show-address", Short: "Shows this node's tendermint validator consensus address", RunE: func(cmd *cobra.Command, args []string) error { + cfg := ctx.Config - privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorFile()) - valConsAddr := (sdk.ConsAddress)(privValidator.Address) + UpgradeOldPrivValFile(cfg) + privValidator := pvm.LoadOrGenFilePV( + cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) + valConsAddr := (sdk.ConsAddress)(privValidator.GetAddress()) - if viper.GetBool(client.FlagJson) { + if viper.GetString(cli.OutputFlag) == "json" { return printlnJSON(valConsAddr) } @@ -80,7 +94,26 @@ func ShowAddressCmd(ctx *Context) *cobra.Command { }, } - cmd.Flags().Bool(client.FlagJson, false, "get machine parseable output") + cmd.Flags().StringP(cli.OutputFlag, "o", "text", "Output format (text|json)") + return cmd +} + +// VersionCmd prints tendermint and ABCI version numbers. +func VersionCmd(ctx *Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: "Print tendermint libraries' version", + Long: `Print protocols' and libraries' version numbers +against which this app has been compiled. +`, + RunE: func(cmd *cobra.Command, args []string) error { + + fmt.Printf(versionString, tversion.Version, tversion.ABCIVersion, + tversion.BlockProtocol.Uint64(), tversion.P2PProtocol.Uint64()) + + return nil + }, + } return cmd } @@ -102,7 +135,7 @@ func UnsafeResetAllCmd(ctx *Context) *cobra.Command { Short: "Resets the blockchain database, removes address book files, and resets priv_validator.json to the genesis state", RunE: func(cmd *cobra.Command, args []string) error { cfg := ctx.Config - tcmd.ResetAll(cfg.DBDir(), cfg.P2P.AddrBookFile(), cfg.PrivValidatorFile(), ctx.Logger) + tcmd.ResetAll(cfg.DBDir(), cfg.P2P.AddrBookFile(), cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile(), ctx.Logger) return nil }, } diff --git a/server/util.go b/server/util.go index 5c07d1b465c3..5092cab9ad81 100644 --- a/server/util.go +++ b/server/util.go @@ -18,6 +18,7 @@ import ( "github.com/tendermint/tendermint/libs/cli" tmflags "github.com/tendermint/tendermint/libs/cli/flags" "github.com/tendermint/tendermint/libs/log" + pvm "github.com/tendermint/tendermint/privval" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -103,18 +104,15 @@ func interceptLoadConfig() (conf *cfg.Config, err error) { conf, err = tcmd.ParseConfig() // NOTE: ParseConfig() creates dir/files as necessary. } - cosmosConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml") - viper.SetConfigName("cosmos") - _ = viper.MergeInConfig() - var cosmosConf *config.Config - if _, err := os.Stat(cosmosConfigFilePath); os.IsNotExist(err) { - cosmosConf, _ := config.ParseConfig() - config.WriteConfigFile(cosmosConfigFilePath, cosmosConf) + // create a default gaia config file if it does not exist + gaiaConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml") + if _, err := os.Stat(gaiaConfigFilePath); os.IsNotExist(err) { + gaiaConf, _ := config.ParseConfig() + config.WriteConfigFile(gaiaConfigFilePath, gaiaConf) } - if cosmosConf == nil { - _, err = config.ParseConfig() - } + viper.SetConfigName("gaiad") + err = viper.MergeInConfig() return } @@ -144,6 +142,7 @@ func AddCommands( ShowNodeIDCmd(ctx), ShowValidatorCmd(ctx), ShowAddressCmd(ctx), + VersionCmd(ctx), ) rootCmd.AddCommand( @@ -224,6 +223,16 @@ func TrapSignal(cleanupFunc func()) { }() } +// UpgradeOldPrivValFile converts old priv_validator.json file (prior to Tendermint 0.28) +// to the new priv_validator_key.json and priv_validator_state.json files. +func UpgradeOldPrivValFile(config *cfg.Config) { + if _, err := os.Stat(config.OldPrivValidatorFile()); !os.IsNotExist(err) { + if oldFilePV, err := pvm.LoadOldFilePV(config.OldPrivValidatorFile()); err == nil { + oldFilePV.Upgrade(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) + } + } +} + func skipInterface(iface net.Interface) bool { if iface.Flags&net.FlagUp == 0 { return true // interface down diff --git a/store/codec.go b/store/codec.go index 353cd2e3cd77..181f12e511b7 100644 --- a/store/codec.go +++ b/store/codec.go @@ -7,7 +7,7 @@ import ( // Import cosmos-sdk/types/store.go for convenience. // nolint type ( - PruningStrategy = types.PruningStrategy + PruningOptions = types.PruningOptions Store = types.Store Committer = types.Committer CommitStore = types.CommitStore diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go index 76e673de5a67..b662bcf45ddd 100644 --- a/store/dbstoreadapter.go +++ b/store/dbstoreadapter.go @@ -64,4 +64,4 @@ func (cdsa commitDBStoreAdapter) LastCommitID() CommitID { } } -func (cdsa commitDBStoreAdapter) SetPruning(_ PruningStrategy) {} +func (cdsa commitDBStoreAdapter) SetPruning(_ PruningOptions) {} diff --git a/store/iavlstore.go b/store/iavlstore.go index fccde38e27e0..26c739da3bf3 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -19,7 +19,7 @@ const ( ) // load the iavl store -func LoadIAVLStore(db dbm.DB, id CommitID, pruning sdk.PruningStrategy) (CommitStore, error) { +func LoadIAVLStore(db dbm.DB, id CommitID, pruning sdk.PruningOptions) (CommitStore, error) { tree := iavl.NewMutableTree(db, defaultIAVLCacheSize) _, err := tree.LoadVersion(id.Version) if err != nil { @@ -38,7 +38,6 @@ var _ Queryable = (*iavlStore)(nil) // iavlStore Implements KVStore and CommitStore. type iavlStore struct { - // The underlying tree. tree *iavl.MutableTree @@ -102,17 +101,9 @@ func (st *iavlStore) LastCommitID() CommitID { } // Implements Committer. -func (st *iavlStore) SetPruning(pruning sdk.PruningStrategy) { - switch pruning { - case sdk.PruneEverything: - st.numRecent = 0 - st.storeEvery = 0 - case sdk.PruneNothing: - st.storeEvery = 1 - case sdk.PruneSyncable: - st.numRecent = 100 - st.storeEvery = 10000 - } +func (st *iavlStore) SetPruning(opt sdk.PruningOptions) { + st.numRecent = opt.KeepRecent() + st.storeEvery = opt.KeepEvery() } // VersionExists returns whether or not a given version is stored. diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 0f80657b84b6..3d70451e7273 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -13,7 +13,7 @@ import ( func TestVerifyIAVLStoreQueryProof(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() - iStore, err := LoadIAVLStore(db, CommitID{}, sdk.PruneNothing) + iStore, err := LoadIAVLStore(db, CommitID{}, PruneNothing) store := iStore.(*iavlStore) require.Nil(t, err) store.Set([]byte("MYKEY"), []byte("MYVALUE")) diff --git a/store/pruning.go b/store/pruning.go new file mode 100644 index 000000000000..9a7aeb4d29e6 --- /dev/null +++ b/store/pruning.go @@ -0,0 +1,29 @@ +package store + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// default pruning strategies +var ( + // PruneEverything means all saved states will be deleted, storing only the current state + PruneEverything = sdk.NewPruningOptions(0, 0) + // PruneNothing means all historic states will be saved, nothing will be deleted + PruneNothing = sdk.NewPruningOptions(0, 1) + // PruneSyncable means only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) + PruneSyncable = sdk.NewPruningOptions(100, 10000) +) + +func NewPruningOptions(strategy string) (opt PruningOptions) { + switch strategy { + case "nothing": + opt = PruneNothing + case "everything": + opt = PruneEverything + case "syncable": + opt = PruneSyncable + default: + opt = PruneSyncable + } + return +} diff --git a/store/rootmultistore.go b/store/rootmultistore.go index c309f9e9b28a..fa576d3dcc6a 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -24,7 +24,7 @@ const ( type rootMultiStore struct { db dbm.DB lastCommitID CommitID - pruning sdk.PruningStrategy + pruningOpts sdk.PruningOptions storesParams map[StoreKey]storeParams stores map[StoreKey]CommitStore keysByName map[string]StoreKey @@ -47,10 +47,10 @@ func NewCommitMultiStore(db dbm.DB) *rootMultiStore { } // Implements CommitMultiStore -func (rs *rootMultiStore) SetPruning(pruning sdk.PruningStrategy) { - rs.pruning = pruning +func (rs *rootMultiStore) SetPruning(pruningOpts sdk.PruningOptions) { + rs.pruningOpts = pruningOpts for _, substore := range rs.stores { - substore.SetPruning(pruning) + substore.SetPruning(pruningOpts) } } @@ -355,7 +355,7 @@ func (rs *rootMultiStore) loadCommitStoreFromParams(key sdk.StoreKey, id CommitI // TODO: id? // return NewCommitMultiStore(db, id) case sdk.StoreTypeIAVL: - store, err = LoadIAVLStore(db, id, rs.pruning) + store, err = LoadIAVLStore(db, id, rs.pruningOpts) return case sdk.StoreTypeDB: store = commitDBStoreAdapter{dbStoreAdapter{db}} diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index cd555d6f2d5a..10f0956562ba 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -195,6 +195,7 @@ func TestMultiStoreQuery(t *testing.T) { func newMultiStoreWithMounts(db dbm.DB) *rootMultiStore { store := NewCommitMultiStore(db) + store.pruningOpts = PruneSyncable store.MountStoreWithDB( sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, nil) store.MountStoreWithDB( diff --git a/store/transientstore.go b/store/transientstore.go index 63b154c017f7..2de1197b977d 100644 --- a/store/transientstore.go +++ b/store/transientstore.go @@ -26,7 +26,7 @@ func (ts *transientStore) Commit() (id CommitID) { } // Implements CommitStore -func (ts *transientStore) SetPruning(pruning PruningStrategy) { +func (ts *transientStore) SetPruning(opts PruningOptions) { } // Implements CommitStore diff --git a/tests/check_basecli.sh b/tests/check_basecli.sh deleted file mode 100755 index ffcf39a5178b..000000000000 --- a/tests/check_basecli.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/sh - -# Note: Bucky, I know you want to kill bash tests. -# Please show me how to do an alternative to this. -# I would rather get code running before I leave than -# fight trying to invent some new test harness that -# no one else will understand. -# -# Thus, I leave this as an exercise to the reader to -# port into a non-bash version. And I don't do it proper... -# just automate my manual tests - -# WARNING!!! -rm -rf ~/.basecoind ~/.basecli -cd $GOPATH/src/github.com/cosmos/cosmos-sdk -# make get_vendor_deps -make build - -# init stuff -SEED=`./build/basecoind init | tail -1` -PASS='some-silly-123' -(echo $PASS; echo $SEED) | ./build/basecli keys add demo --recover -ADDR=`./build/basecli keys show demo | cut -f3` -echo "Recovered seed for demo:" $ADDR - -# start up server -./build/basecoind start > ~/.basecoind/basecoind.log 2>&1 & -sleep 5 -PID_SERVER=$! - -# query original state -TO='ABCAFE00DEADBEEF00CAFE00DEADBEEF00CAFE00' -echo; echo "My account:" $ADDR -./build/basecli account $ADDR -echo; echo "Empty account:" $TO -./build/basecli account $TO - -# send some money -TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --from=demo --seq=0` -echo; echo "SendTx"; echo $TX -HASH=`echo $TX | cut -d' ' -f6` -echo "tx hash:" $HASH - -# let some blocks come up.... -./build/basecli status | jq .latest_block_height -sleep 2 -./build/basecli status | jq .latest_block_height - -# balances change -echo; echo "My account went down" -./build/basecli account $ADDR -echo; echo "Empty account got some cash" -./build/basecli account $TO - -# query original tx -echo; echo "View tx" -./build/basecli tx $HASH - -# wait a bit then dump out some blockchain state -sleep 10 -./build/basecli status --trace -./build/basecli block --trace -./build/basecli validatorset --trace - -# shutdown, but add a sleep if you want to manually run some cli scripts -# against this server before it goes away -# sleep 120 -kill $PID_SERVER - diff --git a/tests/gobash.go b/tests/gobash.go index 9c4312c6d089..e7bcaa100c82 100644 --- a/tests/gobash.go +++ b/tests/gobash.go @@ -93,18 +93,21 @@ func GoExecuteTWithStdout(t *testing.T, cmd string) (proc *Process) { // Without this, the test halts ?! // (theory: because stdout and/or err aren't connected to anything the process halts) - go func() { + go func(proc *Process) { _, err := ioutil.ReadAll(proc.StdoutPipe) if err != nil { fmt.Println("-------------ERR-----------------------", err) return } - _, err = ioutil.ReadAll(proc.StderrPipe) + }(proc) + + go func(proc *Process) { + _, err := ioutil.ReadAll(proc.StderrPipe) if err != nil { fmt.Println("-------------ERR-----------------------", err) return } - }() + }(proc) err = proc.Cmd.Start() require.NoError(t, err) diff --git a/types/address.go b/types/address.go index 36eb5ac32bc5..a1663aebb528 100644 --- a/types/address.go +++ b/types/address.go @@ -31,6 +31,22 @@ const ( Bech32PrefixConsPub = "cosmosvalconspub" ) +// Address is a common interface for different types of addresses used by the SDK +type Address interface { + Equals(Address) bool + Empty() bool + Marshal() ([]byte, error) + MarshalJSON() ([]byte, error) + Bytes() []byte + String() string + Format(s fmt.State, verb rune) +} + +// Ensure that different address types implement the interface +var _ Address = AccAddress{} +var _ Address = ValAddress{} +var _ Address = ConsAddress{} + // ---------------------------------------------------------------------------- // account // ---------------------------------------------------------------------------- @@ -65,7 +81,7 @@ func AccAddressFromBech32(address string) (addr AccAddress, err error) { } // Returns boolean for whether two AccAddresses are Equal -func (aa AccAddress) Equals(aa2 AccAddress) bool { +func (aa AccAddress) Equals(aa2 Address) bool { if aa.Empty() && aa2.Empty() { return true } @@ -181,7 +197,7 @@ func ValAddressFromBech32(address string) (addr ValAddress, err error) { } // Returns boolean for whether two ValAddresses are Equal -func (va ValAddress) Equals(va2 ValAddress) bool { +func (va ValAddress) Equals(va2 Address) bool { if va.Empty() && va2.Empty() { return true } @@ -303,7 +319,7 @@ func GetConsAddress(pubkey crypto.PubKey) ConsAddress { } // Returns boolean for whether two ConsAddress are Equal -func (ca ConsAddress) Equals(ca2 ConsAddress) bool { +func (ca ConsAddress) Equals(ca2 Address) bool { if ca.Empty() && ca2.Empty() { return true } diff --git a/types/address_test.go b/types/address_test.go index 51c44a12cd60..f3c44e4d34d4 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -221,3 +221,31 @@ func TestConfiguredPrefix(t *testing.T) { } } + +func TestAddressInterface(t *testing.T) { + var pub ed25519.PubKeyEd25519 + rand.Read(pub[:]) + + addrs := []types.Address{ + types.ConsAddress(pub.Address()), + types.ValAddress(pub.Address()), + types.AccAddress(pub.Address()), + } + + for _, addr := range addrs { + switch addr := addr.(type) { + case types.AccAddress: + _, err := types.AccAddressFromBech32(addr.String()) + require.Nil(t, err) + case types.ValAddress: + _, err := types.ValAddressFromBech32(addr.String()) + require.Nil(t, err) + case types.ConsAddress: + _, err := types.ConsAddressFromBech32(addr.String()) + require.Nil(t, err) + default: + t.Fail() + } + } + +} diff --git a/types/coin.go b/types/coin.go index 1b353e6b43dd..6fc0e8c5a3cc 100644 --- a/types/coin.go +++ b/types/coin.go @@ -30,6 +30,9 @@ func NewCoin(denom string, amount Int) Coin { if amount.LT(ZeroInt()) { panic(fmt.Sprintf("negative coin amount: %v\n", amount)) } + if strings.ToLower(denom) != denom { + panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom)) + } return Coin{ Denom: denom, @@ -93,7 +96,7 @@ func (coin Coin) Minus(coinB Coin) Coin { } res := Coin{coin.Denom, coin.Amount.Sub(coinB.Amount)} - if !res.IsNotNegative() { + if res.IsNegative() { panic("negative count amount") } @@ -104,14 +107,14 @@ func (coin Coin) Minus(coinB Coin) Coin { // // TODO: Remove once unsigned integers are used. func (coin Coin) IsPositive() bool { - return (coin.Amount.Sign() == 1) + return coin.Amount.Sign() == 1 } -// IsNotNegative returns true if coin amount is not negative and false otherwise. +// IsNegative returns true if the coin amount is negative and false otherwise. // // TODO: Remove once unsigned integers are used. -func (coin Coin) IsNotNegative() bool { - return (coin.Amount.Sign() != -1) +func (coin Coin) IsNegative() bool { + return coin.Amount.Sign() == -1 } //----------------------------------------------------------------------------- @@ -132,12 +135,16 @@ func (coins Coins) String() string { return out[:len(out)-1] } -// IsValid asserts the Coins are sorted and have positive amounts. +// IsValid asserts the Coins are sorted, have positive amount, +// and Denom does not contain upper case characters. func (coins Coins) IsValid() bool { switch len(coins) { case 0: return true case 1: + if strings.ToLower(coins[0].Denom) != coins[0].Denom { + return false + } return coins[0].IsPositive() default: // check single coin case @@ -147,6 +154,9 @@ func (coins Coins) IsValid() bool { lowDenom := coins[0].Denom for _, coin := range coins[1:] { + if strings.ToLower(coin.Denom) != coin.Denom { + return false + } if coin.Denom <= lowDenom { return false } @@ -255,7 +265,7 @@ func (coins Coins) SafeMinus(coinsB Coins) (Coins, bool) { return diff, !diff.IsNotNegative() } -// IsAllGT returns true iff for every denom in coins, the denom is present at a +// 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) @@ -324,6 +334,9 @@ func (coins Coins) Empty() bool { // Returns the amount of a denom from coins func (coins Coins) AmountOf(denom string) Int { + if strings.ToLower(denom) != denom { + panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom)) + } switch len(coins) { case 0: return ZeroInt() @@ -377,7 +390,7 @@ func (coins Coins) IsNotNegative() bool { } for _, coin := range coins { - if !coin.IsNotNegative() { + if coin.IsNegative() { return false } } @@ -417,6 +430,12 @@ func removeZeroCoins(coins Coins) Coins { return coins[:i] } +func copyCoins(coins Coins) Coins { + copyCoins := make(Coins, len(coins)) + copy(copyCoins, coins) + return copyCoins +} + //----------------------------------------------------------------------------- // Sort interface @@ -438,10 +457,12 @@ func (coins Coins) Sort() Coins { var ( // Denominations can be 3 ~ 16 characters long. - reDnm = `[[:alpha:]][[:alnum:]]{2,15}` - reAmt = `[[:digit:]]+` - reSpc = `[[:space:]]*` - reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm)) + 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)) ) // ParseCoin parses a cli input for one coin type, returning errors if invalid. @@ -461,7 +482,11 @@ func ParseCoin(coinStr string) (coin Coin, err error) { return Coin{}, fmt.Errorf("failed to parse coin amount: %s", amountStr) } - return Coin{denomStr, amount}, nil + if denomStr != strings.ToLower(denomStr) { + return Coin{}, fmt.Errorf("denom cannot contain upper case characters: %s", denomStr) + } + + return NewCoin(denomStr, amount), nil } // ParseCoins will parse out a list of coins separated by commas. diff --git a/types/coin_test.go b/types/coin_test.go index 851d15515589..3f69ad2ccff4 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -11,10 +11,12 @@ import ( // Coin tests func TestCoin(t *testing.T) { - require.Panics(t, func() { NewInt64Coin("A", -1) }) - require.Panics(t, func() { NewCoin("A", NewInt(-1)) }) - require.Equal(t, NewInt(5), NewInt64Coin("A", 5).Amount) - require.Equal(t, NewInt(5), NewCoin("A", NewInt(5)).Amount) + require.Panics(t, func() { NewInt64Coin("a", -1) }) + require.Panics(t, func() { NewCoin("a", NewInt(-1)) }) + require.Panics(t, func() { NewInt64Coin("Atom", 10) }) + require.Panics(t, func() { NewCoin("Atom", NewInt(10)) }) + require.Equal(t, NewInt(5), NewInt64Coin("a", 5).Amount) + require.Equal(t, NewInt(5), NewCoin("a", NewInt(5)).Amount) } func TestSameDenomAsCoin(t *testing.T) { @@ -23,8 +25,7 @@ func TestSameDenomAsCoin(t *testing.T) { inputTwo Coin expected bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, - {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), true}, } @@ -41,8 +42,7 @@ func TestIsEqualCoin(t *testing.T) { inputTwo Coin expected bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, - {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), false}, } @@ -60,9 +60,9 @@ func TestPlusCoin(t *testing.T) { expected Coin shouldPanic bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("A", 1), NewInt64Coin("A", 2), false}, - {NewInt64Coin("A", 1), NewInt64Coin("A", 0), NewInt64Coin("A", 1), false}, - {NewInt64Coin("A", 1), NewInt64Coin("B", 1), NewInt64Coin("A", 1), true}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), NewInt64Coin("a", 2), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 0), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), NewInt64Coin("a", 1), true}, } for tcIndex, tc := range cases { @@ -82,11 +82,11 @@ func TestMinusCoin(t *testing.T) { expected Coin shouldPanic bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("B", 1), NewInt64Coin("A", 1), true}, - {NewInt64Coin("A", 10), NewInt64Coin("A", 1), NewInt64Coin("A", 9), false}, - {NewInt64Coin("A", 5), NewInt64Coin("A", 3), NewInt64Coin("A", 2), false}, - {NewInt64Coin("A", 5), NewInt64Coin("A", 0), NewInt64Coin("A", 5), false}, - {NewInt64Coin("A", 1), NewInt64Coin("A", 5), Coin{}, true}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), NewInt64Coin("a", 1), true}, + {NewInt64Coin("a", 10), NewInt64Coin("a", 1), NewInt64Coin("a", 9), false}, + {NewInt64Coin("a", 5), NewInt64Coin("a", 3), NewInt64Coin("a", 2), false}, + {NewInt64Coin("a", 5), NewInt64Coin("a", 0), NewInt64Coin("a", 5), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 5), Coin{}, true}, } for tcIndex, tc := range cases { @@ -102,7 +102,7 @@ func TestMinusCoin(t *testing.T) { inputOne Coin inputTwo Coin expected int64 - }{NewInt64Coin("A", 1), NewInt64Coin("A", 1), 0} + }{NewInt64Coin("a", 1), NewInt64Coin("a", 1), 0} res := tc.inputOne.Minus(tc.inputTwo) require.Equal(t, tc.expected, res.Amount.Int64()) } @@ -113,9 +113,9 @@ func TestIsGTECoin(t *testing.T) { inputTwo Coin expected bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, - {NewInt64Coin("A", 2), NewInt64Coin("A", 1), true}, - {NewInt64Coin("A", 1), NewInt64Coin("B", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, + {NewInt64Coin("a", 2), NewInt64Coin("a", 1), true}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, } for tcIndex, tc := range cases { @@ -130,8 +130,8 @@ func TestIsLTCoin(t *testing.T) { inputTwo Coin expected bool }{ - {NewInt64Coin("A", 1), NewInt64Coin("A", 1), false}, - {NewInt64Coin("A", 2), NewInt64Coin("A", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 2), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 0), NewInt64Coin("b", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, @@ -145,11 +145,11 @@ func TestIsLTCoin(t *testing.T) { } func TestCoinIsZero(t *testing.T) { - coin := NewInt64Coin("A", 0) + coin := NewInt64Coin("a", 0) res := coin.IsZero() require.True(t, res) - coin = NewInt64Coin("A", 1) + coin = NewInt64Coin("a", 1) res = coin.IsZero() require.False(t, res) } @@ -163,10 +163,10 @@ func TestIsZeroCoins(t *testing.T) { expected bool }{ {Coins{}, true}, - {Coins{NewInt64Coin("A", 0)}, true}, - {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 0)}, true}, - {Coins{NewInt64Coin("A", 1)}, false}, - {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, false}, + {Coins{NewInt64Coin("a", 0)}, true}, + {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 0)}, true}, + {Coins{NewInt64Coin("a", 1)}, false}, + {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, false}, } for _, tc := range cases { @@ -182,12 +182,12 @@ func TestEqualCoins(t *testing.T) { expected bool }{ {Coins{}, Coins{}, true}, - {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 0)}, true}, - {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, true}, - {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("B", 0)}, false}, - {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 1)}, false}, - {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, false}, - {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, Coins{NewInt64Coin("B", 1), NewInt64Coin("A", 0)}, true}, + {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 0)}, true}, + {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, true}, + {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("b", 0)}, false}, + {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 1)}, false}, + {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, false}, + {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, Coins{NewInt64Coin("b", 1), NewInt64Coin("a", 0)}, true}, } for tcnum, tc := range cases { @@ -206,11 +206,11 @@ func TestPlusCoins(t *testing.T) { inputTwo Coins expected Coins }{ - {Coins{{"A", one}, {"B", one}}, Coins{{"A", one}, {"B", one}}, Coins{{"A", two}, {"B", two}}}, - {Coins{{"A", zero}, {"B", one}}, Coins{{"A", zero}, {"B", zero}}, Coins{{"B", one}}}, - {Coins{{"A", two}}, Coins{{"B", zero}}, Coins{{"A", two}}}, - {Coins{{"A", one}}, Coins{{"A", one}, {"B", two}}, Coins{{"A", two}, {"B", two}}}, - {Coins{{"A", zero}, {"B", zero}}, Coins{{"A", zero}, {"B", zero}}, Coins(nil)}, + {Coins{{"a", one}, {"b", one}}, Coins{{"a", one}, {"b", one}}, Coins{{"a", two}, {"b", two}}}, + {Coins{{"a", zero}, {"b", one}}, Coins{{"a", zero}, {"b", zero}}, Coins{{"b", one}}}, + {Coins{{"a", two}}, Coins{{"b", zero}}, Coins{{"a", two}}}, + {Coins{{"a", one}}, Coins{{"a", one}, {"b", two}}, Coins{{"a", two}, {"b", two}}}, + {Coins{{"a", zero}, {"b", zero}}, Coins{{"a", zero}, {"b", zero}}, Coins(nil)}, } for tcIndex, tc := range cases { @@ -231,11 +231,11 @@ func TestMinusCoins(t *testing.T) { expected Coins shouldPanic bool }{ - {Coins{{"A", two}}, Coins{{"A", one}, {"B", two}}, Coins{{"A", one}, {"B", two}}, true}, - {Coins{{"A", two}}, Coins{{"B", zero}}, Coins{{"A", two}}, false}, - {Coins{{"A", one}}, Coins{{"B", zero}}, Coins{{"A", one}}, false}, - {Coins{{"A", one}, {"B", one}}, Coins{{"A", one}}, Coins{{"B", one}}, false}, - {Coins{{"A", one}, {"B", one}}, Coins{{"A", two}}, Coins{}, true}, + {Coins{{"a", two}}, Coins{{"a", one}, {"b", two}}, Coins{{"a", one}, {"b", two}}, true}, + {Coins{{"a", two}}, Coins{{"b", zero}}, Coins{{"a", two}}, false}, + {Coins{{"a", one}}, Coins{{"b", zero}}, Coins{{"a", one}}, false}, + {Coins{{"a", one}, {"b", one}}, Coins{{"a", one}}, Coins{{"b", one}}, false}, + {Coins{{"a", one}, {"b", one}}, Coins{{"a", two}}, Coins{}, true}, } for i, tc := range testCases { @@ -260,31 +260,38 @@ func TestCoins(t *testing.T) { {"MineraL", NewInt(1)}, {"TREE", NewInt(1)}, } + mixedCase2 := Coins{ + {"gAs", NewInt(1)}, + {"mineral", NewInt(1)}, + } + mixedCase3 := Coins{ + {"gAs", NewInt(1)}, + } empty := Coins{ - {"GOLD", NewInt(0)}, + {"gold", NewInt(0)}, } null := Coins{} badSort1 := Coins{ - {"TREE", NewInt(1)}, - {"GAS", NewInt(1)}, - {"MINERAL", NewInt(1)}, + {"tree", NewInt(1)}, + {"gas", NewInt(1)}, + {"mineral", NewInt(1)}, } // both are after the first one, but the second and third are in the wrong order badSort2 := Coins{ - {"GAS", NewInt(1)}, - {"TREE", NewInt(1)}, - {"MINERAL", NewInt(1)}, + {"gas", NewInt(1)}, + {"tree", NewInt(1)}, + {"mineral", NewInt(1)}, } badAmt := Coins{ - {"GAS", NewInt(1)}, - {"TREE", NewInt(0)}, - {"MINERAL", NewInt(1)}, + {"gas", NewInt(1)}, + {"tree", NewInt(0)}, + {"mineral", NewInt(1)}, } dup := Coins{ - {"GAS", NewInt(1)}, - {"GAS", NewInt(1)}, - {"MINERAL", NewInt(1)}, + {"gas", NewInt(1)}, + {"gas", NewInt(1)}, + {"mineral", NewInt(1)}, } neg := Coins{ {"gas", NewInt(-1)}, @@ -293,6 +300,8 @@ func TestCoins(t *testing.T) { assert.True(t, good.IsValid(), "Coins are valid") assert.False(t, mixedCase1.IsValid(), "Coins denoms contain upper case characters") + assert.False(t, mixedCase2.IsValid(), "First Coins denoms contain upper case characters") + assert.False(t, mixedCase3.IsValid(), "Single denom in Coins contains upper case characters") assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good) assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null) assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty) @@ -310,11 +319,11 @@ func TestCoinsGT(t *testing.T) { two := NewInt(2) assert.False(t, Coins{}.IsAllGT(Coins{})) - assert.True(t, Coins{{"A", one}}.IsAllGT(Coins{})) - assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"A", one}})) - assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"B", one}})) - assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", two}})) + assert.True(t, Coins{{"a", one}}.IsAllGT(Coins{})) + assert.False(t, Coins{{"a", one}}.IsAllGT(Coins{{"a", one}})) + assert.False(t, Coins{{"a", one}}.IsAllGT(Coins{{"b", one}})) + assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllGT(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllGT(Coins{{"b", two}})) } func TestCoinsGTE(t *testing.T) { @@ -322,11 +331,11 @@ func TestCoinsGTE(t *testing.T) { two := NewInt(2) assert.True(t, Coins{}.IsAllGTE(Coins{})) - assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{})) - assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{{"A", one}})) - assert.False(t, Coins{{"A", one}}.IsAllGTE(Coins{{"B", one}})) - assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", two}})) + assert.True(t, Coins{{"a", one}}.IsAllGTE(Coins{})) + assert.True(t, Coins{{"a", one}}.IsAllGTE(Coins{{"a", one}})) + assert.False(t, Coins{{"a", one}}.IsAllGTE(Coins{{"b", one}})) + assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllGTE(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllGTE(Coins{{"b", two}})) } func TestCoinsLT(t *testing.T) { @@ -334,14 +343,14 @@ func TestCoinsLT(t *testing.T) { two := NewInt(2) assert.False(t, Coins{}.IsAllLT(Coins{})) - assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{})) - assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"A", one}})) - assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", two}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", one}})) - assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", two}})) - assert.True(t, Coins{}.IsAllLT(Coins{{"A", one}})) + assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{})) + assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{{"a", one}})) + assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"b", two}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"a", one}, {"b", one}})) + assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"a", one}, {"b", two}})) + assert.True(t, Coins{}.IsAllLT(Coins{{"a", one}})) } func TestCoinsLTE(t *testing.T) { @@ -349,14 +358,14 @@ func TestCoinsLTE(t *testing.T) { two := NewInt(2) assert.True(t, Coins{}.IsAllLTE(Coins{})) - assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{})) - assert.True(t, Coins{{"A", one}}.IsAllLTE(Coins{{"A", one}})) - assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", one}})) - assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", two}})) - assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", one}})) - assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", two}})) - assert.True(t, Coins{}.IsAllLTE(Coins{{"A", one}})) + assert.False(t, Coins{{"a", one}}.IsAllLTE(Coins{})) + assert.True(t, Coins{{"a", one}}.IsAllLTE(Coins{{"a", one}})) + assert.False(t, Coins{{"a", one}}.IsAllLTE(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"b", one}})) + assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"b", two}})) + assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"a", one}, {"b", one}})) + assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"a", one}, {"b", two}})) + assert.True(t, Coins{}.IsAllLTE(Coins{{"a", one}})) } func TestParse(t *testing.T) { @@ -393,32 +402,32 @@ func TestParse(t *testing.T) { func TestSortCoins(t *testing.T) { good := Coins{ - NewInt64Coin("GAS", 1), - NewInt64Coin("MINERAL", 1), - NewInt64Coin("TREE", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("mineral", 1), + NewInt64Coin("tree", 1), } empty := Coins{ - NewInt64Coin("GOLD", 0), + NewInt64Coin("gold", 0), } badSort1 := Coins{ - NewInt64Coin("TREE", 1), - NewInt64Coin("GAS", 1), - NewInt64Coin("MINERAL", 1), + NewInt64Coin("tree", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("mineral", 1), } badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order - NewInt64Coin("GAS", 1), - NewInt64Coin("TREE", 1), - NewInt64Coin("MINERAL", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("tree", 1), + NewInt64Coin("mineral", 1), } badAmt := Coins{ - NewInt64Coin("GAS", 1), - NewInt64Coin("TREE", 0), - NewInt64Coin("MINERAL", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("tree", 0), + NewInt64Coin("mineral", 1), } dup := Coins{ - NewInt64Coin("GAS", 1), - NewInt64Coin("GAS", 1), - NewInt64Coin("MINERAL", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("mineral", 1), } cases := []struct { @@ -449,16 +458,16 @@ func TestAmountOf(t *testing.T) { NewInt64Coin(" ", 0), } case3 := Coins{ - NewInt64Coin("GOLD", 0), + NewInt64Coin("gold", 0), } case4 := Coins{ - NewInt64Coin("GAS", 1), - NewInt64Coin("MINERAL", 1), - NewInt64Coin("TREE", 1), + NewInt64Coin("gas", 1), + NewInt64Coin("mineral", 1), + NewInt64Coin("tree", 1), } case5 := Coins{ - NewInt64Coin("MINERAL", 1), - NewInt64Coin("TREE", 1), + NewInt64Coin("mineral", 1), + NewInt64Coin("tree", 1), } case6 := Coins{ NewInt64Coin("", 6), @@ -467,7 +476,7 @@ func TestAmountOf(t *testing.T) { NewInt64Coin(" ", 7), } case8 := Coins{ - NewInt64Coin("GAS", 8), + NewInt64Coin("gas", 8), } cases := []struct { @@ -492,8 +501,10 @@ func TestAmountOf(t *testing.T) { for _, tc := range cases { assert.Equal(t, NewInt(tc.amountOf), tc.coins.AmountOf("")) assert.Equal(t, NewInt(tc.amountOfSpace), tc.coins.AmountOf(" ")) - assert.Equal(t, NewInt(tc.amountOfGAS), tc.coins.AmountOf("GAS")) - assert.Equal(t, NewInt(tc.amountOfMINERAL), tc.coins.AmountOf("MINERAL")) - assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("TREE")) + assert.Equal(t, NewInt(tc.amountOfGAS), tc.coins.AmountOf("gas")) + assert.Equal(t, NewInt(tc.amountOfMINERAL), tc.coins.AmountOf("mineral")) + assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("tree")) } + + assert.Panics(t, func() { cases[0].coins.AmountOf("Invalid") }) } diff --git a/types/context.go b/types/context.go index 71e1f5303e64..3b815ee20319 100644 --- a/types/context.go +++ b/types/context.go @@ -47,7 +47,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo c = c.WithLogger(logger) c = c.WithVoteInfos(nil) c = c.WithGasMeter(NewInfiniteGasMeter()) - c = c.WithMinimumFees(Coins{}) + c = c.WithMinGasPrices(DecCoins{}) c = c.WithConsensusParams(nil) return c } @@ -141,7 +141,7 @@ const ( contextKeyVoteInfos contextKeyGasMeter contextKeyBlockGasMeter - contextKeyMinimumFees + contextKeyMinGasPrices contextKeyConsensusParams ) @@ -169,7 +169,7 @@ func (c Context) BlockGasMeter() GasMeter { return c.Value(contextKeyBlockGasMet func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) } -func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) } +func (c Context) MinGasPrices() DecCoins { return c.Value(contextKeyMinGasPrices).(DecCoins) } func (c Context) ConsensusParams() *abci.ConsensusParams { return c.Value(contextKeyConsensusParams).(*abci.ConsensusParams) @@ -222,8 +222,8 @@ func (c Context) WithIsCheckTx(isCheckTx bool) Context { return c.withValue(contextKeyIsCheckTx, isCheckTx) } -func (c Context) WithMinimumFees(minFees Coins) Context { - return c.withValue(contextKeyMinimumFees, minFees) +func (c Context) WithMinGasPrices(gasPrices DecCoins) Context { + return c.withValue(contextKeyMinGasPrices, gasPrices) } func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { diff --git a/types/context_test.go b/types/context_test.go index 3ccea2a8a9db..7edaa6e20270 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -163,7 +163,7 @@ func TestContextWithCustom(t *testing.T) { logger := NewMockLogger() voteinfos := []abci.VoteInfo{{}} meter := types.NewGasMeter(10000) - minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} + minGasPrices := types.DecCoins{types.NewDecCoin("feetoken", 1)} ctx = types.NewContext(nil, header, ischeck, logger) require.Equal(t, header, ctx.BlockHeader()) @@ -174,7 +174,7 @@ func TestContextWithCustom(t *testing.T) { WithTxBytes(txbytes). WithVoteInfos(voteinfos). WithGasMeter(meter). - WithMinimumFees(minFees) + WithMinGasPrices(minGasPrices) require.Equal(t, height, ctx.BlockHeight()) require.Equal(t, chainid, ctx.ChainID()) require.Equal(t, ischeck, ctx.IsCheckTx()) @@ -182,5 +182,5 @@ func TestContextWithCustom(t *testing.T) { require.Equal(t, logger, ctx.Logger()) require.Equal(t, voteinfos, ctx.VoteInfos()) require.Equal(t, meter, ctx.GasMeter()) - require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feeCoin", 1)}) + require.Equal(t, minGasPrices, ctx.MinGasPrices()) } diff --git a/types/dec_coin.go b/types/dec_coin.go new file mode 100644 index 000000000000..cc29c921c61b --- /dev/null +++ b/types/dec_coin.go @@ -0,0 +1,397 @@ +package types + +import ( + "fmt" + "sort" + "strings" + + "github.com/pkg/errors" +) + +// ---------------------------------------------------------------------------- +// Decimal Coin + +// Coins which can have additional decimal points +type DecCoin struct { + Denom string `json:"denom"` + Amount Dec `json:"amount"` +} + +func NewDecCoin(denom string, amount int64) DecCoin { + if amount < 0 { + panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount)) + } + if strings.ToLower(denom) != denom { + panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom)) + } + + return DecCoin{ + Denom: denom, + Amount: NewDec(amount), + } +} + +func NewDecCoinFromDec(denom string, amount Dec) DecCoin { + if amount.LT(ZeroDec()) { + panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount)) + } + if strings.ToLower(denom) != denom { + panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom)) + } + + return DecCoin{ + Denom: denom, + Amount: amount, + } +} + +func NewDecCoinFromCoin(coin Coin) DecCoin { + if coin.Amount.LT(ZeroInt()) { + panic(fmt.Sprintf("negative decimal coin amount: %v\n", coin.Amount)) + } + if strings.ToLower(coin.Denom) != coin.Denom { + panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", coin.Denom)) + } + + return DecCoin{ + Denom: coin.Denom, + Amount: NewDecFromInt(coin.Amount), + } +} + +// Adds amounts of two coins with same denom +func (coin DecCoin) Plus(coinB DecCoin) DecCoin { + if coin.Denom != coinB.Denom { + panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) + } + return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)} +} + +// Subtracts amounts of two coins with same denom +func (coin DecCoin) Minus(coinB DecCoin) DecCoin { + if coin.Denom != coinB.Denom { + panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) + } + return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} +} + +// 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)) + return NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change} +} + +// IsPositive returns true if coin amount is positive. +// +// TODO: Remove once unsigned integers are used. +func (coin DecCoin) IsPositive() bool { + return coin.Amount.IsPositive() +} + +// String implements the Stringer interface for DecCoin. It returns a +// human-readable representation of a decimal coin. +func (coin DecCoin) String() string { + return fmt.Sprintf("%v%v", coin.Amount, coin.Denom) +} + +// ---------------------------------------------------------------------------- +// Decimal Coins + +// coins with decimal +type DecCoins []DecCoin + +func NewDecCoins(coins Coins) DecCoins { + dcs := make(DecCoins, len(coins)) + for i, coin := range coins { + dcs[i] = NewDecCoinFromCoin(coin) + } + return dcs +} + +// String implements the Stringer interface for DecCoins. It returns a +// human-readable representation of decimal coins. +func (coins DecCoins) String() string { + if len(coins) == 0 { + return "" + } + + out := "" + for _, coin := range coins { + out += fmt.Sprintf("%v,", coin.String()) + } + + return out[:len(out)-1] +} + +// return the coins with trunctated decimals, and return the change +func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) { + changeSum := DecCoins{} + out := make(Coins, len(coins)) + for i, coin := range coins { + truncated, change := coin.TruncateDecimal() + out[i] = truncated + changeSum = changeSum.Plus(DecCoins{change}) + } + return out, changeSum +} + +// Plus combines two sets of coins +// CONTRACT: Plus will never return Coins where one Coin has a 0 amount. +func (coins DecCoins) Plus(coinsB DecCoins) DecCoins { + sum := ([]DecCoin)(nil) + indexA, indexB := 0, 0 + lenA, lenB := len(coins), len(coinsB) + for { + if indexA == lenA { + if indexB == lenB { + return sum + } + return append(sum, coinsB[indexB:]...) + } else if indexB == lenB { + return append(sum, coins[indexA:]...) + } + coinA, coinB := coins[indexA], coinsB[indexB] + switch strings.Compare(coinA.Denom, coinB.Denom) { + case -1: + sum = append(sum, coinA) + indexA++ + case 0: + if coinA.Amount.Add(coinB.Amount).IsZero() { + // ignore 0 sum coin type + } else { + sum = append(sum, coinA.Plus(coinB)) + } + indexA++ + indexB++ + case 1: + sum = append(sum, coinB) + indexB++ + } + } +} + +// Negative returns a set of coins with all amount negative +func (coins DecCoins) Negative() DecCoins { + res := make([]DecCoin, 0, len(coins)) + for _, coin := range coins { + res = append(res, DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.Neg(), + }) + } + return res +} + +// Minus subtracts a set of coins from another (adds the inverse) +func (coins DecCoins) Minus(coinsB DecCoins) DecCoins { + return coins.Plus(coinsB.Negative()) +} + +// multiply all the coins by a decimal +func (coins DecCoins) MulDec(d Dec) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + product := DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.Mul(d), + } + res[i] = product + } + return res +} + +// multiply all the coins by a decimal, truncating +func (coins DecCoins) MulDecTruncate(d Dec) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + product := DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.MulTruncate(d), + } + res[i] = product + } + return res +} + +// divide all the coins by a decimal +func (coins DecCoins) QuoDec(d Dec) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + quotient := DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.Quo(d), + } + res[i] = quotient + } + return res +} + +// divide all the coins by a decimal, truncating +func (coins DecCoins) QuoDecTruncate(d Dec) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + quotient := DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.QuoTruncate(d), + } + res[i] = quotient + } + return res +} + +// returns the amount of a denom from deccoins +func (coins DecCoins) AmountOf(denom string) Dec { + switch len(coins) { + case 0: + return ZeroDec() + case 1: + coin := coins[0] + if coin.Denom == denom { + return coin.Amount + } + return ZeroDec() + default: + midIdx := len(coins) / 2 // binary search + coin := coins[midIdx] + if denom < coin.Denom { + return coins[:midIdx].AmountOf(denom) + } else if denom == coin.Denom { + return coin.Amount + } else { + return coins[midIdx+1:].AmountOf(denom) + } + } +} + +// has a negative DecCoin amount +func (coins DecCoins) HasNegative() bool { + for _, coin := range coins { + if coin.Amount.IsNegative() { + return true + } + } + return false +} + +// return whether all coins are zero +func (coins DecCoins) IsZero() bool { + for _, coin := range coins { + if !coin.Amount.IsZero() { + return false + } + } + return true +} + +// IsValid asserts the DecCoins are sorted, have positive amount, and Denom +// does not contain upper case characters. +func (coins DecCoins) IsValid() bool { + switch len(coins) { + case 0: + return true + + case 1: + if strings.ToLower(coins[0].Denom) != coins[0].Denom { + return false + } + return coins[0].IsPositive() + + default: + // check single coin case + if !(DecCoins{coins[0]}).IsValid() { + return false + } + + lowDenom := coins[0].Denom + for _, coin := range coins[1:] { + if strings.ToLower(coin.Denom) != coin.Denom { + return false + } + if coin.Denom <= lowDenom { + return false + } + if !coin.IsPositive() { + return false + } + + // we compare each coin against the last denom + lowDenom = coin.Denom + } + + return true + } +} + +//----------------------------------------------------------------------------- +// Sorting + +var _ sort.Interface = Coins{} + +//nolint +func (coins DecCoins) Len() int { return len(coins) } +func (coins DecCoins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom } +func (coins DecCoins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] } + +// Sort is a helper function to sort the set of decimal coins in-place. +func (coins DecCoins) Sort() DecCoins { + sort.Sort(coins) + return coins +} + +// ---------------------------------------------------------------------------- +// Parsing + +// ParseDecCoin parses a decimal coin from a string, returning an error if +// invalid. An empty string is considered invalid. +func ParseDecCoin(coinStr string) (coin DecCoin, err error) { + coinStr = strings.TrimSpace(coinStr) + + matches := reDecCoin.FindStringSubmatch(coinStr) + if matches == nil { + return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr) + } + + amountStr, denomStr := matches[1], matches[2] + + amount, err := NewDecFromStr(amountStr) + if err != nil { + 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) + } + + return NewDecCoinFromDec(denomStr, amount), nil +} + +// ParseDecCoins will parse out a list of decimal coins separated by commas. +// If nothing is provided, it returns nil DecCoins. Returned decimal coins are +// sorted. +func ParseDecCoins(coinsStr string) (coins DecCoins, err error) { + coinsStr = strings.TrimSpace(coinsStr) + if len(coinsStr) == 0 { + return nil, nil + } + + coinStrs := strings.Split(coinsStr, ",") + for _, coinStr := range coinStrs { + coin, err := ParseDecCoin(coinStr) + if err != nil { + return nil, err + } + + coins = append(coins, coin) + } + + // sort coins for determinism + coins.Sort() + + // validate coins before returning + if !coins.IsValid() { + return nil, fmt.Errorf("parsed decimal coins are invalid: %#v", coins) + } + + return coins, nil +} diff --git a/types/dec_coin_test.go b/types/dec_coin_test.go new file mode 100644 index 000000000000..b2502ced1de8 --- /dev/null +++ b/types/dec_coin_test.go @@ -0,0 +1,225 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDecCoin(t *testing.T) { + require.NotPanics(t, func() { + NewDecCoin("a", 5) + }) + require.NotPanics(t, func() { + NewDecCoin("a", 0) + }) + require.Panics(t, func() { + NewDecCoin("A", 5) + }) + require.Panics(t, func() { + NewDecCoin("a", -5) + }) +} + +func TestNewDecCoinFromDec(t *testing.T) { + require.NotPanics(t, func() { + NewDecCoinFromDec("a", NewDec(5)) + }) + require.NotPanics(t, func() { + NewDecCoinFromDec("a", ZeroDec()) + }) + require.Panics(t, func() { + NewDecCoinFromDec("A", NewDec(5)) + }) + require.Panics(t, func() { + NewDecCoinFromDec("a", NewDec(-5)) + }) +} + +func TestNewDecCoinFromCoin(t *testing.T) { + require.NotPanics(t, func() { + NewDecCoinFromCoin(Coin{"a", NewInt(5)}) + }) + require.NotPanics(t, func() { + NewDecCoinFromCoin(Coin{"a", NewInt(0)}) + }) + require.Panics(t, func() { + NewDecCoinFromCoin(Coin{"A", NewInt(5)}) + }) + require.Panics(t, func() { + NewDecCoinFromCoin(Coin{"a", NewInt(-5)}) + }) +} + +func TestDecCoinIsPositive(t *testing.T) { + dc := NewDecCoin("a", 5) + require.True(t, dc.IsPositive()) + + dc = NewDecCoin("a", 0) + require.False(t, dc.IsPositive()) +} + +func TestPlusDecCoin(t *testing.T) { + decCoinA1 := NewDecCoinFromDec("a", NewDecWithPrec(11, 1)) + decCoinA2 := NewDecCoinFromDec("a", NewDecWithPrec(22, 1)) + decCoinB1 := NewDecCoinFromDec("b", NewDecWithPrec(11, 1)) + + // regular add + res := decCoinA1.Plus(decCoinA1) + require.Equal(t, decCoinA2, res, "sum of coins is incorrect") + + // bad denom add + require.Panics(t, func() { + decCoinA1.Plus(decCoinB1) + }, "expected panic on sum of different denoms") +} + +func TestPlusDecCoins(t *testing.T) { + one := NewDec(1) + zero := NewDec(0) + two := NewDec(2) + + cases := []struct { + inputOne DecCoins + inputTwo DecCoins + expected DecCoins + }{ + {DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", two}, {"b", two}}}, + {DecCoins{{"a", zero}, {"b", one}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"b", one}}}, + {DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins(nil)}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Plus(tc.inputTwo) + require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) + } +} + +func TestSortDecCoins(t *testing.T) { + good := DecCoins{ + NewDecCoin("gas", 1), + NewDecCoin("mineral", 1), + NewDecCoin("tree", 1), + } + empty := DecCoins{ + NewDecCoin("gold", 0), + } + badSort1 := DecCoins{ + NewDecCoin("tree", 1), + NewDecCoin("gas", 1), + NewDecCoin("mineral", 1), + } + badSort2 := DecCoins{ // both are after the first one, but the second and third are in the wrong order + NewDecCoin("gas", 1), + NewDecCoin("tree", 1), + NewDecCoin("mineral", 1), + } + badAmt := DecCoins{ + NewDecCoin("gas", 1), + NewDecCoin("tree", 0), + NewDecCoin("mineral", 1), + } + dup := DecCoins{ + NewDecCoin("gas", 1), + NewDecCoin("gas", 1), + NewDecCoin("mineral", 1), + } + + cases := []struct { + coins DecCoins + before, after bool // valid before/after sort + }{ + {good, true, true}, + {empty, false, false}, + {badSort1, false, true}, + {badSort2, false, true}, + {badAmt, false, false}, + {dup, false, false}, + } + + for tcIndex, tc := range cases { + require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex) + tc.coins.Sort() + require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex) + } +} + +func TestDecCoinsIsValid(t *testing.T) { + testCases := []struct { + input DecCoins + expected bool + }{ + {DecCoins{}, true}, + {DecCoins{DecCoin{"a", NewDec(5)}}, true}, + {DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, true}, + {DecCoins{DecCoin{"a", NewDec(-5)}}, false}, + {DecCoins{DecCoin{"A", NewDec(5)}}, false}, + {DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"B", NewDec(100000)}}, false}, + {DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(-100000)}}, false}, + {DecCoins{DecCoin{"a", NewDec(-5)}, DecCoin{"b", NewDec(100000)}}, false}, + {DecCoins{DecCoin{"A", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, false}, + } + + for i, tc := range testCases { + res := tc.input.IsValid() + require.Equal(t, tc.expected, res, "unexpected result for test case #%d, input: %v", i, tc.input) + } +} + +func TestParseDecCoins(t *testing.T) { + testCases := []struct { + input string + expectedResult DecCoins + expectedErr bool + }{ + {"", nil, false}, + {"4stake", nil, true}, + {"5.5atom,4stake", nil, true}, + {"0.0stake", nil, true}, + {"0.004STAKE", nil, true}, + { + "0.004stake", + DecCoins{NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision))}, + false, + }, + { + "5.04atom,0.004stake", + DecCoins{ + NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)), + NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)), + }, + false, + }, + } + + for i, tc := range testCases { + res, err := ParseDecCoins(tc.input) + if tc.expectedErr { + require.Error(t, err, "expected error for test case #%d, input: %v", i, tc.input) + } else { + require.NoError(t, err, "unexpected error for test case #%d, input: %v", i, tc.input) + require.Equal(t, tc.expectedResult, res, "unexpected result for test case #%d, input: %v", i, tc.input) + } + } +} + +func TestDecCoinsString(t *testing.T) { + testCases := []struct { + input DecCoins + expected string + }{ + {DecCoins{}, ""}, + { + DecCoins{ + NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)), + NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)), + }, + "5.040000000000000000atom,0.004000000000000000stake", + }, + } + + for i, tc := range testCases { + out := tc.input.String() + require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input) + } +} diff --git a/types/decimal.go b/types/decimal.go index 5e0f8d267027..976ddd560e79 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -17,11 +17,11 @@ type Dec struct { // number of decimal places const ( - Precision = 10 + Precision = 18 // bytes required to represent the above precision - // ceil(log2(9999999999)) - DecimalPrecisionBits = 34 + // Ceiling[Log2[999 999 999 999 999 999]] + DecimalPrecisionBits = 60 ) var ( @@ -142,12 +142,14 @@ func NewDecFromStr(str string) (d Dec, err Error) { strs := strings.Split(str, ".") lenDecs := 0 combinedStr := strs[0] - if len(strs) == 2 { + + if len(strs) == 2 { // has a decimal place lenDecs = len(strs[1]) if lenDecs == 0 || len(combinedStr) == 0 { return d, ErrUnknownRequest("bad decimal length") } combinedStr = combinedStr + strs[1] + } else if len(strs) > 2 { return d, ErrUnknownRequest("too many periods to be a decimal string") } @@ -162,7 +164,7 @@ func NewDecFromStr(str string) (d Dec, err Error) { zeros := fmt.Sprintf(`%0`+strconv.Itoa(zerosToAdd)+`s`, "") combinedStr = combinedStr + zeros - combined, ok := new(big.Int).SetString(combinedStr, 10) + combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10 if !ok { return d, ErrUnknownRequest(fmt.Sprintf("bad string to integer conversion, combinedStr: %v", combinedStr)) } @@ -226,6 +228,17 @@ func (d Dec) Mul(d2 Dec) Dec { return Dec{chopped} } +// multiplication truncate +func (d Dec) MulTruncate(d2 Dec) Dec { + mul := new(big.Int).Mul(d.Int, d2.Int) + chopped := chopPrecisionAndTruncate(mul) + + if chopped.BitLen() > 255+DecimalPrecisionBits { + panic("Int overflow") + } + return Dec{chopped} +} + // multiplication func (d Dec) MulInt(i Int) Dec { mul := new(big.Int).Mul(d.Int, i.i) @@ -252,6 +265,22 @@ func (d Dec) Quo(d2 Dec) Dec { return Dec{chopped} } +// quotient truncate +func (d Dec) QuoTruncate(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 := chopPrecisionAndTruncate(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) @@ -276,36 +305,48 @@ func (d Dec) String() string { if d.IsNegative() { d = d.Neg() } - bz, err := d.Int.MarshalText() + + bzInt, err := d.Int.MarshalText() if err != nil { return "" } - var bzWDec []byte - inputSize := len(bz) + inputSize := len(bzInt) + + var bzStr []byte + // TODO: Remove trailing zeros // case 1, purely decimal - if inputSize <= 10 { - bzWDec = make([]byte, 12) + if inputSize <= Precision { + + bzStr = make([]byte, Precision+2) + // 0. prefix - bzWDec[0] = byte('0') - bzWDec[1] = byte('.') + bzStr[0] = byte('0') + bzStr[1] = byte('.') + // set relevant digits to 0 - for i := 0; i < 10-inputSize; i++ { - bzWDec[i+2] = byte('0') + for i := 0; i < Precision-inputSize; i++ { + bzStr[i+2] = byte('0') } - // set last few digits - copy(bzWDec[2+(10-inputSize):], bz) + + // set final digits + copy(bzStr[2+(Precision-inputSize):], bzInt) + } else { + // inputSize + 1 to account for the decimal point that is being added - bzWDec = make([]byte, inputSize+1) - copy(bzWDec, bz[:inputSize-10]) - bzWDec[inputSize-10] = byte('.') - copy(bzWDec[inputSize-9:], bz[inputSize-10:]) + bzStr = make([]byte, inputSize+1) + decPointPlace := inputSize - Precision + + copy(bzStr, bzInt[:decPointPlace]) // pre-decimal digits + bzStr[decPointPlace] = byte('.') // decimal point + copy(bzStr[decPointPlace+1:], bzInt[decPointPlace:]) // post-decimal digits } + if isNeg { - return "-" + string(bzWDec) + return "-" + string(bzStr) } - return string(bzWDec) + return string(bzStr) } // ____ @@ -333,7 +374,7 @@ func chopPrecisionAndRound(d *big.Int) *big.Int { return d } - // get the trucated quotient and remainder + // get the truncated quotient and remainder quo, rem := d, big.NewInt(0) quo, rem = quo.QuoRem(d, precisionReuse, rem) @@ -405,6 +446,26 @@ func (d Dec) TruncateDec() Dec { return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int)) } +// Ceil returns the smallest interger value (as a decimal) that is greater than +// or equal to the given decimal. +func (d Dec) Ceil() Dec { + tmp := new(big.Int).Set(d.Int) + + quo, rem := tmp, big.NewInt(0) + quo, rem = quo.QuoRem(tmp, precisionReuse, rem) + + // no need to round with a zero remainder regardless of sign + if rem.Cmp(zeroInt) == 0 { + return NewDecFromBigInt(quo) + } + + if rem.Sign() == -1 { + return NewDecFromBigInt(quo) + } + + return NewDecFromBigInt(quo.Add(quo, oneInt)) +} + //___________________________________________________________________________________ // reuse nil values diff --git a/types/decimal_test.go b/types/decimal_test.go index fa6442f3a5fa..2193d841f0d6 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -22,7 +22,7 @@ func mustNewDecFromStr(t *testing.T, str string) (d Dec) { func TestPrecisionMultiplier(t *testing.T) { res := precisionMultiplier(5) - exp := big.NewInt(100000) + exp := big.NewInt(10000000000000) require.Equal(t, 0, res.Cmp(exp), "equality was incorrect, res %v, exp %v", res, exp) } @@ -76,6 +76,25 @@ func TestNewDecFromStr(t *testing.T) { } } +func TestDecString(t *testing.T) { + tests := []struct { + d Dec + want string + }{ + {NewDec(0), "0.000000000000000000"}, + {NewDec(1), "1.000000000000000000"}, + {NewDec(10), "10.000000000000000000"}, + {NewDec(12340), "12340.000000000000000000"}, + {NewDecWithPrec(12340, 4), "1.234000000000000000"}, + {NewDecWithPrec(12340, 5), "0.123400000000000000"}, + {NewDecWithPrec(12340, 8), "0.000123400000000000"}, + {NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"}, + } + for tcIndex, tc := range tests { + assert.Equal(t, tc.want, tc.d.String(), "bad String(), index: %v", tcIndex) + } +} + func TestEqualities(t *testing.T) { tests := []struct { d1, d2 Dec @@ -140,7 +159,7 @@ func TestArithmetic(t *testing.T) { d1, d2 Dec expMul, expDiv, expAdd, expSub Dec }{ - // d1 d2 MUL DIV ADD SUB + // 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)}, @@ -152,14 +171,14 @@ func TestArithmetic(t *testing.T) { {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(3), NewDec(7), NewDec(21), NewDecWithPrec(4285714286, 10), NewDec(10), NewDec(-4)}, + {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)}, {NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), NewDec(1), NewDec(3), NewDec(0)}, {NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), - NewDecWithPrec(10009009009, 9), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, + MustNewDecFromStr("10.009009009009009009"), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, } for tcIndex, tc := range tests { @@ -245,14 +264,14 @@ func TestDecMarshalJSON(t *testing.T) { want string wantErr bool // if wantErr = false, will also attempt unmarshaling }{ - {"zero", decimal(0), "\"0.0000000000\"", false}, - {"one", decimal(1), "\"0.0000000001\"", false}, - {"ten", decimal(10), "\"0.0000000010\"", false}, - {"12340", decimal(12340), "\"0.0000012340\"", false}, - {"zeroInt", NewDec(0), "\"0.0000000000\"", false}, - {"oneInt", NewDec(1), "\"1.0000000000\"", false}, - {"tenInt", NewDec(10), "\"10.0000000000\"", false}, - {"12340Int", NewDec(12340), "\"12340.0000000000\"", false}, + {"zero", decimal(0), "\"0.000000000000000000\"", false}, + {"one", decimal(1), "\"0.000000000000000001\"", false}, + {"ten", decimal(10), "\"0.000000000000000010\"", false}, + {"12340", decimal(12340), "\"0.000000000000012340\"", false}, + {"zeroInt", NewDec(0), "\"0.000000000000000000\"", false}, + {"oneInt", NewDec(1), "\"1.000000000000000000\"", false}, + {"tenInt", NewDec(10), "\"10.000000000000000000\"", false}, + {"12340Int", NewDec(12340), "\"12340.000000000000000000\"", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -344,7 +363,7 @@ func TestStringOverflow(t *testing.T) { require.NoError(t, err) dec3 := dec1.Add(dec2) require.Equal(t, - "19844653375691057515930281852116324640.0000000000", + "19844653375691057515930281852116324640.000000000000000000", dec3.String(), ) } @@ -365,3 +384,24 @@ func TestDecMulInt(t *testing.T) { require.Equal(t, tc.want, got, "Incorrect result on test case %d", i) } } + +func TestDecCeil(t *testing.T) { + testCases := []struct { + input Dec + expected Dec + }{ + {NewDecWithPrec(1000000000000000, Precision), NewDec(1)}, // 0.001 => 1.0 + {NewDecWithPrec(-1000000000000000, Precision), ZeroDec()}, // -0.001 => 0.0 + {ZeroDec(), ZeroDec()}, // 0.0 => 0.0 + {NewDecWithPrec(900000000000000000, Precision), NewDec(1)}, // 0.9 => 1.0 + {NewDecWithPrec(4001000000000000000, Precision), NewDec(5)}, // 4.001 => 5.0 + {NewDecWithPrec(-4001000000000000000, Precision), NewDec(-4)}, // -4.001 => -4.0 + {NewDecWithPrec(4700000000000000000, Precision), NewDec(5)}, // 4.7 => 5.0 + {NewDecWithPrec(-4700000000000000000, Precision), NewDec(-4)}, // -4.7 => -4.0 + } + + for i, tc := range testCases { + res := tc.input.Ceil() + require.Equal(t, tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) + } +} diff --git a/types/errors.go b/types/errors.go index a54c9d71c528..a400fec6f95b 100644 --- a/types/errors.go +++ b/types/errors.go @@ -45,6 +45,7 @@ const ( CodeInsufficientFee CodeType = 14 CodeTooManySignatures CodeType = 15 CodeGasOverflow CodeType = 16 + CodeNoSignatures CodeType = 17 // CodespaceRoot is a codespace for error codes in this file only. // Notice that 0 is an "unset" codespace, which can be overridden with @@ -90,6 +91,8 @@ func CodeToDefaultMsg(code CodeType) string { return "insufficient fee" case CodeTooManySignatures: return "maximum numer of signatures exceeded" + case CodeNoSignatures: + return "no signatures supplied" default: return unknownCodeMsg(code) } @@ -145,6 +148,9 @@ func ErrInsufficientFee(msg string) Error { func ErrTooManySignatures(msg string) Error { return newErrorWithRootCodespace(CodeTooManySignatures, msg) } +func ErrNoSignatures(msg string) Error { + return newErrorWithRootCodespace(CodeNoSignatures, msg) +} func ErrGasOverflow(msg string) Error { return newErrorWithRootCodespace(CodeGasOverflow, msg) } diff --git a/types/int.go b/types/int.go index 417a1763d431..dec7e81c8278 100644 --- a/types/int.go +++ b/types/int.go @@ -37,6 +37,15 @@ func min(i *big.Int, i2 *big.Int) *big.Int { if i.Cmp(i2) == 1 { return new(big.Int).Set(i2) } + + return new(big.Int).Set(i) +} + +func max(i *big.Int, i2 *big.Int) *big.Int { + if i.Cmp(i2) == -1 { + return new(big.Int).Set(i2) + } + return new(big.Int).Set(i) } @@ -153,6 +162,16 @@ func (i Int) IsZero() bool { return i.i.Sign() == 0 } +// IsNegative returns true if Int is negative +func (i Int) IsNegative() bool { + return i.i.Sign() == -1 +} + +// IsPositive returns true if Int is positive +func (i Int) IsPositive() bool { + return i.i.Sign() == 1 +} + // Sign returns sign of Int func (i Int) Sign() int { return i.i.Sign() @@ -254,11 +273,16 @@ func (i Int) Neg() (res Int) { return Int{neg(i.i)} } -// Return the minimum of the ints +// return the minimum of the ints func MinInt(i1, i2 Int) Int { return Int{min(i1.BigInt(), i2.BigInt())} } +// MaxInt returns the maximum between two integers. +func MaxInt(i, i2 Int) Int { + return Int{max(i.BigInt(), i2.BigInt())} +} + // Human readable string func (i Int) String() string { return i.i.String() @@ -496,6 +520,11 @@ 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() diff --git a/types/int_test.go b/types/int_test.go index 9e189858c702..18d968e903e8 100644 --- a/types/int_test.go +++ b/types/int_test.go @@ -145,6 +145,13 @@ func minint(i1, i2 int64) int64 { return i2 } +func maxint(i1, i2 int64) int64 { + if i1 > i2 { + return i1 + } + return i2 +} + func TestArithInt(t *testing.T) { for d := 0; d < 1000; d++ { n1 := int64(rand.Int31()) @@ -165,6 +172,7 @@ func TestArithInt(t *testing.T) { {i1.MulRaw(n2), n1 * n2}, {i1.DivRaw(n2), n1 / n2}, {MinInt(i1, i2), minint(n1, n2)}, + {MaxInt(i1, i2), maxint(n1, n2)}, {i1.Neg(), -n1}, } @@ -226,6 +234,13 @@ func minuint(i1, i2 uint64) uint64 { return i2 } +func maxuint(i1, i2 uint64) uint64 { + if i1 > i2 { + return i1 + } + return i2 +} + func TestArithUint(t *testing.T) { for d := 0; d < 1000; d++ { n1 := uint64(rand.Uint32()) @@ -244,6 +259,7 @@ func TestArithUint(t *testing.T) { {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 { diff --git a/types/stake.go b/types/stake.go index 38f1c0c9baab..25c02d8489d4 100644 --- a/types/stake.go +++ b/types/stake.go @@ -42,18 +42,19 @@ type Validator interface { GetOperator() ValAddress // operator address to receive/return validators coins GetConsPubKey() crypto.PubKey // validation consensus pubkey GetConsAddr() ConsAddress // validation consensus address - GetPower() Dec // validation power - GetTokens() Dec // validation tokens + GetPower() Int // validation power + GetTokens() Int // validation tokens GetCommission() Dec // validator commission rate - GetDelegatorShares() Dec // Total out standing delegator shares + GetDelegatorShares() Dec // total outstanding delegator shares GetBondHeight() int64 // height in which the validator became active + GetDelegatorShareExRate() Dec // tokens per delegator share exchange rate } // validator which fulfills abci validator interface for use in Tendermint func ABCIValidator(v Validator) abci.Validator { return abci.Validator{ Address: v.GetConsPubKey().Address(), - Power: v.GetPower().RoundInt64(), + Power: v.GetPower().Int64(), } } @@ -73,7 +74,7 @@ type ValidatorSet interface { Validator(Context, ValAddress) Validator // get a particular validator by operator address ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address - TotalPower(Context) Dec // total power of the validator set + TotalPower(Context) Int // total power of the validator set // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Slash(Context, ConsAddress, int64, int64, Dec) @@ -115,15 +116,17 @@ type DelegationSet interface { // event hooks for staking validator object type StakingHooks interface { - OnValidatorCreated(ctx Context, valAddr ValAddress) // Must be called when a validator is created - OnValidatorModified(ctx Context, valAddr ValAddress) // Must be called when a validator's state changes - OnValidatorRemoved(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is deleted - - OnValidatorBonded(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is bonded - OnValidatorBeginUnbonding(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator begins unbonding - OnValidatorPowerDidChange(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Called at EndBlock when a validator's power did change - - OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created - OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified - OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed + AfterValidatorCreated(ctx Context, valAddr ValAddress) // Must be called when a validator is created + BeforeValidatorModified(ctx Context, valAddr ValAddress) // Must be called when a validator's state changes + AfterValidatorRemoved(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is deleted + + AfterValidatorBonded(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is bonded + AfterValidatorBeginUnbonding(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator begins unbonding + AfterValidatorPowerDidChange(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Called at EndBlock when a validator's power did change + + BeforeDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created + BeforeDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified + BeforeDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed + AfterDelegationModified(ctx Context, delAddr AccAddress, valAddr ValAddress) + BeforeValidatorSlashed(ctx Context, valAddr ValAddress, fraction Dec) } diff --git a/types/store.go b/types/store.go index 671a881c28be..3118fba8f166 100644 --- a/types/store.go +++ b/types/store.go @@ -12,19 +12,29 @@ import ( // NOTE: These are implemented in cosmos-sdk/store. -// PruningStrategy specfies how old states will be deleted over time -type PruningStrategy uint8 +// PruningStrategy specifies how old states will be deleted over time where +// keepRecent can be used with keepEvery to create a pruning "strategy". +type PruningOptions struct { + keepRecent int64 + keepEvery int64 +} -const ( - // PruneSyncable means only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) - PruneSyncable PruningStrategy = iota +func NewPruningOptions(keepRecent, keepEvery int64) PruningOptions { + return PruningOptions{ + keepRecent: keepRecent, + keepEvery: keepEvery, + } +} - // PruneEverything means all saved states will be deleted, storing only the current state - PruneEverything PruningStrategy = iota +// How much recent state will be kept. Older state will be deleted. +func (po PruningOptions) KeepRecent() int64 { + return po.keepRecent +} - // PruneNothing means all historic states will be saved, nothing will be deleted - PruneNothing PruningStrategy = iota -) +// Keeps every N stated, deleting others. +func (po PruningOptions) KeepEvery() int64 { + return po.keepEvery +} type Store interface { //nolint GetStoreType() StoreType @@ -35,7 +45,7 @@ type Store interface { //nolint type Committer interface { Commit() CommitID LastCommitID() CommitID - SetPruning(PruningStrategy) + SetPruning(PruningOptions) } // Stores of MultiStore must implement CommitStore. diff --git a/types/utils.go b/types/utils.go index 91cdcf9b0384..f5b244d74591 100644 --- a/types/utils.go +++ b/types/utils.go @@ -4,9 +4,6 @@ import ( "encoding/binary" "encoding/json" "time" - - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - tmtypes "github.com/tendermint/tendermint/types" ) // SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces @@ -61,22 +58,3 @@ func ParseTimeBytes(bz []byte) (time.Time, error) { } return t.UTC().Round(0), nil } - -// DefaultChainID returns the chain ID from the genesis file if present. An -// error is returned if the file cannot be read or parsed. -// -// TODO: This should be removed and the chainID should always be provided by -// the end user. -func DefaultChainID() (string, error) { - cfg, err := tcmd.ParseConfig() - if err != nil { - return "", err - } - - doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) - if err != nil { - return "", err - } - - return doc.ChainID, nil -} diff --git a/version/command.go b/version/command.go index 8f47dba8f5fc..49cf1e021d2c 100644 --- a/version/command.go +++ b/version/command.go @@ -2,6 +2,7 @@ package version import ( "fmt" + "runtime" "github.com/spf13/cobra" ) @@ -22,5 +23,9 @@ func GetVersion() string { // CMD func printVersion(cmd *cobra.Command, args []string) { - fmt.Println(GetVersion()) + fmt.Println("cosmos-sdk:", GetVersion()) + fmt.Println("git commit:", Commit) + fmt.Println("vendor hash:", VendorDirHash) + fmt.Printf("go version %s %s/%s\n", + runtime.Version(), runtime.GOOS, runtime.GOARCH) } diff --git a/version/version.go b/version/version.go index 407797eeb017..031976ecd367 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,9 @@ //nolint package version -// GitCommit set by build flags -var Version = "" +// Variables set by build flags +var ( + Commit = "" + Version = "" + VendorDirHash = "" +) diff --git a/x/auth/account.go b/x/auth/account.go index f647601cabcf..e67e081aea6c 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -2,6 +2,8 @@ package auth import ( "errors" + "fmt" + "time" "github.com/tendermint/tendermint/crypto" @@ -30,19 +32,45 @@ type Account interface { GetCoins() sdk.Coins SetCoins(sdk.Coins) error + + // Calculates the amount of coins that can be sent to other accounts given + // the current time. + SpendableCoins(blockTime time.Time) sdk.Coins + + // Ensure that account implements stringer + String() string +} + +// VestingAccount defines an account type that vests coins via a vesting schedule. +type VestingAccount interface { + Account + + // Delegation and undelegation accounting that returns the resulting base + // coins amount. + TrackDelegation(blockTime time.Time, amount sdk.Coins) + TrackUndelegation(amount sdk.Coins) + + GetVestedCoins(blockTime time.Time) sdk.Coins + GetVestingCoins(blockTime time.Time) sdk.Coins + + GetStartTime() int64 + GetEndTime() int64 + + GetOriginalVesting() sdk.Coins + GetDelegatedFree() sdk.Coins + GetDelegatedVesting() sdk.Coins } // AccountDecoder unmarshals account bytes type AccountDecoder func(accountBytes []byte) (Account, error) -//----------------------------------------------------------- +//----------------------------------------------------------------------------- // BaseAccount var _ Account = (*BaseAccount)(nil) // BaseAccount - a base account structure. // This can be extended by embedding within in your AppAccount. -// There are examples of this in: examples/basecoin/types/account.go. // However one doesn't have to use BaseAccount as long as your struct // implements Account. type BaseAccount struct { @@ -53,6 +81,16 @@ type BaseAccount struct { Sequence uint64 `json:"sequence"` } +// String implements fmt.Stringer +func (acc BaseAccount) String() string { + return fmt.Sprintf(`Account %s: + Coins: %s + PubKey: %s + AccountNumber: %d + Sequence: %d`, acc.Address, acc.Coins, + acc.PubKey.Address(), acc.AccountNumber, acc.Sequence) +} + // Prototype function for BaseAccount func ProtoBaseAccount() Account { return &BaseAccount{} @@ -122,12 +160,386 @@ func (acc *BaseAccount) SetSequence(seq uint64) error { return nil } -//---------------------------------------- -// Wire +// SpendableCoins returns the total set of spendable coins. For a base account, +// this is simply the base coins. +func (acc *BaseAccount) SpendableCoins(_ time.Time) sdk.Coins { + return acc.GetCoins() +} + +//----------------------------------------------------------------------------- +// Base Vesting Account + +// BaseVestingAccount implements the VestingAccount interface. It contains all +// the necessary fields needed for any vesting account implementation. +type BaseVestingAccount struct { + *BaseAccount + + OriginalVesting sdk.Coins // coins in account upon initialization + DelegatedFree sdk.Coins // coins that are vested and delegated + DelegatedVesting sdk.Coins // coins that vesting and delegated + + EndTime int64 // when the coins become unlocked +} + +// String implements fmt.Stringer +func (bva BaseVestingAccount) String() string { + return fmt.Sprintf(`Vesting Account: + Address: %s + Coins: %s + AccountNumber: %d + Sequence: %d + OriginalVesting: %s + DelegatedFree: %s + DelegatedVesting: %s + EndTime: %d `, + bva.Address, bva.Coins, + bva.AccountNumber, bva.Sequence, + bva.OriginalVesting, bva.DelegatedFree, + bva.DelegatedVesting, bva.EndTime, + ) +} + +// spendableCoins returns all the spendable coins for a vesting account given a +// set of vesting coins. +// +// CONTRACT: The account's coins, delegated vesting coins, vestingCoins must be +// sorted. +func (bva BaseVestingAccount) spendableCoins(vestingCoins sdk.Coins) sdk.Coins { + var spendableCoins sdk.Coins + bc := bva.GetCoins() + + j, k := 0, 0 + for _, coin := range bc { + // zip/lineup all coins by their denomination to provide O(n) time + for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom { + j++ + } + for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom { + k++ + } + + baseAmt := coin.Amount + + vestingAmt := sdk.ZeroInt() + if len(vestingCoins) > 0 { + vestingAmt = vestingCoins[j].Amount + } + + delVestingAmt := sdk.ZeroInt() + if len(bva.DelegatedVesting) > 0 { + delVestingAmt = bva.DelegatedVesting[k].Amount + } + + // compute min((BC + DV) - V, BC) per the specification + min := sdk.MinInt(baseAmt.Add(delVestingAmt).Sub(vestingAmt), baseAmt) + spendableCoin := sdk.NewCoin(coin.Denom, min) + + if !spendableCoin.IsZero() { + spendableCoins = spendableCoins.Plus(sdk.Coins{spendableCoin}) + } + } + + return spendableCoins +} + +// trackDelegation tracks a delegation amount for any given vesting account type +// given the amount of coins currently vesting. It returns the resulting base +// coins. +// +// CONTRACT: The account's coins, delegation coins, vesting coins, and delegated +// vesting coins must be sorted. +func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) { + bc := bva.GetCoins() + + i, j, k := 0, 0, 0 + for _, coin := range amount { + // zip/lineup all coins by their denomination to provide O(n) time + for i < len(bc) && bc[i].Denom != coin.Denom { + i++ + } + for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom { + j++ + } + for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom { + k++ + } + + baseAmt := sdk.ZeroInt() + if len(bc) > 0 { + baseAmt = bc[i].Amount + } + + vestingAmt := sdk.ZeroInt() + if len(vestingCoins) > 0 { + vestingAmt = vestingCoins[j].Amount + } + + delVestingAmt := sdk.ZeroInt() + if len(bva.DelegatedVesting) > 0 { + delVestingAmt = bva.DelegatedVesting[k].Amount + } + + // Panic if the delegation amount is zero or if the base coins does not + // exceed the desired delegation amount. + if coin.Amount.IsZero() || baseAmt.LT(coin.Amount) { + panic("delegation attempt with zero coins or insufficient funds") + } + + // compute x and y per the specification, where: + // X := min(max(V - DV, 0), D) + // Y := D - X + x := sdk.MinInt(sdk.MaxInt(vestingAmt.Sub(delVestingAmt), sdk.ZeroInt()), coin.Amount) + y := coin.Amount.Sub(x) + + if !x.IsZero() { + xCoin := sdk.NewCoin(coin.Denom, x) + bva.DelegatedVesting = bva.DelegatedVesting.Plus(sdk.Coins{xCoin}) + } + + if !y.IsZero() { + yCoin := sdk.NewCoin(coin.Denom, y) + bva.DelegatedFree = bva.DelegatedFree.Plus(sdk.Coins{yCoin}) + } + + bva.Coins = bva.Coins.Minus(sdk.Coins{coin}) + } +} + +// TrackUndelegation tracks an undelegation amount by setting the necessary +// values by which delegated vesting and delegated vesting need to decrease and +// by which amount the base coins need to increase. The resulting base coins are +// returned. +// +// CONTRACT: The account's coins and undelegation coins must be sorted. +func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) { + i := 0 + for _, coin := range amount { + // panic if the undelegation amount is zero + if coin.Amount.IsZero() { + panic("undelegation attempt with zero coins") + } + + for i < len(bva.DelegatedFree) && bva.DelegatedFree[i].Denom != coin.Denom { + i++ + } + + delegatedFree := sdk.ZeroInt() + if len(bva.DelegatedFree) > 0 { + delegatedFree = bva.DelegatedFree[i].Amount + } + + // compute x and y per the specification, where: + // X := min(DF, D) + // Y := D - X + x := sdk.MinInt(delegatedFree, coin.Amount) + y := coin.Amount.Sub(x) + + if !x.IsZero() { + xCoin := sdk.NewCoin(coin.Denom, x) + bva.DelegatedFree = bva.DelegatedFree.Minus(sdk.Coins{xCoin}) + } + + if !y.IsZero() { + yCoin := sdk.NewCoin(coin.Denom, y) + bva.DelegatedVesting = bva.DelegatedVesting.Minus(sdk.Coins{yCoin}) + } + + bva.Coins = bva.Coins.Plus(sdk.Coins{coin}) + } +} + +// GetOriginalVesting returns a vesting account's original vesting amount +func (bva BaseVestingAccount) GetOriginalVesting() sdk.Coins { + return bva.OriginalVesting +} + +// GetDelegatedFree returns a vesting account's delegation amount that is not +// vesting. +func (bva BaseVestingAccount) GetDelegatedFree() sdk.Coins { + return bva.DelegatedFree +} + +// GetDelegatedVesting returns a vesting account's delegation amount that is +// still vesting. +func (bva BaseVestingAccount) GetDelegatedVesting() sdk.Coins { + return bva.DelegatedVesting +} + +//----------------------------------------------------------------------------- +// Continuous Vesting Account + +var _ VestingAccount = (*ContinuousVestingAccount)(nil) + +// ContinuousVestingAccount implements the VestingAccount interface. It +// continuously vests by unlocking coins linearly with respect to time. +type ContinuousVestingAccount struct { + *BaseVestingAccount + + StartTime int64 // when the coins start to vest +} + +func NewContinuousVestingAccount( + baseAcc *BaseAccount, StartTime, EndTime int64, +) *ContinuousVestingAccount { + + baseVestingAcc := &BaseVestingAccount{ + BaseAccount: baseAcc, + OriginalVesting: baseAcc.Coins, + EndTime: EndTime, + } + + return &ContinuousVestingAccount{ + StartTime: StartTime, + BaseVestingAccount: baseVestingAcc, + } +} + +func (cva ContinuousVestingAccount) String() string { + return fmt.Sprintf(`Continuous Vesting Account: + Address: %s + Coins: %s + AccountNumber: %d + Sequence: %d + OriginalVesting: %s + DelegatedFree: %s + DelegatedVesting: %s + StartTime: %d + EndTime: %d `, + cva.Address, cva.Coins, + cva.AccountNumber, cva.Sequence, + cva.OriginalVesting, cva.DelegatedFree, + cva.DelegatedVesting, cva.StartTime, cva.EndTime, + ) +} + +// GetVestedCoins returns the total number of vested coins. If no coins are vested, +// nil is returned. +func (cva ContinuousVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins { + var vestedCoins sdk.Coins + + // We must handle the case where the start time for a vesting account has + // been set into the future or when the start of the chain is not exactly + // known. + if blockTime.Unix() <= cva.StartTime { + return vestedCoins + } else if blockTime.Unix() >= cva.EndTime { + return cva.OriginalVesting + } + + // calculate the vesting scalar + x := blockTime.Unix() - cva.StartTime + y := cva.EndTime - cva.StartTime + s := sdk.NewDec(x).Quo(sdk.NewDec(y)) + + for _, ovc := range cva.OriginalVesting { + vestedAmt := sdk.NewDecFromInt(ovc.Amount).Mul(s).RoundInt() + vestedCoins = append(vestedCoins, sdk.NewCoin(ovc.Denom, vestedAmt)) + } + + return vestedCoins +} + +// 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)) +} + +// SpendableCoins returns the total number of spendable coins per denom for a +// continuous vesting account. +func (cva ContinuousVestingAccount) SpendableCoins(blockTime time.Time) sdk.Coins { + return cva.spendableCoins(cva.GetVestingCoins(blockTime)) +} + +// TrackDelegation tracks a desired delegation amount by setting the appropriate +// values for the amount of delegated vesting, delegated free, and reducing the +// overall amount of base coins. +func (cva *ContinuousVestingAccount) TrackDelegation(blockTime time.Time, amount sdk.Coins) { + cva.trackDelegation(cva.GetVestingCoins(blockTime), amount) +} + +// GetStartTime returns the time when vesting starts for a continuous vesting +// account. +func (cva *ContinuousVestingAccount) GetStartTime() int64 { + return cva.StartTime +} + +// GetEndTime returns the time when vesting ends for a continuous vesting account. +func (cva *ContinuousVestingAccount) GetEndTime() int64 { + return cva.EndTime +} + +//----------------------------------------------------------------------------- +// Delayed Vesting Account + +var _ VestingAccount = (*DelayedVestingAccount)(nil) + +// DelayedVestingAccount implements the VestingAccount interface. It vests all +// coins after a specific time, but non prior. In other words, it keeps them +// locked until a specified time. +type DelayedVestingAccount struct { + *BaseVestingAccount +} + +func NewDelayedVestingAccount(baseAcc *BaseAccount, EndTime int64) *DelayedVestingAccount { + baseVestingAcc := &BaseVestingAccount{ + BaseAccount: baseAcc, + OriginalVesting: baseAcc.Coins, + EndTime: EndTime, + } + + return &DelayedVestingAccount{baseVestingAcc} +} + +// GetVestedCoins returns the total amount of vested coins for a delayed vesting +// account. All coins are only vested once the schedule has elapsed. +func (dva DelayedVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins { + if blockTime.Unix() >= dva.EndTime { + return dva.OriginalVesting + } + + return nil +} + +// 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)) +} + +// SpendableCoins returns the total number of spendable coins for a delayed +// vesting account. +func (dva DelayedVestingAccount) SpendableCoins(blockTime time.Time) sdk.Coins { + return dva.spendableCoins(dva.GetVestingCoins(blockTime)) +} + +// TrackDelegation tracks a desired delegation amount by setting the appropriate +// values for the amount of delegated vesting, delegated free, and reducing the +// overall amount of base coins. +func (dva *DelayedVestingAccount) TrackDelegation(blockTime time.Time, amount sdk.Coins) { + dva.trackDelegation(dva.GetVestingCoins(blockTime), amount) +} + +// GetStartTime returns zero since a delayed vesting account has no start time. +func (dva *DelayedVestingAccount) GetStartTime() int64 { + return 0 +} + +// GetEndTime returns the time when vesting ends for a delayed vesting account. +func (dva *DelayedVestingAccount) GetEndTime() int64 { + return dva.EndTime +} + +//----------------------------------------------------------------------------- +// Codec // Most users shouldn't use this, but this comes in handy for tests. func RegisterBaseAccount(cdc *codec.Codec) { cdc.RegisterInterface((*Account)(nil), nil) + cdc.RegisterInterface((*VestingAccount)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil) + cdc.RegisterConcrete(&BaseVestingAccount{}, "cosmos-sdk/BaseVestingAccount", nil) + cdc.RegisterConcrete(&ContinuousVestingAccount{}, "cosmos-sdk/ContinuousVestingAccount", nil) + cdc.RegisterConcrete(&DelayedVestingAccount{}, "cosmos-sdk/DelayedVestingAccount", nil) codec.RegisterCrypto(cdc) } diff --git a/x/auth/account_test.go b/x/auth/account_test.go index 8b75e8ce5c9e..6a7753a59ba1 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -2,22 +2,17 @@ package auth import ( "testing" + "time" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" + tmtime "github.com/tendermint/tendermint/types/time" - codec "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) -func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { - key := ed25519.GenPrivKey() - pub := key.PubKey() - addr := sdk.AccAddress(pub.Address()) - return key, pub, addr -} +var testDenom = "testdenom" func TestBaseAddressPubKey(t *testing.T) { _, pub1, addr1 := keyPubAddr() @@ -106,3 +101,379 @@ func TestBaseAccountMarshal(t *testing.T) { err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2) require.NotNil(t, err) } + +func TestGetVestedCoinsContVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + + // require no coins vested in the very beginning of the vesting schedule + vestedCoins := cva.GetVestedCoins(now) + require.Nil(t, vestedCoins) + + // require all coins vested at the end of the vesting schedule + vestedCoins = cva.GetVestedCoins(endTime) + require.Equal(t, origCoins, vestedCoins) + + // require 50% of coins vested + vestedCoins = cva.GetVestedCoins(now.Add(12 * time.Hour)) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestedCoins) + + // require 100% of coins vested + vestedCoins = cva.GetVestedCoins(now.Add(48 * time.Hour)) + require.Equal(t, origCoins, vestedCoins) +} + +func TestGetVestingCoinsContVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + + // require all coins vesting in the beginning of the vesting schedule + vestingCoins := cva.GetVestingCoins(now) + require.Equal(t, origCoins, vestingCoins) + + // require no coins vesting at the end of the vesting schedule + vestingCoins = cva.GetVestingCoins(endTime) + require.Nil(t, vestingCoins) + + // require 50% of coins vesting + vestingCoins = cva.GetVestingCoins(now.Add(12 * time.Hour)) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestingCoins) +} + +func TestSpendableCoinsContVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + + // require that there exist no spendable coins in the beginning of the + // vesting schedule + spendableCoins := cva.SpendableCoins(now) + require.Nil(t, spendableCoins) + + // require that all original coins are spendable at the end of the vesting + // schedule + spendableCoins = cva.SpendableCoins(endTime) + require.Equal(t, origCoins, spendableCoins) + + // require that all vested coins (50%) are spendable + spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, spendableCoins) + + // receive some coins + recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)} + cva.SetCoins(cva.GetCoins().Plus(recvAmt)) + + // require that all vested coins (50%) are spendable plus any received + spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) + require.Equal(t, origCoins, spendableCoins) + + // spend all spendable coins + cva.SetCoins(cva.GetCoins().Minus(spendableCoins)) + + // require that no more coins are spendable + spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) + require.Nil(t, spendableCoins) +} + +func TestTrackDelegationContVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require the ability to delegate all vesting coins + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(now, origCoins) + require.Equal(t, origCoins, cva.DelegatedVesting) + require.Nil(t, cva.DelegatedFree) + require.Nil(t, cva.GetCoins()) + + // require the ability to delegate all vested coins + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(endTime, origCoins) + require.Nil(t, cva.DelegatedVesting) + require.Equal(t, origCoins, cva.DelegatedFree) + require.Nil(t, cva.GetCoins()) + + // require the ability to delegate all vesting coins (50%) and all vested coins (50%) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) + require.Nil(t, cva.DelegatedFree) + + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedFree) + require.Nil(t, cva.GetCoins()) + + // require no modifications when delegation amount is zero or not enough funds + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + require.Panics(t, func() { + cva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) + }) + require.Nil(t, cva.DelegatedVesting) + require.Nil(t, cva.DelegatedFree) + require.Equal(t, origCoins, cva.GetCoins()) +} + +func TestTrackUndelegationContVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require the ability to undelegate all vesting coins + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(now, origCoins) + cva.TrackUndelegation(origCoins) + require.Nil(t, cva.DelegatedFree) + require.Nil(t, cva.DelegatedVesting) + require.Equal(t, origCoins, cva.GetCoins()) + + // require the ability to undelegate all vested coins + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + + cva.TrackDelegation(endTime, origCoins) + cva.TrackUndelegation(origCoins) + require.Nil(t, cva.DelegatedFree) + require.Nil(t, cva.DelegatedVesting) + require.Equal(t, origCoins, cva.GetCoins()) + + // require no modifications when the undelegation amount is zero + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + + require.Panics(t, func() { + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) + }) + require.Nil(t, cva.DelegatedFree) + require.Nil(t, cva.DelegatedVesting) + require.Equal(t, origCoins, cva.GetCoins()) + + // vest 50% and delegate to two validators + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + + // undelegate from one validator that got slashed 50% + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.GetCoins()) + + // undelegate from the other validator that did not get slashed + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + require.Nil(t, cva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, cva.GetCoins()) +} + +func TestGetVestedCoinsDelVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require no coins are vested until schedule maturation + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) + vestedCoins := dva.GetVestedCoins(now) + require.Nil(t, vestedCoins) + + // require all coins be vested at schedule maturation + vestedCoins = dva.GetVestedCoins(endTime) + require.Equal(t, origCoins, vestedCoins) +} + +func TestGetVestingCoinsDelVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require all coins vesting at the beginning of the schedule + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) + vestingCoins := dva.GetVestingCoins(now) + require.Equal(t, origCoins, vestingCoins) + + // require no coins vesting at schedule maturation + vestingCoins = dva.GetVestingCoins(endTime) + require.Nil(t, vestingCoins) +} + +func TestSpendableCoinsDelVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require that no coins are spendable in the beginning of the vesting + // schedule + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) + spendableCoins := dva.SpendableCoins(now) + require.Nil(t, spendableCoins) + + // require that all coins are spendable after the maturation of the vesting + // schedule + spendableCoins = dva.SpendableCoins(endTime) + require.Equal(t, origCoins, spendableCoins) + + // require that all coins are still vesting after some time + spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour)) + require.Nil(t, spendableCoins) + + // receive some coins + recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)} + dva.SetCoins(dva.GetCoins().Plus(recvAmt)) + + // require that only received coins are spendable since the account is still + // vesting + spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour)) + require.Equal(t, recvAmt, spendableCoins) + + // spend all spendable coins + dva.SetCoins(dva.GetCoins().Minus(spendableCoins)) + + // require that no more coins are spendable + spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour)) + require.Nil(t, spendableCoins) +} + +func TestTrackDelegationDelVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require the ability to delegate all vesting coins + bacc.SetCoins(origCoins) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(now, origCoins) + require.Equal(t, origCoins, dva.DelegatedVesting) + require.Nil(t, dva.DelegatedFree) + require.Nil(t, dva.GetCoins()) + + // require the ability to delegate all vested coins + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(endTime, origCoins) + require.Nil(t, dva.DelegatedVesting) + require.Equal(t, origCoins, dva.DelegatedFree) + require.Nil(t, dva.GetCoins()) + + // require the ability to delegate all coins half way through the vesting + // schedule + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(now.Add(12*time.Hour), origCoins) + require.Equal(t, origCoins, dva.DelegatedVesting) + require.Nil(t, dva.DelegatedFree) + require.Nil(t, dva.GetCoins()) + + // require no modifications when delegation amount is zero or not enough funds + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + + require.Panics(t, func() { + dva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) + }) + require.Nil(t, dva.DelegatedVesting) + require.Nil(t, dva.DelegatedFree) + require.Equal(t, origCoins, dva.GetCoins()) +} + +func TestTrackUndelegationDelVestingAcc(t *testing.T) { + now := tmtime.Now() + endTime := now.Add(24 * time.Hour) + + _, _, addr := keyPubAddr() + origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + + // require the ability to undelegate all vesting coins + bacc.SetCoins(origCoins) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(now, origCoins) + dva.TrackUndelegation(origCoins) + require.Nil(t, dva.DelegatedFree) + require.Nil(t, dva.DelegatedVesting) + require.Equal(t, origCoins, dva.GetCoins()) + + // require the ability to undelegate all vested coins + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(endTime, origCoins) + dva.TrackUndelegation(origCoins) + require.Nil(t, dva.DelegatedFree) + require.Nil(t, dva.DelegatedVesting) + require.Equal(t, origCoins, dva.GetCoins()) + + // require no modifications when the undelegation amount is zero + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + + require.Panics(t, func() { + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) + }) + require.Nil(t, dva.DelegatedFree) + require.Nil(t, dva.DelegatedVesting) + require.Equal(t, origCoins, dva.GetCoins()) + + // vest 50% and delegate to two validators + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) + dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + + // undelegate from one validator that got slashed 50% + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}) + + require.Nil(t, dva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.GetCoins()) + + // undelegate from the other validator that did not get slashed + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + require.Nil(t, dva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.GetCoins()) +} diff --git a/x/auth/ante.go b/x/auth/ante.go index 91789dd22ca2..f82b7fc650ce 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -4,31 +4,21 @@ import ( "bytes" "encoding/hex" "fmt" + "strings" + "time" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) -const ( - memoCostPerByte sdk.Gas = 3 - ed25519VerifyCost = 590 - secp256k1VerifyCost = 1000 - maxMemoCharacters = 256 - - // how much gas = 1 atom - gasPerUnitCost = 10000 - - // max total number of sigs per tx - txSigLimit = 7 -) - // NewAnteHandler returns an AnteHandler that checks and increments sequence // numbers, checks signatures & account numbers, and deducts fees from the first // signer. -func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { +func NewAnteHandler(ak AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, simulate bool, ) (newCtx sdk.Context, res sdk.Result, abort bool) { @@ -36,20 +26,25 @@ func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { // all transactions must be of type auth.StdTx stdTx, ok := tx.(StdTx) if !ok { - return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true + // Set a gas meter with limit 0 as to prevent an infinite gas meter attack + // during runTx. + newCtx = SetGasMeter(simulate, ctx, 0) + return newCtx, sdk.ErrInternal("tx must be StdTx").Result(), true } + params := ak.GetParams(ctx) + // Ensure that the provided fees meet a minimum threshold for the validator, // if this is a CheckTx. This is only for local mempool purposes, and thus // is only ran on check tx. if ctx.IsCheckTx() && !simulate { - res := ensureSufficientMempoolFees(ctx, stdTx) + res := EnsureSufficientMempoolFees(ctx, stdTx.Fee) if !res.IsOK() { return newCtx, res, true } } - newCtx = setGasMeter(simulate, ctx, stdTx) + newCtx = SetGasMeter(simulate, ctx, stdTx.Fee.Gas) // AnteHandlers must have their own defer/recover in order for the BaseApp // to know how much gas was used! This is because the GasMeter is created in @@ -74,23 +69,24 @@ func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { return newCtx, err.Result(), true } - newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") + if res := ValidateMemo(newCtx.GasMeter(), stdTx, params); !res.IsOK() { + return newCtx, res, true + } // stdSigs contains the sequence number, account number, and signatures. // When simulating, this would just be a 0-length slice. - stdSigs := stdTx.GetSignatures() signerAddrs := stdTx.GetSigners() - signerAccs, res := getSignerAccs(newCtx, am, signerAddrs) + signerAccs := make([]Account, len(signerAddrs)) + isGenesis := ctx.BlockHeight() == 0 + + // fetch first signer, who's going to pay the fees + signerAccs[0], res = GetSignerAcc(newCtx, ak, signerAddrs[0]) if !res.IsOK() { return newCtx, res, true } - isGenesis := ctx.BlockHeight() == 0 - signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, signerAccs, isGenesis) - - // first sig pays the fees if !stdTx.Fee.Amount.IsZero() { - signerAccs[0], res = deductFees(signerAccs[0], stdTx.Fee) + signerAccs[0], res = DeductFees(ctx.BlockHeader().Time, signerAccs[0], stdTx.Fee) if !res.IsOK() { return newCtx, res, true } @@ -98,43 +94,67 @@ func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler { fck.AddCollectedFees(newCtx, stdTx.Fee.Amount) } + // stdSigs contains the sequence number, account number, and signatures. + // When simulating, this would just be a 0-length slice. + stdSigs := stdTx.GetSignatures() + for i := 0; i < len(stdSigs); i++ { + // skip the fee payer, account is cached and fees were deducted already + if i != 0 { + signerAccs[i], res = GetSignerAcc(newCtx, ak, signerAddrs[i]) + if !res.IsOK() { + return newCtx, res, true + } + } + // check signature, return account with incremented nonce - signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], simulate) + signBytes := GetSignBytes(newCtx.ChainID(), stdTx, signerAccs[i], isGenesis) + signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytes, simulate, params) if !res.IsOK() { return newCtx, res, true } - am.SetAccount(newCtx, signerAccs[i]) + ak.SetAccount(newCtx, signerAccs[i]) } - // cache the signer accounts in the context - newCtx = WithSigners(newCtx, signerAccs) - // TODO: tx tags (?) return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue... } } -func getSignerAccs(ctx sdk.Context, am AccountKeeper, addrs []sdk.AccAddress) (accs []Account, res sdk.Result) { - accs = make([]Account, len(addrs)) - for i := 0; i < len(accs); i++ { - accs[i] = am.GetAccount(ctx, addrs[i]) - if accs[i] == nil { - return nil, sdk.ErrUnknownAddress(addrs[i].String()).Result() - } +// GetSignerAcc returns an account for a given address that is expected to sign +// a transaction. +func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (Account, sdk.Result) { + if acc := ak.GetAccount(ctx, addr); acc != nil { + return acc, sdk.Result{} + } + return nil, sdk.ErrUnknownAddress(addr.String()).Result() +} + +// ValidateMemo validates the memo and if successful consumes gas for +// verification. +func ValidateMemo(gasMeter sdk.GasMeter, stdTx StdTx, params Params) sdk.Result { + memoLength := len(stdTx.GetMemo()) + if uint64(memoLength) > params.MaxMemoCharacters { + return sdk.ErrMemoTooLarge( + fmt.Sprintf( + "maximum number of characters is %d but received %d characters", + params.MaxMemoCharacters, memoLength, + ), + ).Result() } - return + gasMeter.ConsumeGas(params.MemoCostPerByte*sdk.Gas(memoLength), "memo") + return sdk.Result{} } // verify the signature and increment the sequence. If the account doesn't have // a pubkey, set it. func processSig( - ctx sdk.Context, acc Account, sig StdSignature, signBytes []byte, simulate bool, + ctx sdk.Context, acc Account, sig StdSignature, signBytes []byte, simulate bool, params Params, ) (updatedAcc Account, res sdk.Result) { - pubKey, res := processPubKey(acc, sig, simulate) + pubKey, res := ProcessPubKey(acc, sig, simulate) if !res.IsOK() { return nil, res } @@ -144,7 +164,7 @@ func processSig( return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() } - consumeSignatureVerificationGas(ctx.GasMeter(), pubKey) + consumeSignatureVerificationGas(ctx.GasMeter(), sig.Signature, pubKey, params) if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) { return nil, sdk.ErrUnauthorized("signature verification failed").Result() } @@ -165,7 +185,10 @@ func init() { copy(dummySecp256k1Pubkey[:], bz) } -func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) { +// ProcessPubKey verifies that the given account address matches that of the +// StdSignature. In addition, it will set the public key of the account if it +// has not been set. +func ProcessPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) { // If pubkey is not known for account, set it from the StdSignature. pubKey := acc.GetPubKey() if simulate { @@ -188,41 +211,56 @@ func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, if !bytes.Equal(pubKey.Address(), acc.GetAddress()) { return nil, sdk.ErrInvalidPubKey( - fmt.Sprintf("PubKey does not match Signer address %v", acc.GetAddress())).Result() + fmt.Sprintf("PubKey does not match Signer address %s", acc.GetAddress())).Result() } } return pubKey, sdk.Result{} } -func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { - switch pubkey.(type) { - case ed25519.PubKeyEd25519: - meter.ConsumeGas(ed25519VerifyCost, "ante verify: ed25519") - case secp256k1.PubKeySecp256k1: - meter.ConsumeGas(secp256k1VerifyCost, "ante verify: secp256k1") +// consumeSignatureVerificationGas consumes gas for signature verification based +// upon the public key type. The cost is fetched from the given params and is +// matched by the concrete type. +// +// TODO: Design a cleaner and flexible way to match concrete public key types. +func consumeSignatureVerificationGas(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) { + pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) + switch { + case strings.Contains(pubkeyType, "ed25519"): + meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + case strings.Contains(pubkeyType, "secp256k1"): + meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + case strings.Contains(pubkeyType, "multisigthreshold"): + + var multisignature multisig.Multisignature + codec.Cdc.MustUnmarshalBinaryBare(sig, &multisignature) + multisigPubKey := pubkey.(multisig.PubKeyMultisigThreshold) + + consumeMultisignatureVerificationGas(meter, multisignature, multisigPubKey, params) default: - panic("Unrecognized signature type") + panic(fmt.Sprintf("unrecognized signature type: %s", pubkeyType)) } } -func adjustFeesByGas(fees sdk.Coins, gas uint64) sdk.Coins { - gasCost := gas / gasPerUnitCost - gasFees := make(sdk.Coins, len(fees)) +func consumeMultisignatureVerificationGas(meter sdk.GasMeter, + sig multisig.Multisignature, pubkey multisig.PubKeyMultisigThreshold, + params Params) { - // TODO: Make this not price all coins in the same way - // TODO: Undo int64 casting once unsigned integers are supported for coins - for i := 0; i < len(fees); i++ { - gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, int64(gasCost)) + size := sig.BitArray.Size() + sigIndex := 0 + for i := 0; i < size; i++ { + if sig.BitArray.GetIndex(i) { + consumeSignatureVerificationGas(meter, sig.Sigs[sigIndex], pubkey.PubKeys[i], params) + sigIndex++ + } } - - return fees.Plus(gasFees) } -// Deduct the fee from the account. -// We could use the CoinKeeper (in addition to the AccountKeeper, -// because the CoinKeeper doesn't give us accounts), but it seems easier to do this. -func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { +// DeductFees deducts fees from the given account. +// +// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because +// the CoinKeeper doesn't give us accounts), but it seems easier to do this. +func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Result) { coins := acc.GetCoins() feeAmount := fee.Amount @@ -230,14 +268,21 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { return nil, sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", feeAmount)).Result() } + // get the resulting coins deducting the fees newCoins, ok := coins.SafeMinus(feeAmount) if ok { errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) return nil, sdk.ErrInsufficientFunds(errMsg).Result() } - err := acc.SetCoins(newCoins) - if err != nil { + // 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 { + return nil, sdk.ErrInsufficientFunds(fmt.Sprintf("%s < %s", spendableCoins, feeAmount)).Result() + } + + if err := acc.SetCoins(newCoins); err != nil { // Handle w/ #870 panic(err) } @@ -245,51 +290,59 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { return acc, sdk.Result{} } -func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { - // Currently we use a very primitive gas pricing model with a constant - // gasPrice where adjustFeesByGas handles calculating the amount of fees - // required based on the provided gas. - // - // TODO: - // - Make the gasPrice not a constant, and account for tx size. - // - Make Gas an unsigned integer and use tx basic validation - if stdTx.Fee.Gas <= 0 { - return sdk.ErrInternal(fmt.Sprintf("invalid gas supplied: %d", stdTx.Fee.Gas)).Result() - } - requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) +// EnsureSufficientMempoolFees verifies that the given transaction has supplied +// enough fees to cover a proposer's minimum fees. An result object is returned +// indicating success or failure. +// +// TODO: Account for transaction size. +// +// Contract: This should only be called during CheckTx as it cannot be part of +// consensus. +func EnsureSufficientMempoolFees(ctx sdk.Context, stdFee StdFee) sdk.Result { + minGasPrices := ctx.MinGasPrices() + if !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdk.NewDec(int64(stdFee.Gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewInt64Coin(gp.Denom, fee.Ceil().RoundInt64()) + } - // NOTE: !A.IsAllGTE(B) is not the same as A.IsAllLT(B). - if !ctx.MinimumFees().IsZero() && !stdTx.Fee.Amount.IsAllGTE(requiredFees) { - // validators reject any tx from the mempool with less than the minimum fee per gas * gas factor - return sdk.ErrInsufficientFee( - fmt.Sprintf( - "insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees), - ).Result() + if !stdFee.Amount.IsAllGTE(requiredFees) { + return sdk.ErrInsufficientFee( + fmt.Sprintf( + "insufficient fees; got: %q required: %q", stdFee.Amount, requiredFees, + ), + ).Result() + } } return sdk.Result{} } -func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context { +// SetGasMeter returns a new context with a gas meter set from a given context. +func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context { // In various cases such as simulation and during the genesis block, we do not // meter any gas utilization. if simulate || ctx.BlockHeight() == 0 { return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) } - return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) + return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) } -func getSignBytesList(chainID string, stdTx StdTx, accs []Account, genesis bool) (signatureBytesList [][]byte) { - signatureBytesList = make([][]byte, len(accs)) - for i := 0; i < len(accs); i++ { - accNum := accs[i].GetAccountNumber() - if genesis { - accNum = 0 - } - signatureBytesList[i] = StdSignBytes(chainID, - accNum, accs[i].GetSequence(), - stdTx.Fee, stdTx.Msgs, stdTx.Memo) +// GetSignBytes returns a slice of bytes to sign over for a given transaction +// and an account. +func GetSignBytes(chainID string, stdTx StdTx, acc Account, genesis bool) []byte { + var accNum uint64 + if !genesis { + accNum = acc.GetAccountNumber() } - return + + return StdSignBytes( + chainID, accNum, acc.GetSequence(), stdTx.Fee, stdTx.Msgs, stdTx.Memo, + ) } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 9d76107fe73f..bbe4f62fd64d 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -2,45 +2,19 @@ package auth import ( "fmt" + "math/rand" "strings" "testing" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/tendermint/tendermint/libs/log" - codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) -func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { - return sdk.NewTestMsg(addrs...) -} - -func newStdFee() StdFee { - return NewStdFee(50000, - sdk.NewInt64Coin("atom", 150), - ) -} - -// coins to more than cover the fee -func newCoins() sdk.Coins { - return sdk.Coins{ - sdk.NewInt64Coin("atom", 10000000), - } -} - -// generate a priv key and return it with its address -func privAndAddr() (crypto.PrivKey, sdk.AccAddress) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - return priv, addr -} - // run the tx through the anteHandler and ensure its valid func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool) { _, result, abort := anteHandler(ctx, tx, simulate) @@ -68,63 +42,17 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, } } -func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} - } - tx := NewStdTx(msgs, fee, sigs, "") - return tx -} - -func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, memo string) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} - } - tx := NewStdTx(msgs, fee, sigs, memo) - return tx -} - -// All signers sign over the same StdSignDoc. Should always create invalid signatures -func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, signBytes []byte, memo string) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} - } - tx := NewStdTx(msgs, fee, sigs, memo) - return tx -} - // Test various error cases in the AnteHandler control flow. func TestAnteHandlerSigErrors(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + input := setupTestInput() + ctx := input.ctx + anteHandler := NewAnteHandler(input.ak, input.fck) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() - priv3, addr3 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + priv3, _, addr3 := keyPubAddr() // msg and signatures var tx sdk.Tx @@ -144,7 +72,7 @@ func TestAnteHandlerSigErrors(t *testing.T) { require.Equal(t, expectedSigners, stdTx.GetSigners()) // Check no signatures fails - checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnauthorized) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeNoSignatures) // test num sigs dont match GetSigners privs, accNums, seqs = []crypto.PrivKey{priv1}, []uint64{0}, []uint64{0} @@ -157,35 +85,30 @@ func TestAnteHandlerSigErrors(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnknownAddress) // save the first account, but second is still unrecognized - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(fee.Amount) - mapper.SetAccount(ctx, acc1) + input.ak.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnknownAddress) } // Test logic around account number checking with one signer and many signers. func TestAnteHandlerAccountNumbers(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(ctx, acc2) // msg and signatures var tx sdk.Tx @@ -226,26 +149,21 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { // Test logic around account number checking with many signers when BlockHeight is 0. func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(0) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(0) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(ctx, acc2) // msg and signatures var tx sdk.Tx @@ -286,30 +204,25 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { // Test logic around sequence checking with one signer and many signers. func TestAnteHandlerSequences(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() - priv3, addr3 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + priv3, _, addr3 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) - acc3 := mapper.NewAccountWithAddress(ctx, addr3) + input.ak.SetAccount(ctx, acc2) + acc3 := input.ak.NewAccountWithAddress(ctx, addr3) acc3.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc3) + input.ak.SetAccount(ctx, acc3) // msg and signatures var tx sdk.Tx @@ -365,20 +278,16 @@ func TestAnteHandlerSequences(t *testing.T) { // Test logic around fee deduction. func TestAnteHandlerFees(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + input := setupTestInput() + ctx := input.ctx + anteHandler := NewAnteHandler(input.ak, input.fck) // keys and addresses - priv1, addr1 := privAndAddr() + priv1, _, addr1 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) - mapper.SetAccount(ctx, acc1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) + input.ak.SetAccount(ctx, acc1) // msg and signatures var tx sdk.Tx @@ -392,89 +301,81 @@ func TestAnteHandlerFees(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInsufficientFunds) acc1.SetCoins(sdk.Coins{sdk.NewInt64Coin("atom", 149)}) - mapper.SetAccount(ctx, acc1) + input.ak.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInsufficientFunds) - require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, input.ak.GetAccount(ctx, addr1).GetCoins().AmountOf("atom").Equal(sdk.NewInt(149))) acc1.SetCoins(sdk.Coins{sdk.NewInt64Coin("atom", 150)}) - mapper.SetAccount(ctx, acc1) + input.ak.SetAccount(ctx, acc1) checkValidTx(t, anteHandler, ctx, tx, false) - require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(sdk.Coins{sdk.NewInt64Coin("atom", 150)})) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(sdk.Coins{sdk.NewInt64Coin("atom", 150)})) + require.True(t, input.ak.GetAccount(ctx, addr1).GetCoins().AmountOf("atom").Equal(sdk.NewInt(0))) } // Test logic around memo gas consumption. func TestAnteHandlerMemoGas(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() + priv1, _, addr1 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) - mapper.SetAccount(ctx, acc1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) + input.ak.SetAccount(ctx, acc1) // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1) privs, accnums, seqs := []crypto.PrivKey{priv1}, []uint64{0}, []uint64{0} - fee := NewStdFee(0, sdk.NewInt64Coin("atom", 0)) + fee := NewStdFee(0, sdk.Coins{sdk.NewInt64Coin("atom", 0)}) // tx does not have enough gas tx = newTestTx(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeOutOfGas) // tx with memo doesn't have enough gas - fee = NewStdFee(801, sdk.NewInt64Coin("atom", 0)) + fee = NewStdFee(801, sdk.Coins{sdk.NewInt64Coin("atom", 0)}) tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeOutOfGas) // memo too large - fee = NewStdFee(9000, sdk.NewInt64Coin("atom", 0)) + fee = NewStdFee(9000, sdk.Coins{sdk.NewInt64Coin("atom", 0)}) tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, strings.Repeat("01234567890", 500)) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeMemoTooLarge) // tx with memo has enough gas - fee = NewStdFee(9000, sdk.NewInt64Coin("atom", 0)) + fee = NewStdFee(9000, sdk.Coins{sdk.NewInt64Coin("atom", 0)}) tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, strings.Repeat("0123456789", 10)) checkValidTx(t, anteHandler, ctx, tx, false) } func TestAnteHandlerMultiSigner(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() - priv3, addr3 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + priv3, _, addr3 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) - acc3 := mapper.NewAccountWithAddress(ctx, addr3) + input.ak.SetAccount(ctx, acc2) + acc3 := input.ak.NewAccountWithAddress(ctx, addr3) acc3.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc3) + input.ak.SetAccount(ctx, acc3) // set up msgs and fee var tx sdk.Tx @@ -503,26 +404,21 @@ func TestAnteHandlerMultiSigner(t *testing.T) { func TestAnteHandlerBadSignBytes(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(ctx, acc2) var tx sdk.Tx msg := newTestMsg(addr1) @@ -561,7 +457,6 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { privs, seqs = []crypto.PrivKey{priv1}, []uint64{1} for _, cs := range cases { tx := newTestTxWithSignBytes( - msgs, privs, accnums, seqs, fee, StdSignBytes(cs.chainID, cs.accnum, cs.seq, cs.fee, cs.msgs, ""), "", @@ -580,31 +475,25 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { privs, accnums, seqs = []crypto.PrivKey{priv1}, []uint64{1}, []uint64{0} tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) - } func TestAnteHandlerSetPubKey(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - _, addr2 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + _, _, addr2 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(ctx, acc2) var tx sdk.Tx @@ -616,7 +505,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx, false) - acc1 = mapper.GetAccount(ctx, addr1) + acc1 = input.ak.GetAccount(ctx, addr1) require.Equal(t, acc1.GetPubKey(), priv1.PubKey()) // test public key not found @@ -627,27 +516,29 @@ func TestAnteHandlerSetPubKey(t *testing.T) { sigs[0].PubKey = nil checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) - acc2 = mapper.GetAccount(ctx, addr2) + acc2 = input.ak.GetAccount(ctx, addr2) require.Nil(t, acc2.GetPubKey()) // test invalid signature and public key tx = newTestTx(ctx, msgs, privs, []uint64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) - acc2 = mapper.GetAccount(ctx, addr2) + acc2 = input.ak.GetAccount(ctx, addr2) require.Nil(t, acc2.GetPubKey()) } func TestProcessPubKey(t *testing.T) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + input := setupTestInput() + ctx := input.ctx + // keys - _, addr1 := privAndAddr() - priv2, _ := privAndAddr() - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + _, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) + + acc2.SetPubKey(priv2.PubKey()) + type args struct { acc Account sig StdSignature @@ -660,21 +551,35 @@ func TestProcessPubKey(t *testing.T) { }{ {"no sigs, simulate off", args{acc1, StdSignature{}, false}, true}, {"no sigs, simulate on", args{acc1, StdSignature{}, true}, false}, + {"no sigs, account with pub, simulate on", args{acc2, StdSignature{}, true}, false}, {"pubkey doesn't match addr, simulate off", args{acc1, StdSignature{PubKey: priv2.PubKey()}, false}, true}, {"pubkey doesn't match addr, simulate on", args{acc1, StdSignature{PubKey: priv2.PubKey()}, true}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := processPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + _, err := ProcessPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) require.Equal(t, tt.wantErr, !err.IsOK()) }) } } func TestConsumeSignatureVerificationGas(t *testing.T) { + params := DefaultParams() + msg := []byte{1, 2, 3, 4} + + pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false) + multisigKey1 := multisig.NewPubKeyMultisigThreshold(2, pkSet1) + multisignature1 := multisig.NewMultisig(len(pkSet1)) + expectedCost1 := expectedGasCostByKeys(pkSet1) + for i := 0; i < len(pkSet1); i++ { + multisignature1.AddSignatureFromPubKey(sigSet1[i], pkSet1[i], pkSet1) + } + type args struct { meter sdk.GasMeter + sig []byte pubkey crypto.PubKey + params Params } tests := []struct { name string @@ -682,40 +587,53 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { gasConsumed uint64 wantPanic bool }{ - {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), ed25519.GenPrivKey().PubKey()}, ed25519VerifyCost, false}, - {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), secp256k1.GenPrivKey().PubKey()}, secp256k1VerifyCost, false}, - {"unknown key", args{sdk.NewInfiniteGasMeter(), nil}, 0, true}, + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, false}, + {"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() { consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) }) + require.Panics(t, func() { consumeSignatureVerificationGas(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) }) } else { - consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) - require.Equal(t, tt.args.meter.GasConsumed(), tt.gasConsumed) + consumeSignatureVerificationGas(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) + require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) } }) } } -func TestAdjustFeesByGas(t *testing.T) { - type args struct { - fee sdk.Coins - gas uint64 - } - tests := []struct { - name string - args args - want sdk.Coins - }{ - {"nil coins", args{sdk.Coins{}, 100000}, sdk.Coins{}}, - {"nil coins", args{sdk.Coins{sdk.NewInt64Coin("A", 10), sdk.NewInt64Coin("B", 0)}, 100000}, sdk.Coins{sdk.NewInt64Coin("A", 20), sdk.NewInt64Coin("B", 10)}}, +func generatePubKeysAndSignatures(n int, msg []byte, keyTypeed25519 bool) (pubkeys []crypto.PubKey, signatures [][]byte) { + pubkeys = make([]crypto.PubKey, n) + signatures = make([][]byte, n) + for i := 0; i < n; i++ { + var privkey crypto.PrivKey + if rand.Int63()%2 == 0 { + privkey = ed25519.GenPrivKey() + } else { + privkey = secp256k1.GenPrivKey() + } + pubkeys[i] = privkey.PubKey() + signatures[i], _ = privkey.Sign(msg) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.True(t, tt.want.IsEqual(adjustFeesByGas(tt.args.fee, tt.args.gas))) - }) + return +} + +func expectedGasCostByKeys(pubkeys []crypto.PubKey) uint64 { + cost := uint64(0) + for _, pubkey := range pubkeys { + pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) + switch { + case strings.Contains(pubkeyType, "ed25519"): + cost += DefaultParams().SigVerifyCostED25519 + case strings.Contains(pubkeyType, "secp256k1"): + cost += DefaultParams().SigVerifyCostSecp256k1 + default: + panic("unexpected key type") + } } + return cost } func TestCountSubkeys(t *testing.T) { @@ -726,23 +644,23 @@ func TestCountSubkeys(t *testing.T) { } return ret } - genMultiKey := func(n, k int, keysGen func(n int) []crypto.PubKey) crypto.PubKey { - return multisig.NewPubKeyMultisigThreshold(k, keysGen(n)) - } + singleKey := secp256k1.GenPrivKey().PubKey() + singleLevelMultiKey := multisig.NewPubKeyMultisigThreshold(4, genPubKeys(5)) + multiLevelSubKey1 := multisig.NewPubKeyMultisigThreshold(4, genPubKeys(5)) + multiLevelSubKey2 := multisig.NewPubKeyMultisigThreshold(4, genPubKeys(5)) + multiLevelMultiKey := multisig.NewPubKeyMultisigThreshold(2, []crypto.PubKey{ + multiLevelSubKey1, multiLevelSubKey2, secp256k1.GenPrivKey().PubKey()}) type args struct { pub crypto.PubKey } - mkey := genMultiKey(5, 4, genPubKeys) - mkeyType := mkey.(*multisig.PubKeyMultisigThreshold) - mkeyType.PubKeys = append(mkeyType.PubKeys, genMultiKey(6, 5, genPubKeys)) tests := []struct { name string args args want int }{ - {"single key", args{secp256k1.GenPrivKey().PubKey()}, 1}, - {"multi sig key", args{genMultiKey(5, 4, genPubKeys)}, 5}, - {"multi multi sig", args{mkey}, 11}, + {"single key", args{singleKey}, 1}, + {"single level multikey", args{singleLevelMultiKey}, 5}, + {"multi level multikey", args{multiLevelMultiKey}, 11}, } for _, tt := range tests { t.Run(tt.name, func(T *testing.T) { @@ -753,32 +671,27 @@ func TestCountSubkeys(t *testing.T) { func TestAnteHandlerSigLimitExceeded(t *testing.T) { // setup - ms, capKey, capKey2 := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - feeCollector := NewFeeCollectionKeeper(cdc, capKey2) - anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - ctx = ctx.WithBlockHeight(1) + input := setupTestInput() + anteHandler := NewAnteHandler(input.ak, input.fck) + ctx := input.ctx.WithBlockHeight(1) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() - priv3, addr3 := privAndAddr() - priv4, addr4 := privAndAddr() - priv5, addr5 := privAndAddr() - priv6, addr6 := privAndAddr() - priv7, addr7 := privAndAddr() - priv8, addr8 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + priv3, _, addr3 := keyPubAddr() + priv4, _, addr4 := keyPubAddr() + priv5, _, addr5 := keyPubAddr() + priv6, _, addr6 := keyPubAddr() + priv7, _, addr7 := keyPubAddr() + priv8, _, addr8 := keyPubAddr() // set the accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1 := input.ak.NewAccountWithAddress(ctx, addr1) acc1.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, acc1) + acc2 := input.ak.NewAccountWithAddress(ctx, addr2) acc2.SetCoins(newCoins()) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(ctx, acc2) var tx sdk.Tx msg := newTestMsg(addr1, addr2, addr3, addr4, addr5, addr6, addr7, addr8) @@ -791,3 +704,51 @@ func TestAnteHandlerSigLimitExceeded(t *testing.T) { tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeTooManySignatures) } + +func TestEnsureSufficientMempoolFees(t *testing.T) { + // setup + input := setupTestInput() + ctx := input.ctx.WithMinGasPrices( + sdk.DecCoins{ + sdk.NewDecCoinFromDec("photino", sdk.NewDecWithPrec(1000000, sdk.Precision)), // 0.0001photino + sdk.NewDecCoinFromDec("stake", sdk.NewDecWithPrec(10000, sdk.Precision)), // 0.000001stake + }, + ) + + testCases := []struct { + input StdFee + expectedOK bool + }{ + {NewStdFee(200000, sdk.Coins{sdk.NewInt64Coin("stake", 1)}), false}, + {NewStdFee(200000, sdk.Coins{sdk.NewInt64Coin("photino", 20)}), false}, + { + NewStdFee( + 200000, + sdk.Coins{ + sdk.NewInt64Coin("photino", 20), + sdk.NewInt64Coin("stake", 1), + }, + ), + true, + }, + { + NewStdFee( + 200000, + sdk.Coins{ + sdk.NewInt64Coin("atom", 2), + sdk.NewInt64Coin("photino", 20), + sdk.NewInt64Coin("stake", 1), + }, + ), + true, + }, + } + + for i, tc := range testCases { + res := EnsureSufficientMempoolFees(ctx, tc.input) + require.Equal( + t, tc.expectedOK, res.IsOK(), + "unexpected result; tc #%d, input: %v, log: %v", i, tc.input, res.Log, + ) + } +} diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index 922b3e2db202..f68873187746 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -20,18 +18,14 @@ func GetAccountCmd(storeName string, cdc *codec.Codec) *cobra.Command { Short: "Query account balance", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - // find the key to look up the account - addr := args[0] + cliCtx := context.NewCLIContext(). + WithCodec(cdc).WithAccountDecoder(cdc) - key, err := sdk.AccAddressFromBech32(addr) + key, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(cdc) - if err = cliCtx.EnsureAccountExistsFromAddr(key); err != nil { return err } @@ -41,18 +35,7 @@ func GetAccountCmd(storeName string, cdc *codec.Codec) *cobra.Command { return err } - var output []byte - if cliCtx.Indent { - output, err = cdc.MarshalJSONIndent(acc, "", " ") - } else { - output, err = cdc.MarshalJSON(acc) - } - if err != nil { - return err - } - - fmt.Println(string(output)) - return nil + return cliCtx.PrintOutput(acc) }, } diff --git a/x/auth/client/cli/multisign.go b/x/auth/client/cli/multisign.go new file mode 100644 index 000000000000..9fcc8a0a0730 --- /dev/null +++ b/x/auth/client/cli/multisign.go @@ -0,0 +1,159 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto/multisig" + "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/keys" + crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/x/auth" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" +) + +// GetSignCommand returns the sign command +func GetMultiSignCommand(codec *amino.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "multisign <...>", + Short: "Generate multisig signatures for transactions generated offline", + Long: `Sign transactions created with the --generate-only flag that require multisig signatures. + +Read signature(s) from file(s), generate a multisig signature compliant to the +multisig key , and attach it to the transaction read from . Example: + + gaiacli multisign transaction.json k1k2k3 k1sig.json k2sig.json k3sig.json + +If the flag --signature-only flag is on, it outputs a JSON representation +of the generated signature only. + +The --offline flag makes sure that the client will not reach out to an external node. +Thus account number or sequence number lookups will not be performed and it is +recommended to set such parameters manually. +`, + RunE: makeMultiSignCmd(codec), + Args: cobra.MinimumNArgs(3), + } + 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 makeMultiSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) (err error) { + stdTx, err := readAndUnmarshalStdTx(cdc, args[0]) + if err != nil { + return + } + + keybase, err := keys.GetKeyBaseFromDir(viper.GetString(cli.HomeFlag)) + if err != nil { + return + } + + multisigInfo, err := keybase.Get(args[1]) + if err != nil { + return + } + if multisigInfo.GetType() != crkeys.TypeOffline { + return fmt.Errorf("%q must be of type offline: %s", + args[1], multisigInfo.GetType()) + } + + multisigPub := multisigInfo.GetPubKey().(multisig.PubKeyMultisigThreshold) + multisigSig := multisig.NewMultisig(len(multisigPub.PubKeys)) + cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc) + txBldr := authtxb.NewTxBuilderFromCLI() + + if !viper.GetBool(flagOffline) { + addr := multisigInfo.GetAddress() + accnum, err := cliCtx.GetAccountNumber(addr) + if err != nil { + return err + } + + seq, err := cliCtx.GetAccountSequence(addr) + if err != nil { + return err + } + + txBldr = txBldr.WithAccountNumber(accnum).WithSequence(seq) + } + + // read each signature and add it to the multisig if valid + for i := 2; i < len(args); i++ { + stdSig, err := readAndUnmarshalStdSignature(cdc, args[i]) + if err != nil { + return err + } + + // Validate each signature + sigBytes := auth.StdSignBytes( + txBldr.GetChainID(), txBldr.GetAccountNumber(), txBldr.GetSequence(), + stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(), + ) + if ok := stdSig.PubKey.VerifyBytes(sigBytes, stdSig.Signature); !ok { + return fmt.Errorf("couldn't verify signature") + } + multisigSig.AddSignatureFromPubKey(stdSig.Signature, stdSig.PubKey, multisigPub.PubKeys) + } + + newStdSig := auth.StdSignature{Signature: cdc.MustMarshalBinaryBare(multisigSig), PubKey: multisigPub} + newTx := auth.NewStdTx(stdTx.GetMsgs(), stdTx.Fee, []auth.StdSignature{newStdSig}, stdTx.GetMemo()) + + sigOnly := viper.GetBool(flagSigOnly) + var json []byte + switch { + case sigOnly && cliCtx.Indent: + json, err = cdc.MarshalJSONIndent(newTx.Signatures[0], "", " ") + case sigOnly && !cliCtx.Indent: + json, err = cdc.MarshalJSON(newTx.Signatures[0]) + case !sigOnly && cliCtx.Indent: + json, err = cdc.MarshalJSONIndent(newTx, "", " ") + default: + json, err = cdc.MarshalJSON(newTx) + } + if err != nil { + return err + } + + if viper.GetString(flagOutfile) == "" { + fmt.Printf("%s\n", json) + return + } + + fp, err := os.OpenFile( + viper.GetString(flagOutfile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644, + ) + if err != nil { + return err + } + defer fp.Close() + + fmt.Fprintf(fp, "%s\n", json) + + return + } +} + +func readAndUnmarshalStdSignature(cdc *amino.Codec, filename string) (stdSig auth.StdSignature, err error) { + var bytes []byte + if bytes, err = ioutil.ReadFile(filename); err != nil { + return + } + if err = cdc.UnmarshalJSON(bytes, &stdSig); err != nil { + return + } + return +} diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index 842299df8780..1e0bc895ff0a 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -19,6 +19,7 @@ import ( ) const ( + flagMultisig = "multisig" flagAppend = "append" flagValidateSigs = "validate-signatures" flagOffline = "offline" @@ -45,17 +46,26 @@ performed as that will require communication with a full node. The --offline flag makes sure that the client will not reach out to an external node. Thus account number or sequence number lookups will not be performed and it is -recommended to set such parameters manually.`, +recommended to set such parameters manually. + +The --multisig= flag generates a signature on behalf of a multisig account +key. It implies --signature-only. Full multisig signed transactions may eventually +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") - cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit.") + "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.") + "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") @@ -75,7 +85,7 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error txBldr := authtxb.NewTxBuilderFromCLI() if viper.GetBool(flagValidateSigs) { - if !printAndValidateSigs(cliCtx, txBldr.ChainID, stdTx, offline) { + if !printAndValidateSigs(cliCtx, txBldr.GetChainID(), stdTx, offline) { return fmt.Errorf("signatures validation failed") } @@ -88,9 +98,25 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error } // if --signature-only is on, then override --append + var newTx auth.StdTx generateSignatureOnly := viper.GetBool(flagSigOnly) - appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly - newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, appendSig, offline) + multisigAddrStr := viper.GetString(flagMultisig) + + 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) + generateSignatureOnly = true + } else { + appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly + newTx, err = utils.SignStdTx( + txBldr, cliCtx, name, stdTx, appendSig, offline) + } if err != nil { return err } diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go index 0307db6c1afa..7f7787ea4298 100644 --- a/x/auth/client/rest/sign.go +++ b/x/auth/client/rest/sign.go @@ -1,53 +1,60 @@ package rest import ( - "io/ioutil" "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/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"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber uint64 `json:"account_number"` - Sequence uint64 `json:"sequence"` - AppendSig bool `json:"append_sig"` + Tx auth.StdTx `json:"tx"` + AppendSig bool `json:"append_sig"` + BaseReq utils.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 - body, err := ioutil.ReadAll(r.Body) - if err != nil { + if err := utils.ReadRESTReq(w, r, cdc, &m); err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + + if !m.BaseReq.ValidateBasic(w) { return } - txBldr := authtxb.TxBuilder{ - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, + // 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 { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } - signedTx, err := txBldr.SignStdTx(m.LocalAccountName, m.Password, m.Tx, m.AppendSig) + 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(m.BaseReq.Name, m.BaseReq.Password, m.Tx, m.AppendSig) if keyerror.IsErrKeyNotFound(err) { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index ce0753d1600c..ed5f4339d360 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -1,6 +1,8 @@ package context import ( + "strings" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,108 +14,178 @@ import ( // TxBuilder implements a transaction context created in SDK modules. type TxBuilder struct { - TxEncoder sdk.TxEncoder - AccountNumber uint64 - Sequence uint64 - Gas uint64 - GasAdjustment float64 - SimulateGas bool - ChainID string - Memo string - Fee string + txEncoder sdk.TxEncoder + accountNumber uint64 + sequence uint64 + gas uint64 + gasAdjustment float64 + simulateAndExecute bool + chainID string + memo string + fees sdk.Coins + gasPrices sdk.DecCoins +} + +// NewTxBuilder returns a new initialized TxBuilder. +func NewTxBuilder( + txEncoder sdk.TxEncoder, accNumber, seq, gas uint64, gasAdj float64, + simulateAndExecute bool, chainID, memo string, fees sdk.Coins, gasPrices sdk.DecCoins, +) TxBuilder { + + return TxBuilder{ + txEncoder: txEncoder, + accountNumber: accNumber, + sequence: seq, + gas: gas, + gasAdjustment: gasAdj, + simulateAndExecute: simulateAndExecute, + chainID: chainID, + memo: memo, + fees: fees, + gasPrices: gasPrices, + } } // NewTxBuilderFromCLI returns a new initialized TxBuilder with parameters from // the command line using Viper. func NewTxBuilderFromCLI() TxBuilder { - // if chain ID is not specified manually, read default chain ID - chainID := viper.GetString(client.FlagChainID) - if chainID == "" { - defaultChainID, err := sdk.DefaultChainID() - if err != nil { - chainID = defaultChainID - } + txbldr := TxBuilder{ + accountNumber: uint64(viper.GetInt64(client.FlagAccountNumber)), + sequence: uint64(viper.GetInt64(client.FlagSequence)), + gas: client.GasFlagVar.Gas, + gasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), + simulateAndExecute: client.GasFlagVar.Simulate, + chainID: viper.GetString(client.FlagChainID), + memo: viper.GetString(client.FlagMemo), } - return TxBuilder{ - ChainID: chainID, - AccountNumber: uint64(viper.GetInt64(client.FlagAccountNumber)), - Gas: client.GasFlagVar.Gas, - GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), - Sequence: uint64(viper.GetInt64(client.FlagSequence)), - SimulateGas: client.GasFlagVar.Simulate, - Fee: viper.GetString(client.FlagFee), - Memo: viper.GetString(client.FlagMemo), - } + txbldr = txbldr.WithFees(viper.GetString(client.FlagFees)) + txbldr = txbldr.WithGasPrices(viper.GetString(client.FlagGasPrices)) + + return txbldr } -// WithCodec returns a copy of the context with an updated codec. +// GetTxEncoder returns the transaction encoder +func (bldr TxBuilder) GetTxEncoder() sdk.TxEncoder { return bldr.txEncoder } + +// GetAccountNumber returns the account number +func (bldr TxBuilder) GetAccountNumber() uint64 { return bldr.accountNumber } + +// GetSequence returns the transaction sequence +func (bldr TxBuilder) GetSequence() uint64 { return bldr.sequence } + +// GetGas returns the gas for the transaction +func (bldr TxBuilder) GetGas() uint64 { return bldr.gas } + +// GetGasAdjustment returns the gas adjustment +func (bldr TxBuilder) GetGasAdjustment() float64 { return bldr.gasAdjustment } + +// GetSimulateAndExecute returns the option to simulate and then execute the transaction +// using the gas from the simulation results +func (bldr TxBuilder) GetSimulateAndExecute() bool { return bldr.simulateAndExecute } + +// GetChainID returns the chain id +func (bldr TxBuilder) GetChainID() string { return bldr.chainID } + +// GetMemo returns the memo message +func (bldr TxBuilder) GetMemo() string { return bldr.memo } + +// GetFees returns the fees for the transaction +func (bldr TxBuilder) GetFees() sdk.Coins { return bldr.fees } + +// GetGasPrices returns the gas prices set for the transaction, if any. +func (bldr TxBuilder) GetGasPrices() sdk.DecCoins { return bldr.gasPrices } + +// WithTxEncoder returns a copy of the context with an updated codec. func (bldr TxBuilder) WithTxEncoder(txEncoder sdk.TxEncoder) TxBuilder { - bldr.TxEncoder = txEncoder + bldr.txEncoder = txEncoder return bldr } // WithChainID returns a copy of the context with an updated chainID. func (bldr TxBuilder) WithChainID(chainID string) TxBuilder { - bldr.ChainID = chainID + bldr.chainID = chainID return bldr } // WithGas returns a copy of the context with an updated gas. func (bldr TxBuilder) WithGas(gas uint64) TxBuilder { - bldr.Gas = gas + bldr.gas = gas + return bldr +} + +// WithFees returns a copy of the context with an updated fee. +func (bldr TxBuilder) WithFees(fees string) TxBuilder { + parsedFees, err := sdk.ParseCoins(fees) + if err != nil { + panic(err) + } + + bldr.fees = parsedFees return bldr } -// WithFee returns a copy of the context with an updated fee. -func (bldr TxBuilder) WithFee(fee string) TxBuilder { - bldr.Fee = fee +// WithGasPrices returns a copy of the context with updated gas prices. +func (bldr TxBuilder) WithGasPrices(gasPrices string) TxBuilder { + parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) + if err != nil { + panic(err) + } + + bldr.gasPrices = parsedGasPrices return bldr } // WithSequence returns a copy of the context with an updated sequence number. func (bldr TxBuilder) WithSequence(sequence uint64) TxBuilder { - bldr.Sequence = sequence + bldr.sequence = sequence return bldr } // WithMemo returns a copy of the context with an updated memo. func (bldr TxBuilder) WithMemo(memo string) TxBuilder { - bldr.Memo = memo + bldr.memo = strings.TrimSpace(memo) return bldr } // WithAccountNumber returns a copy of the context with an account number. func (bldr TxBuilder) WithAccountNumber(accnum uint64) TxBuilder { - bldr.AccountNumber = accnum + bldr.accountNumber = accnum return bldr } // Build 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) Build(msgs []sdk.Msg) (StdSignMsg, error) { - chainID := bldr.ChainID + chainID := bldr.chainID if chainID == "" { return StdSignMsg{}, errors.Errorf("chain ID required but not specified") } - fee := sdk.Coin{} - if bldr.Fee != "" { - parsedFee, err := sdk.ParseCoin(bldr.Fee) - if err != nil { - return StdSignMsg{}, err + fees := bldr.fees + if !bldr.gasPrices.IsZero() { + if !fees.IsZero() { + return StdSignMsg{}, errors.New("cannot provide both fees and gas prices") } - fee = parsedFee + glDec := sdk.NewDec(int64(bldr.gas)) + + // Derive the fees based on the provided gas prices, where + // fee = ceil(gasPrice * gasLimit). + fees = make(sdk.Coins, len(bldr.gasPrices)) + for i, gp := range bldr.gasPrices { + fee := gp.Amount.Mul(glDec) + fees[i] = sdk.NewInt64Coin(gp.Denom, fee.Ceil().RoundInt64()) + } } return StdSignMsg{ - ChainID: bldr.ChainID, - AccountNumber: bldr.AccountNumber, - Sequence: bldr.Sequence, - Memo: bldr.Memo, + ChainID: bldr.chainID, + AccountNumber: bldr.accountNumber, + Sequence: bldr.sequence, + Memo: bldr.memo, Msgs: msgs, - Fee: auth.NewStdFee(bldr.Gas, fee), + Fee: auth.NewStdFee(bldr.gas, fees), }, nil } @@ -125,7 +197,7 @@ func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, err return nil, err } - return bldr.TxEncoder(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) + return bldr.txEncoder(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo)) } // BuildAndSign builds a single message to be signed, and signs a transaction @@ -164,16 +236,16 @@ func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, erro PubKey: info.GetPubKey(), }} - return bldr.TxEncoder(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) + return bldr.txEncoder(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) } // SignStdTx appends a signature to a StdTx and returns a copy of a it. If append // is false, it replaces the signatures already attached with the new signature. func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) { stdSignature, err := MakeSignature(name, passphrase, StdSignMsg{ - ChainID: bldr.ChainID, - AccountNumber: bldr.AccountNumber, - Sequence: bldr.Sequence, + ChainID: bldr.chainID, + AccountNumber: bldr.accountNumber, + Sequence: bldr.sequence, Fee: stdTx.Fee, Msgs: stdTx.GetMsgs(), Memo: stdTx.GetMemo(), diff --git a/x/auth/client/txbuilder/txbuilder_test.go b/x/auth/client/txbuilder/txbuilder_test.go index 1861c3e2af69..fd2eeefc3491 100644 --- a/x/auth/client/txbuilder/txbuilder_test.go +++ b/x/auth/client/txbuilder/txbuilder_test.go @@ -11,7 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -29,7 +29,8 @@ func TestTxBuilderBuild(t *testing.T) { SimulateGas bool ChainID string Memo string - Fee string + Fees sdk.Coins + GasPrices sdk.DecCoins } defaultMsg := []sdk.Msg{sdk.NewTestMsg(addr)} tests := []struct { @@ -43,37 +44,56 @@ func TestTxBuilderBuild(t *testing.T) { TxEncoder: auth.DefaultTxEncoder(codec.New()), AccountNumber: 1, Sequence: 1, - Gas: 100, + Gas: 200000, GasAdjustment: 1.1, SimulateGas: false, ChainID: "test-chain", - Memo: "hello", - Fee: "1" + stakeTypes.DefaultBondDenom, + Memo: "hello from Voyager 1!", + Fees: sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))}, }, defaultMsg, StdSignMsg{ ChainID: "test-chain", AccountNumber: 1, Sequence: 1, - Memo: "hello", + Memo: "hello from Voyager 1!", Msgs: defaultMsg, - Fee: auth.NewStdFee(100, sdk.NewCoin(stakeTypes.DefaultBondDenom, sdk.NewInt(1))), + Fee: auth.NewStdFee(200000, sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))}), + }, + false, + }, + { + fields{ + TxEncoder: auth.DefaultTxEncoder(codec.New()), + AccountNumber: 1, + Sequence: 1, + Gas: 200000, + GasAdjustment: 1.1, + SimulateGas: false, + ChainID: "test-chain", + Memo: "hello from Voyager 2!", + GasPrices: sdk.DecCoins{sdk.NewDecCoinFromDec(stakingtypes.DefaultBondDenom, sdk.NewDecWithPrec(10000, sdk.Precision))}, + }, + defaultMsg, + StdSignMsg{ + ChainID: "test-chain", + AccountNumber: 1, + Sequence: 1, + Memo: "hello from Voyager 2!", + Msgs: defaultMsg, + Fee: auth.NewStdFee(200000, sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))}), }, false, }, } + for i, tc := range tests { - bldr := TxBuilder{ - TxEncoder: tc.fields.TxEncoder, - AccountNumber: tc.fields.AccountNumber, - Sequence: tc.fields.Sequence, - Gas: tc.fields.Gas, - GasAdjustment: tc.fields.GasAdjustment, - SimulateGas: tc.fields.SimulateGas, - ChainID: tc.fields.ChainID, - Memo: tc.fields.Memo, - Fee: tc.fields.Fee, - } + bldr := NewTxBuilder( + tc.fields.TxEncoder, tc.fields.AccountNumber, tc.fields.Sequence, + tc.fields.Gas, tc.fields.GasAdjustment, tc.fields.SimulateGas, + tc.fields.ChainID, tc.fields.Memo, tc.fields.Fees, tc.fields.GasPrices, + ) + got, err := bldr.Build(tc.msgs) require.Equal(t, tc.wantErr, (err != nil), "TxBuilder.Build() error = %v, wantErr %v, tc %d", err, tc.wantErr, i) if !reflect.DeepEqual(got, tc.want) { diff --git a/x/auth/codec.go b/x/auth/codec.go index 624bdf4288db..0c8374d5ea7a 100644 --- a/x/auth/codec.go +++ b/x/auth/codec.go @@ -8,6 +8,10 @@ import ( func RegisterCodec(cdc *codec.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) + cdc.RegisterInterface((*VestingAccount)(nil), nil) + cdc.RegisterConcrete(&BaseVestingAccount{}, "auth/BaseVestingAccount", nil) + cdc.RegisterConcrete(&ContinuousVestingAccount{}, "auth/ContinuousVestingAccount", nil) + cdc.RegisterConcrete(&DelayedVestingAccount{}, "auth/DelayedVestingAccount", nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } diff --git a/x/auth/context.go b/x/auth/context.go deleted file mode 100644 index 28d159c97222..000000000000 --- a/x/auth/context.go +++ /dev/null @@ -1,48 +0,0 @@ -package auth - -import ( - "github.com/cosmos/cosmos-sdk/types" -) - -/* - -Usage: - -var accounts types.AccountKeeper - -// Fetch all signer accounts. -addrs := tx.GetSigners() -signers := make([]types.Account, len(addrs)) -for i, addr := range addrs { - acc := accounts.GetAccount(ctx) - signers[i] = acc -} -ctx = auth.SetSigners(ctx, signers) - -// Get all signer accounts. -signers := auth.GetSigners(ctx) -for i, signer := range signers { - signer.Address() == tx.GetSigners()[i] -} - -*/ - -type contextKey int // local to the auth module - -const ( - contextKeySigners contextKey = iota -) - -// add the signers to the context -func WithSigners(ctx types.Context, accounts []Account) types.Context { - return ctx.WithValue(contextKeySigners, accounts) -} - -// get the signers from the context -func GetSigners(ctx types.Context) []Account { - v := ctx.Value(contextKeySigners) - if v == nil { - return []Account{} - } - return v.([]Account) -} diff --git a/x/auth/context_test.go b/x/auth/context_test.go deleted file mode 100644 index b58547328d1a..000000000000 --- a/x/auth/context_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package auth - -import ( - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestContextWithSigners(t *testing.T) { - ms, _, _ := setupMultiStore() - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) - - _, _, addr1 := keyPubAddr() - _, _, addr2 := keyPubAddr() - acc1 := NewBaseAccountWithAddress(addr1) - acc1.SetSequence(7132) - acc2 := NewBaseAccountWithAddress(addr2) - acc2.SetSequence(8821) - - // new ctx has no signers - signers := GetSigners(ctx) - require.Equal(t, 0, len(signers)) - - ctx2 := WithSigners(ctx, []Account{&acc1, &acc2}) - - // original context is unchanged - signers = GetSigners(ctx) - require.Equal(t, 0, len(signers)) - - // new context has signers - signers = GetSigners(ctx2) - require.Equal(t, 2, len(signers)) - require.Equal(t, acc1, *(signers[0].(*BaseAccount))) - require.Equal(t, acc2, *(signers[1].(*BaseAccount))) -} diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index d481511617db..a624fb38c178 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -5,10 +5,6 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - - codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -19,57 +15,45 @@ var ( ) func TestFeeCollectionKeeperGetSet(t *testing.T) { - ms, _, capKey2 := setupMultiStore() - cdc := codec.New() - - // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - fck := NewFeeCollectionKeeper(cdc, capKey2) + input := setupTestInput() + ctx := input.ctx // no coins initially - currFees := fck.GetCollectedFees(ctx) + currFees := input.fck.GetCollectedFees(ctx) require.True(t, currFees.IsEqual(emptyCoins)) // set feeCollection to oneCoin - fck.setCollectedFees(ctx, oneCoin) + input.fck.setCollectedFees(ctx, oneCoin) // check that it is equal to oneCoin - require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(oneCoin)) } func TestFeeCollectionKeeperAdd(t *testing.T) { - ms, _, capKey2 := setupMultiStore() - cdc := codec.New() - - // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - fck := NewFeeCollectionKeeper(cdc, capKey2) + input := setupTestInput() + ctx := input.ctx // no coins initially - require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) // add oneCoin and check that pool is now oneCoin - fck.AddCollectedFees(ctx, oneCoin) - require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + input.fck.AddCollectedFees(ctx, oneCoin) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(oneCoin)) // add oneCoin again and check that pool is now twoCoins - fck.AddCollectedFees(ctx, oneCoin) - require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + input.fck.AddCollectedFees(ctx, oneCoin) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(twoCoins)) } func TestFeeCollectionKeeperClear(t *testing.T) { - ms, _, capKey2 := setupMultiStore() - cdc := codec.New() - - // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - fck := NewFeeCollectionKeeper(cdc, capKey2) + input := setupTestInput() + ctx := input.ctx // set coins initially - fck.setCollectedFees(ctx, twoCoins) - require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + input.fck.setCollectedFees(ctx, twoCoins) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(twoCoins)) // clear fees and see that pool is now empty - fck.ClearCollectedFees(ctx) - require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + input.fck.ClearCollectedFees(ctx) + require.True(t, input.fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) } diff --git a/x/auth/genesis.go b/x/auth/genesis.go index abc4fc3aea8f..fa9fa2d82a15 100644 --- a/x/auth/genesis.go +++ b/x/auth/genesis.go @@ -1,33 +1,62 @@ package auth import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) // GenesisState - all auth state that must be provided at genesis type GenesisState struct { CollectedFees sdk.Coins `json:"collected_fees"` // collected fees + Params Params `json:"params"` } // Create a new genesis state -func NewGenesisState(collectedFees sdk.Coins) GenesisState { +func NewGenesisState(collectedFees sdk.Coins, params Params) GenesisState { return GenesisState{ CollectedFees: collectedFees, + Params: params, } } // Return a default genesis state func DefaultGenesisState() GenesisState { - return NewGenesisState(sdk.Coins{}) + return NewGenesisState(sdk.Coins{}, DefaultParams()) } // Init store state from genesis data -func InitGenesis(ctx sdk.Context, keeper FeeCollectionKeeper, data GenesisState) { - keeper.setCollectedFees(ctx, data.CollectedFees) +func InitGenesis(ctx sdk.Context, ak AccountKeeper, fck FeeCollectionKeeper, data GenesisState) { + ak.SetParams(ctx, data.Params) + fck.setCollectedFees(ctx, data.CollectedFees) } // ExportGenesis returns a GenesisState for a given context and keeper -func ExportGenesis(ctx sdk.Context, keeper FeeCollectionKeeper) GenesisState { - collectedFees := keeper.GetCollectedFees(ctx) - return NewGenesisState(collectedFees) +func ExportGenesis(ctx sdk.Context, ak AccountKeeper, fck FeeCollectionKeeper) GenesisState { + collectedFees := fck.GetCollectedFees(ctx) + params := ak.GetParams(ctx) + + return NewGenesisState(collectedFees, params) +} + +// ValidateGenesis performs basic validation of auth genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + if data.Params.TxSigLimit == 0 { + return fmt.Errorf("invalid tx signature limit: %d", data.Params.TxSigLimit) + } + if data.Params.SigVerifyCostED25519 == 0 { + return fmt.Errorf("invalid ED25519 signature verification cost: %d", data.Params.SigVerifyCostED25519) + } + if data.Params.SigVerifyCostSecp256k1 == 0 { + return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", data.Params.SigVerifyCostSecp256k1) + } + if data.Params.MaxMemoCharacters == 0 { + return fmt.Errorf("invalid max memo characters: %d", data.Params.MaxMemoCharacters) + } + if data.Params.MemoCostPerByte == 0 { + return fmt.Errorf("invalid memo cost per byte: %d", data.Params.MemoCostPerByte) + } + + return nil } diff --git a/x/auth/keeper.go b/x/auth/keeper.go index bf8b92da603b..f2cf86820611 100644 --- a/x/auth/keeper.go +++ b/x/auth/keeper.go @@ -5,6 +5,7 @@ import ( codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" ) var ( @@ -12,12 +13,17 @@ var ( AddressStoreKeyPrefix = []byte{0x01} globalAccountNumberKey = []byte("globalAccountNumber") + + // 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" ) -// This AccountKeeper encodes/decodes accounts using the -// go-amino (binary) encoding/decoding library. +// This AccountKeeper encodes/decodes accounts using the go-amino (binary) +// encoding/decoding library. type AccountKeeper struct { - // The (unexposed) key used to access the store from the Context. key sdk.StoreKey @@ -26,28 +32,34 @@ type AccountKeeper struct { // The codec codec for binary encoding/decoding of accounts. cdc *codec.Codec + + paramSubspace params.Subspace } -// NewAccountKeeper returns a new sdk.AccountKeeper that -// uses go-amino to (binary) encode and decode concrete sdk.Accounts. +// NewAccountKeeper returns a new sdk.AccountKeeper that uses go-amino to +// (binary) encode and decode concrete sdk.Accounts. // nolint -func NewAccountKeeper(cdc *codec.Codec, key sdk.StoreKey, proto func() Account) AccountKeeper { +func NewAccountKeeper( + cdc *codec.Codec, key sdk.StoreKey, paramstore params.Subspace, proto func() Account, +) AccountKeeper { + return AccountKeeper{ - key: key, - proto: proto, - cdc: cdc, + key: key, + proto: proto, + cdc: cdc, + paramSubspace: paramstore.WithTypeTable(ParamTypeTable()), } } // Implaements sdk.AccountKeeper. -func (am AccountKeeper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) Account { - acc := am.proto() +func (ak AccountKeeper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) Account { + acc := ak.proto() err := acc.SetAddress(addr) if err != nil { // Handle w/ #870 panic(err) } - err = acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) + err = acc.SetAccountNumber(ak.GetNextAccountNumber(ctx)) if err != nil { // Handle w/ #870 panic(err) @@ -56,8 +68,8 @@ func (am AccountKeeper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddre } // New Account -func (am AccountKeeper) NewAccount(ctx sdk.Context, acc Account) Account { - err := acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) +func (ak AccountKeeper) NewAccount(ctx sdk.Context, acc Account) Account { + err := acc.SetAccountNumber(ak.GetNextAccountNumber(ctx)) if err != nil { // TODO: Handle with #870 panic(err) @@ -71,34 +83,34 @@ func AddressStoreKey(addr sdk.AccAddress) []byte { } // Implements sdk.AccountKeeper. -func (am AccountKeeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account { - store := ctx.KVStore(am.key) +func (ak AccountKeeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) Account { + store := ctx.KVStore(ak.key) bz := store.Get(AddressStoreKey(addr)) if bz == nil { return nil } - acc := am.decodeAccount(bz) + acc := ak.decodeAccount(bz) return acc } // Implements sdk.AccountKeeper. -func (am AccountKeeper) SetAccount(ctx sdk.Context, acc Account) { +func (ak AccountKeeper) SetAccount(ctx sdk.Context, acc Account) { addr := acc.GetAddress() - store := ctx.KVStore(am.key) - bz := am.encodeAccount(acc) + store := ctx.KVStore(ak.key) + bz := ak.encodeAccount(acc) store.Set(AddressStoreKey(addr), bz) } // RemoveAccount removes an account for the account mapper store. -func (am AccountKeeper) RemoveAccount(ctx sdk.Context, acc Account) { +func (ak AccountKeeper) RemoveAccount(ctx sdk.Context, acc Account) { addr := acc.GetAddress() - store := ctx.KVStore(am.key) + store := ctx.KVStore(ak.key) store.Delete(AddressStoreKey(addr)) } // Implements sdk.AccountKeeper. -func (am AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) { - store := ctx.KVStore(am.key) +func (ak AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) { + store := ctx.KVStore(ak.key) iter := sdk.KVStorePrefixIterator(store, AddressStoreKeyPrefix) defer iter.Close() for { @@ -106,7 +118,7 @@ func (am AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) ( return } val := iter.Value() - acc := am.decodeAccount(val) + acc := ak.decodeAccount(val) if process(acc) { return } @@ -115,8 +127,8 @@ func (am AccountKeeper) IterateAccounts(ctx sdk.Context, process func(Account) ( } // Returns the PubKey of the account at address -func (am AccountKeeper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, sdk.Error) { - acc := am.GetAccount(ctx, addr) +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()) } @@ -124,16 +136,16 @@ func (am AccountKeeper) GetPubKey(ctx sdk.Context, addr sdk.AccAddress) (crypto. } // Returns the Sequence of the account at address -func (am AccountKeeper) GetSequence(ctx sdk.Context, addr sdk.AccAddress) (uint64, sdk.Error) { - acc := am.GetAccount(ctx, addr) +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 acc.GetSequence(), nil } -func (am AccountKeeper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSequence uint64) sdk.Error { - acc := am.GetAccount(ctx, addr) +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()) } @@ -142,43 +154,57 @@ func (am AccountKeeper) setSequence(ctx sdk.Context, addr sdk.AccAddress, newSeq // Handle w/ #870 panic(err) } - am.SetAccount(ctx, acc) + ak.SetAccount(ctx, acc) return nil } // Returns and increments the global account number counter -func (am AccountKeeper) GetNextAccountNumber(ctx sdk.Context) uint64 { +func (ak AccountKeeper) GetNextAccountNumber(ctx sdk.Context) uint64 { var accNumber uint64 - store := ctx.KVStore(am.key) + store := ctx.KVStore(ak.key) bz := store.Get(globalAccountNumberKey) if bz == nil { accNumber = 0 } else { - err := am.cdc.UnmarshalBinaryLengthPrefixed(bz, &accNumber) + err := ak.cdc.UnmarshalBinaryLengthPrefixed(bz, &accNumber) if err != nil { panic(err) } } - bz = am.cdc.MustMarshalBinaryLengthPrefixed(accNumber + 1) + bz = ak.cdc.MustMarshalBinaryLengthPrefixed(accNumber + 1) store.Set(globalAccountNumberKey, bz) return accNumber } -//---------------------------------------- -// misc. +//----------------------------------------------------------------------------- +// Params + +// SetParams sets the auth module's parameters. +func (ak AccountKeeper) SetParams(ctx sdk.Context, params Params) { + ak.paramSubspace.SetParamSet(ctx, ¶ms) +} + +// GetParams gets the auth module's parameters. +func (ak AccountKeeper) GetParams(ctx sdk.Context) (params Params) { + ak.paramSubspace.GetParamSet(ctx, ¶ms) + return +} + +//----------------------------------------------------------------------------- +// Misc. -func (am AccountKeeper) encodeAccount(acc Account) []byte { - bz, err := am.cdc.MarshalBinaryBare(acc) +func (ak AccountKeeper) encodeAccount(acc Account) []byte { + bz, err := ak.cdc.MarshalBinaryBare(acc) if err != nil { panic(err) } return bz } -func (am AccountKeeper) decodeAccount(bz []byte) (acc Account) { - err := am.cdc.UnmarshalBinaryBare(bz, &acc) +func (ak AccountKeeper) decodeAccount(bz []byte) (acc Account) { + err := ak.cdc.UnmarshalBinaryBare(bz, &acc) if err != nil { panic(err) } diff --git a/x/auth/keeper_bench_test.go b/x/auth/keeper_bench_test.go index 413cd6afdf6e..8a3ae6da59ff 100644 --- a/x/auth/keeper_bench_test.go +++ b/x/auth/keeper_bench_test.go @@ -3,46 +3,29 @@ package auth import ( "testing" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) func BenchmarkAccountMapperGetAccountFound(b *testing.B) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + input := setupTestInput() // assumes b.N < 2**24 for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} addr := sdk.AccAddress(arr) - acc := mapper.NewAccountWithAddress(ctx, addr) - mapper.SetAccount(ctx, acc) + acc := input.ak.NewAccountWithAddress(input.ctx, addr) + input.ak.SetAccount(input.ctx, acc) } b.ResetTimer() for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} - mapper.GetAccount(ctx, sdk.AccAddress(arr)) + input.ak.GetAccount(input.ctx, sdk.AccAddress(arr)) } } func BenchmarkAccountMapperGetAccountFoundWithCoins(b *testing.B) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - + input := setupTestInput() coins := sdk.Coins{ sdk.NewCoin("LTC", sdk.NewInt(1000)), sdk.NewCoin("BTC", sdk.NewInt(1000)), @@ -56,46 +39,34 @@ func BenchmarkAccountMapperGetAccountFoundWithCoins(b *testing.B) { for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} addr := sdk.AccAddress(arr) - acc := mapper.NewAccountWithAddress(ctx, addr) + acc := input.ak.NewAccountWithAddress(input.ctx, addr) acc.SetCoins(coins) - mapper.SetAccount(ctx, acc) + input.ak.SetAccount(input.ctx, acc) } b.ResetTimer() for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} - mapper.GetAccount(ctx, sdk.AccAddress(arr)) + input.ak.GetAccount(input.ctx, sdk.AccAddress(arr)) } } func BenchmarkAccountMapperSetAccount(b *testing.B) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + input := setupTestInput() b.ResetTimer() + // assumes b.N < 2**24 for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} addr := sdk.AccAddress(arr) - acc := mapper.NewAccountWithAddress(ctx, addr) - mapper.SetAccount(ctx, acc) + acc := input.ak.NewAccountWithAddress(input.ctx, addr) + input.ak.SetAccount(input.ctx, acc) } } func BenchmarkAccountMapperSetAccountWithCoins(b *testing.B) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - + input := setupTestInput() coins := sdk.Coins{ sdk.NewCoin("LTC", sdk.NewInt(1000)), sdk.NewCoin("BTC", sdk.NewInt(1000)), @@ -106,12 +77,13 @@ func BenchmarkAccountMapperSetAccountWithCoins(b *testing.B) { } b.ResetTimer() + // assumes b.N < 2**24 for i := 0; i < b.N; i++ { arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} addr := sdk.AccAddress(arr) - acc := mapper.NewAccountWithAddress(ctx, addr) + acc := input.ak.NewAccountWithAddress(input.ctx, addr) acc.SetCoins(coins) - mapper.SetAccount(ctx, acc) + input.ak.SetAccount(input.ctx, acc) } } diff --git a/x/auth/keeper_test.go b/x/auth/keeper_test.go index ad05c0f8ed1f..4285132cb69e 100644 --- a/x/auth/keeper_test.go +++ b/x/auth/keeper_test.go @@ -4,96 +4,87 @@ import ( "testing" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - codec "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { - db := dbm.NewMemDB() - capKey := sdk.NewKVStoreKey("capkey") - capKey2 := sdk.NewKVStoreKey("capkey2") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms, capKey, capKey2 -} - func TestAccountMapperGetSet(t *testing.T) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - + input := setupTestInput() addr := sdk.AccAddress([]byte("some-address")) // no account before its created - acc := mapper.GetAccount(ctx, addr) + acc := input.ak.GetAccount(input.ctx, addr) require.Nil(t, acc) // create account and check default values - acc = mapper.NewAccountWithAddress(ctx, addr) + acc = input.ak.NewAccountWithAddress(input.ctx, addr) require.NotNil(t, acc) require.Equal(t, addr, acc.GetAddress()) require.EqualValues(t, nil, acc.GetPubKey()) require.EqualValues(t, 0, acc.GetSequence()) // NewAccount doesn't call Set, so it's still nil - require.Nil(t, mapper.GetAccount(ctx, addr)) + require.Nil(t, input.ak.GetAccount(input.ctx, addr)) // set some values on the account and save it newSequence := uint64(20) acc.SetSequence(newSequence) - mapper.SetAccount(ctx, acc) + input.ak.SetAccount(input.ctx, acc) // check the new values - acc = mapper.GetAccount(ctx, addr) + acc = input.ak.GetAccount(input.ctx, addr) require.NotNil(t, acc) require.Equal(t, newSequence, acc.GetSequence()) } func TestAccountMapperRemoveAccount(t *testing.T) { - ms, capKey, _ := setupMultiStore() - cdc := codec.New() - RegisterBaseAccount(cdc) - - // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) - + input := setupTestInput() addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) // create accounts - acc1 := mapper.NewAccountWithAddress(ctx, addr1) - acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc1 := input.ak.NewAccountWithAddress(input.ctx, addr1) + acc2 := input.ak.NewAccountWithAddress(input.ctx, addr2) accSeq1 := uint64(20) accSeq2 := uint64(40) acc1.SetSequence(accSeq1) acc2.SetSequence(accSeq2) - mapper.SetAccount(ctx, acc1) - mapper.SetAccount(ctx, acc2) + input.ak.SetAccount(input.ctx, acc1) + input.ak.SetAccount(input.ctx, acc2) - acc1 = mapper.GetAccount(ctx, addr1) + acc1 = input.ak.GetAccount(input.ctx, addr1) require.NotNil(t, acc1) require.Equal(t, accSeq1, acc1.GetSequence()) // remove one account - mapper.RemoveAccount(ctx, acc1) - acc1 = mapper.GetAccount(ctx, addr1) + input.ak.RemoveAccount(input.ctx, acc1) + acc1 = input.ak.GetAccount(input.ctx, addr1) require.Nil(t, acc1) - acc2 = mapper.GetAccount(ctx, addr2) + acc2 = input.ak.GetAccount(input.ctx, addr2) require.NotNil(t, acc2) require.Equal(t, accSeq2, acc2.GetSequence()) } + +func TestSetParams(t *testing.T) { + input := setupTestInput() + params := DefaultParams() + + input.ak.SetParams(input.ctx, params) + + newParams := Params{} + input.ak.paramSubspace.Get(input.ctx, KeyTxSigLimit, &newParams.TxSigLimit) + require.Equal(t, newParams.TxSigLimit, DefaultTxSigLimit) +} + +func TestGetParams(t *testing.T) { + input := setupTestInput() + params := DefaultParams() + + input.ak.SetParams(input.ctx, params) + + newParams := input.ak.GetParams(input.ctx) + require.Equal(t, params, newParams) +} diff --git a/x/auth/params.go b/x/auth/params.go new file mode 100644 index 000000000000..9fcde581cdf1 --- /dev/null +++ b/x/auth/params.go @@ -0,0 +1,115 @@ +package auth + +import ( + "bytes" + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// DefaultParamspace defines the default auth module parameter subspace +const DefaultParamspace = "auth" + +// Default parameter values +const ( + DefaultMemoCostPerByte sdk.Gas = 3 + DefaultMaxMemoCharacters uint64 = 256 + DefaultTxSigLimit uint64 = 7 + DefaultSigVerifyCostED25519 uint64 = 590 + DefaultSigVerifyCostSecp256k1 uint64 = 1000 +) + +// Parameter keys +var ( + KeyMemoCostPerByte = []byte("MemoCostPerByte") + KeyMaxMemoCharacters = []byte("MaxMemoCharacters") + KeyTxSigLimit = []byte("TxSigLimit") + KeySigVerifyCostED25519 = []byte("SigVerifyCostED25519") + KeySigVerifyCostSecp256k1 = []byte("SigVerifyCostSecp256k1") +) + +var _ params.ParamSet = &Params{} + +// Params defines the parameters for the auth module. +type Params struct { + MemoCostPerByte sdk.Gas + MaxMemoCharacters uint64 + TxSigLimit uint64 // max total number of signatures per tx + SigVerifyCostED25519 uint64 + SigVerifyCostSecp256k1 uint64 +} + +// ParamTable for staking module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable().RegisterParamSet(&Params{}) +} + +// KeyValuePairs implements the ParamSet interface and returns all the key/value +// pairs of auth module's parameters. +// nolint +func (p *Params) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {KeyMemoCostPerByte, &p.MemoCostPerByte}, + {KeyMaxMemoCharacters, &p.MaxMemoCharacters}, + {KeyTxSigLimit, &p.TxSigLimit}, + {KeySigVerifyCostED25519, &p.SigVerifyCostED25519}, + {KeySigVerifyCostSecp256k1, &p.SigVerifyCostSecp256k1}, + } +} + +// Equal returns a boolean determining if two Params types are identical. +func (p Params) Equal(p2 Params) bool { + bz1 := msgCdc.MustMarshalBinaryLengthPrefixed(&p) + bz2 := msgCdc.MustMarshalBinaryLengthPrefixed(&p2) + return bytes.Equal(bz1, bz2) +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return Params{ + MemoCostPerByte: DefaultMemoCostPerByte, + MaxMemoCharacters: DefaultMaxMemoCharacters, + TxSigLimit: DefaultTxSigLimit, + SigVerifyCostED25519: DefaultSigVerifyCostED25519, + SigVerifyCostSecp256k1: DefaultSigVerifyCostSecp256k1, + } +} + +// String implements the stringer interface. +func (p Params) String() string { + var sb strings.Builder + + sb.WriteString("Params: \n") + sb.WriteString(fmt.Sprintf("MemoCostPerByte: %v\n", p.MemoCostPerByte)) + sb.WriteString(fmt.Sprintf("MaxMemoCharacters: %d\n", p.MaxMemoCharacters)) + sb.WriteString(fmt.Sprintf("TxSigLimit: %d\n", p.TxSigLimit)) + sb.WriteString(fmt.Sprintf("SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519)) + sb.WriteString(fmt.Sprintf("SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1)) + + return sb.String() +} + +// MustUnmarshalParams deserializes raw Params bytes into a Params structure. It +// will panic upon failure. +func MustUnmarshalParams(cdc *codec.Codec, value []byte) Params { + params, err := UnmarshalParams(cdc, value) + if err != nil { + panic(err) + } + + return params +} + +// UnmarshalParams deserializes raw Params bytes into a Params structure. It will +// return an error upon failure. +func UnmarshalParams(cdc *codec.Codec, value []byte) (params Params, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(value, ¶ms) + if err != nil { + return Params{}, err + } + + return +} diff --git a/x/auth/params_test.go b/x/auth/params_test.go new file mode 100644 index 000000000000..67699c4304db --- /dev/null +++ b/x/auth/params_test.go @@ -0,0 +1,16 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsEqual(t *testing.T) { + p1 := DefaultParams() + p2 := DefaultParams() + require.Equal(t, p1, p2) + + p1.TxSigLimit += 10 + require.NotEqual(t, p1, p2) +} diff --git a/x/auth/simulation/fake.go b/x/auth/simulation/fake.go index 222b747ec4a0..c037d74c284e 100644 --- a/x/auth/simulation/fake.go +++ b/x/auth/simulation/fake.go @@ -28,24 +28,36 @@ func SimulateDeductFee(m auth.AccountKeeper, f auth.FeeCollectionKeeper) simulat } denomIndex := r.Intn(len(initCoins)) - amt, err := randPositiveInt(r, initCoins[denomIndex].Amount) + randCoin := initCoins[denomIndex] + + amt, err := randPositiveInt(r, randCoin.Amount) if err != nil { event(fmt.Sprintf("auth/SimulateDeductFee/false")) return action, nil, nil } - coins := sdk.Coins{sdk.NewCoin(initCoins[denomIndex].Denom, amt)} - err = stored.SetCoins(initCoins.Minus(coins)) - if err != nil { - panic(err) + // Create a random fee and verify the fees are within the account's spendable + // balance. + fees := sdk.Coins{sdk.NewCoin(randCoin.Denom, amt)} + spendableCoins := stored.SpendableCoins(ctx.BlockHeader().Time) + if _, hasNeg := spendableCoins.SafeMinus(fees); hasNeg { + event(fmt.Sprintf("auth/SimulateDeductFee/false")) + return action, nil, nil } - m.SetAccount(ctx, stored) - if !coins.IsNotNegative() { - panic("setting negative fees") + + // get the new account balance + newCoins, hasNeg := initCoins.SafeMinus(fees) + if hasNeg { + event(fmt.Sprintf("auth/SimulateDeductFee/false")) + return action, nil, nil } - f.AddCollectedFees(ctx, coins) + if err := stored.SetCoins(newCoins); err != nil { + panic(err) + } + m.SetAccount(ctx, stored) + f.AddCollectedFees(ctx, fees) event(fmt.Sprintf("auth/SimulateDeductFee/true")) action = "TestDeductFee" diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 79abfce0a094..f13e77457e0f 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -50,26 +50,18 @@ func (tx StdTx) ValidateBasic() sdk.Error { return sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee %s amount provided", tx.Fee.Amount)) } if len(stdSigs) == 0 { - return sdk.ErrUnauthorized("no signers") + return sdk.ErrNoSignatures("no signers") } if len(stdSigs) != len(tx.GetSigners()) { return sdk.ErrUnauthorized("wrong number of signers") } - if len(tx.GetMemo()) > maxMemoCharacters { - return sdk.ErrMemoTooLarge( - fmt.Sprintf( - "maximum number of characters is %d but received %d characters", - maxMemoCharacters, len(tx.GetMemo()), - ), - ) - } sigCount := 0 for i := 0; i < len(stdSigs); i++ { sigCount += countSubKeys(stdSigs[i].PubKey) - if sigCount > txSigLimit { + if uint64(sigCount) > DefaultTxSigLimit { return sdk.ErrTooManySignatures( - fmt.Sprintf("signatures: %d, limit: %d", sigCount, txSigLimit), + fmt.Sprintf("signatures: %d, limit: %d", sigCount, DefaultTxSigLimit), ) } } @@ -77,8 +69,9 @@ func (tx StdTx) ValidateBasic() sdk.Error { return nil } +// countSubKeys counts the total number of keys for a multi-sig public key. func countSubKeys(pub crypto.PubKey) int { - v, ok := pub.(*multisig.PubKeyMultisigThreshold) + v, ok := pub.(multisig.PubKeyMultisigThreshold) if !ok { return 1 } @@ -133,7 +126,7 @@ type StdFee struct { Gas uint64 `json:"gas"` } -func NewStdFee(gas uint64, amount ...sdk.Coin) StdFee { +func NewStdFee(gas uint64, amount sdk.Coins) StdFee { return StdFee{ Amount: amount, Gas: gas, diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index f89e2b8cfec8..a1fbf4a34ba4 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -2,8 +2,6 @@ package auth import ( "fmt" - "github.com/cosmos/cosmos-sdk/codec" - "strings" "testing" "github.com/stretchr/testify/require" @@ -12,6 +10,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -62,14 +61,14 @@ func TestTxValidateBasic(t *testing.T) { ctx := sdk.NewContext(nil, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) // keys and addresses - priv1, addr1 := privAndAddr() - priv2, addr2 := privAndAddr() - priv3, addr3 := privAndAddr() - priv4, addr4 := privAndAddr() - priv5, addr5 := privAndAddr() - priv6, addr6 := privAndAddr() - priv7, addr7 := privAndAddr() - priv8, addr8 := privAndAddr() + priv1, _, addr1 := keyPubAddr() + priv2, _, addr2 := keyPubAddr() + priv3, _, addr3 := keyPubAddr() + priv4, _, addr4 := keyPubAddr() + priv5, _, addr5 := keyPubAddr() + priv6, _, addr6 := keyPubAddr() + priv7, _, addr7 := keyPubAddr() + priv8, _, addr8 := keyPubAddr() // msg and signatures msg1 := newTestMsg(addr1, addr2) @@ -92,7 +91,7 @@ func TestTxValidateBasic(t *testing.T) { err = tx.ValidateBasic() require.Error(t, err) - require.Equal(t, sdk.CodeUnauthorized, err.Result().Code) + require.Equal(t, sdk.CodeNoSignatures, err.Result().Code) // require to fail validation when signatures do not match expected signers privs, accNums, seqs = []crypto.PrivKey{priv1}, []uint64{0, 1}, []uint64{0, 0} @@ -102,15 +101,6 @@ func TestTxValidateBasic(t *testing.T) { require.Error(t, err) require.Equal(t, sdk.CodeUnauthorized, err.Result().Code) - // require to fail validation when memo is too large - badMemo := strings.Repeat("bad memo", 50) - privs, accNums, seqs = []crypto.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0} - tx = newTestTxWithMemo(ctx, msgs, privs, accNums, seqs, fee, badMemo) - - err = tx.ValidateBasic() - require.Error(t, err) - require.Equal(t, sdk.CodeMemoTooLarge, err.Result().Code) - // require to fail validation when there are too many signatures privs = []crypto.PrivKey{priv1, priv2, priv3, priv4, priv5, priv6, priv7, priv8} accNums, seqs = []uint64{0, 0, 0, 0, 0, 0, 0, 0}, []uint64{0, 0, 0, 0, 0, 0, 0, 0} diff --git a/x/auth/test_utils.go b/x/auth/test_utils.go new file mode 100644 index 000000000000..096b3897a238 --- /dev/null +++ b/x/auth/test_utils.go @@ -0,0 +1,123 @@ +// nolint +package auth + +import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + 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/params" +) + +type testInput struct { + cdc *codec.Codec + ctx sdk.Context + ak AccountKeeper + fck FeeCollectionKeeper +} + +func setupTestInput() testInput { + db := dbm.NewMemDB() + + cdc := codec.New() + RegisterBaseAccount(cdc) + + authCapKey := sdk.NewKVStoreKey("authCapKey") + fckCapKey := sdk.NewKVStoreKey("fckCapKey") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(fckCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + ms.LoadLatestVersion() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + ak := NewAccountKeeper(cdc, authCapKey, pk.Subspace(DefaultParamspace), ProtoBaseAccount) + fck := NewFeeCollectionKeeper(cdc, fckCapKey) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger()) + + ak.SetParams(ctx, DefaultParams()) + + return testInput{cdc: cdc, ctx: ctx, ak: ak, fck: fck} +} + +func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { + return sdk.NewTestMsg(addrs...) +} + +func newStdFee() StdFee { + return NewStdFee(50000, + sdk.Coins{sdk.NewInt64Coin("atom", 150)}, + ) +} + +// coins to more than cover the fee +func newCoins() sdk.Coins { + return sdk.Coins{ + sdk.NewInt64Coin("atom", 10000000), + } +} + +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := ed25519.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} + +func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} + } + + tx := NewStdTx(msgs, fee, sigs, "") + return tx +} + +func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) + + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} + } + + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} + +func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, signBytes []byte, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig} + } + + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 42796d64354c..36e084b73a86 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -43,7 +43,7 @@ var ( coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} halfCoins = sdk.Coins{sdk.NewInt64Coin("foocoin", 5)} manyCoins = sdk.Coins{sdk.NewInt64Coin("foocoin", 1), sdk.NewInt64Coin("barcoin", 1)} - freeFee = auth.NewStdFee(100000, sdk.Coins{sdk.NewInt64Coin("foocoin", 0)}...) + freeFee = auth.NewStdFee(100000, sdk.Coins{sdk.NewInt64Coin("foocoin", 0)}) sendMsg1 = MsgSend{ Inputs: []Input{NewInput(addr1, coins)}, diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 27cbc043c751..d73d7e61e3cb 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -49,21 +49,18 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } msg := client.CreateMsg(sdk.AccAddress(info.GetPubKey().Address()), to, req.Amount) - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/bank/handler.go b/x/bank/handler.go index ea3ee4398c43..9b4095700050 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -20,7 +20,6 @@ func NewHandler(k Keeper) sdk.Handler { // Handle MsgSend. func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { // NOTE: totalIn == totalOut should already have been checked - tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) if err != nil { return err.Result() diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 1a11631ca652..499b43e59499 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -2,6 +2,7 @@ package bank import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -21,6 +22,9 @@ type Keeper interface { SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) + + DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) + UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) } // BaseKeeper manages transfers between accounts. It implements the Keeper @@ -68,6 +72,20 @@ func (keeper BaseKeeper) InputOutputCoins( return inputOutputCoins(ctx, keeper.ak, inputs, outputs) } +// 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) { + 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) { + return undelegateCoins(ctx, keeper.ak, addr, amt) +} + //----------------------------------------------------------------------------- // Send Keeper @@ -140,6 +158,7 @@ func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt } //----------------------------------------------------------------------------- +// Auxiliary func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.Coins { acc := am.GetAccount(ctx, addr) @@ -168,16 +187,37 @@ func hasCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s return getCoins(ctx, am, addr).IsAllGTE(amt) } -// SubtractCoins subtracts amt from the coins at the addr. -func subtractCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { - oldCoins := getCoins(ctx, am, addr) - newCoins, hasNeg := oldCoins.SafeMinus(amt) +func getAccount(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress) auth.Account { + return ak.GetAccount(ctx, addr) +} + +func setAccount(ctx sdk.Context, ak auth.AccountKeeper, acc auth.Account) { + ak.SetAccount(ctx, acc) +} + +// subtractCoins subtracts amt coins from an account with the given address addr. +// +// 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) { + oldCoins, spendableCoins := sdk.Coins{}, sdk.Coins{} + + acc := getAccount(ctx, ak, addr) + if acc != nil { + oldCoins = acc.GetCoins() + spendableCoins = acc.SpendableCoins(ctx.BlockHeader().Time) + } + + // 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) if hasNeg { - return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", spendableCoins, amt)) } - err := setCoins(ctx, am, addr, newCoins) - tags := sdk.NewTags("sender", []byte(addr.String())) + newCoins := oldCoins.Minus(amt) // should not panic as spendable coins was already checked + err := setCoins(ctx, ak, addr, newCoins) + tags := sdk.NewTags(TagKeySender, []byte(addr.String())) + return newCoins, tags, err } @@ -185,11 +225,14 @@ func subtractCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Plus(amt) + if !newCoins.IsNotNegative() { return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } + err := setCoins(ctx, am, addr, newCoins) - tags := sdk.NewTags("recipient", []byte(addr.String())) + tags := sdk.NewTags(TagKeyRecipient, []byte(addr.String())) + return newCoins, tags, err } @@ -243,3 +286,72 @@ func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, ou return allTags, nil } + +func delegateCoins( + ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins, +) (sdk.Tags, sdk.Error) { + + acc := getAccount(ctx, ak, addr) + if acc == nil { + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) + } + + oldCoins := acc.GetCoins() + + _, hasNeg := oldCoins.SafeMinus(amt) + if hasNeg { + return nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + } + + if err := trackDelegation(acc, ctx.BlockHeader().Time, amt); err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to track delegation: %v", err)) + } + + setAccount(ctx, ak, acc) + + return sdk.NewTags( + sdk.TagAction, TagActionDelegateCoins, + sdk.TagDelegator, []byte(addr.String()), + ), nil +} + +func undelegateCoins( + ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins, +) (sdk.Tags, sdk.Error) { + + acc := getAccount(ctx, ak, addr) + if acc == nil { + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)) + } + + if err := trackUndelegation(acc, amt); err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to track undelegation: %v", err)) + } + + setAccount(ctx, ak, acc) + + return sdk.NewTags( + sdk.TagAction, TagActionUndelegateCoins, + sdk.TagDelegator, []byte(addr.String()), + ), nil +} + +func trackDelegation(acc auth.Account, blockTime time.Time, amount sdk.Coins) error { + vacc, ok := acc.(auth.VestingAccount) + if ok { + vacc.TrackDelegation(blockTime, amount) + return nil + } + + return acc.SetCoins(acc.GetCoins().Minus(amount)) +} + +func trackUndelegation(acc auth.Account, amount sdk.Coins) error { + vacc, ok := acc.(auth.VestingAccount) + if ok { + vacc.TrackUndelegation(amount) + return nil + } + + return acc.SetCoins(acc.GetCoins().Plus(amount)) +} diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 45630383174c..8a3d4dc83882 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -2,46 +2,68 @@ package bank import ( "testing" + "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + tmtime "github.com/tendermint/tendermint/types/time" codec "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/params" ) -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { - db := dbm.NewMemDB() - authKey := sdk.NewKVStoreKey("authkey") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms, authKey +type testInput struct { + cdc *codec.Codec + ctx sdk.Context + ak auth.AccountKeeper } -func TestKeeper(t *testing.T) { - ms, authKey := setupMultiStore() +func setupTestInput() testInput { + db := dbm.NewMemDB() cdc := codec.New() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountKeeper) + authCapKey := sdk.NewKVStoreKey("authCapKey") + fckCapKey := sdk.NewKVStoreKey("fckCapKey") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(fckCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + ms.LoadLatestVersion() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + ak := auth.NewAccountKeeper( + cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, + ) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger()) + + ak.SetParams(ctx, auth.DefaultParams()) + + return testInput{cdc: cdc, ctx: ctx, ak: ak} +} + +func TestKeeper(t *testing.T) { + input := setupTestInput() + ctx := input.ctx + bankKeeper := NewBaseKeeper(input.ak) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) addr3 := sdk.AccAddress([]byte("addr3")) - acc := accountKeeper.NewAccountWithAddress(ctx, addr) + acc := input.ak.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountKeeper.SetAccount(ctx, acc) + input.ak.SetAccount(ctx, acc) require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) @@ -78,7 +100,7 @@ func TestKeeper(t *testing.T) { require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) _, err2 := bankKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 50)}) - assert.Implements(t, (*sdk.Error)(nil), err2) + require.Implements(t, (*sdk.Error)(nil), err2) require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) @@ -110,22 +132,17 @@ func TestKeeper(t *testing.T) { } func TestSendKeeper(t *testing.T) { - ms, authKey := setupMultiStore() - - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountKeeper) - sendKeeper := NewBaseSendKeeper(accountKeeper) + input := setupTestInput() + ctx := input.ctx + bankKeeper := NewBaseKeeper(input.ak) + sendKeeper := NewBaseSendKeeper(input.ak) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) - acc := accountKeeper.NewAccountWithAddress(ctx, addr) + acc := input.ak.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountKeeper.SetAccount(ctx, acc) + input.ak.SetAccount(ctx, acc) require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) @@ -162,21 +179,16 @@ func TestSendKeeper(t *testing.T) { } func TestViewKeeper(t *testing.T) { - ms, authKey := setupMultiStore() - - cdc := codec.New() - auth.RegisterBaseAccount(cdc) - - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) - bankKeeper := NewBaseKeeper(accountKeeper) - viewKeeper := NewBaseViewKeeper(accountKeeper) + input := setupTestInput() + ctx := input.ctx + bankKeeper := NewBaseKeeper(input.ak) + viewKeeper := NewBaseViewKeeper(input.ak) addr := sdk.AccAddress([]byte("addr1")) - acc := accountKeeper.NewAccountWithAddress(ctx, addr) + acc := input.ak.NewAccountWithAddress(ctx, addr) // Test GetCoins/SetCoins - accountKeeper.SetAccount(ctx, acc) + input.ak.SetAccount(ctx, acc) require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) @@ -188,3 +200,151 @@ func TestViewKeeper(t *testing.T) { require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)})) require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)})) } + +func TestVestingAccountSend(t *testing.T) { + input := setupTestInput() + now := tmtime.Now() + ctx := input.ctx.WithBlockHeader(abci.Header{Time: now}) + endTime := now.Add(24 * time.Hour) + + origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} + sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} + bankKeeper := NewBaseKeeper(input.ak) + + addr1 := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) + input.ak.SetAccount(ctx, vacc) + + // require that no coins be sendable at the beginning of the vesting schedule + _, err := bankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) + require.Error(t, err) + + // receive some coins + vacc.SetCoins(origCoins.Plus(sendCoins)) + input.ak.SetAccount(ctx, vacc) + + // require that all vested coins are spendable plus any received + ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) + _, err = bankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) + vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount) + require.NoError(t, err) + require.Equal(t, origCoins, vacc.GetCoins()) +} + +func TestVestingAccountReceive(t *testing.T) { + input := setupTestInput() + now := tmtime.Now() + ctx := input.ctx.WithBlockHeader(abci.Header{Time: now}) + endTime := now.Add(24 * time.Hour) + + origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} + sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} + bankKeeper := NewBaseKeeper(input.ak) + + addr1 := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) + acc := input.ak.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, vacc) + input.ak.SetAccount(ctx, acc) + bankKeeper.SetCoins(ctx, addr2, origCoins) + + // send some coins to the vesting account + bankKeeper.SendCoins(ctx, addr2, addr1, sendCoins) + + // require the coins are spendable + vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount) + require.Equal(t, origCoins.Plus(sendCoins), vacc.GetCoins()) + require.Equal(t, vacc.SpendableCoins(now), sendCoins) + + // require coins are spendable plus any that have vested + require.Equal(t, vacc.SpendableCoins(now.Add(12*time.Hour)), origCoins) +} + +func TestDelegateCoins(t *testing.T) { + input := setupTestInput() + now := tmtime.Now() + ctx := input.ctx.WithBlockHeader(abci.Header{Time: now}) + endTime := now.Add(24 * time.Hour) + + origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} + delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} + bankKeeper := NewBaseKeeper(input.ak) + + addr1 := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) + acc := input.ak.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, vacc) + input.ak.SetAccount(ctx, acc) + bankKeeper.SetCoins(ctx, addr2, origCoins) + + ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) + + // require the ability for a non-vesting account to delegate + _, err := bankKeeper.DelegateCoins(ctx, addr2, delCoins) + acc = input.ak.GetAccount(ctx, addr2) + require.NoError(t, err) + require.Equal(t, delCoins, acc.GetCoins()) + + // require the ability for a vesting account to delegate + _, err = bankKeeper.DelegateCoins(ctx, addr1, delCoins) + vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount) + require.NoError(t, err) + require.Equal(t, delCoins, vacc.GetCoins()) +} + +func TestUndelegateCoins(t *testing.T) { + input := setupTestInput() + now := tmtime.Now() + ctx := input.ctx.WithBlockHeader(abci.Header{Time: now}) + endTime := now.Add(24 * time.Hour) + + origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} + delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} + bankKeeper := NewBaseKeeper(input.ak) + + addr1 := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) + acc := input.ak.NewAccountWithAddress(ctx, addr2) + input.ak.SetAccount(ctx, vacc) + input.ak.SetAccount(ctx, acc) + bankKeeper.SetCoins(ctx, addr2, origCoins) + + ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) + + // require the ability for a non-vesting account to delegate + _, err := bankKeeper.DelegateCoins(ctx, addr2, delCoins) + require.NoError(t, err) + + // require the ability for a non-vesting account to undelegate + _, err = bankKeeper.UndelegateCoins(ctx, addr2, delCoins) + require.NoError(t, err) + + acc = input.ak.GetAccount(ctx, addr2) + require.Equal(t, origCoins, acc.GetCoins()) + + // require the ability for a vesting account to delegate + _, err = bankKeeper.DelegateCoins(ctx, addr1, delCoins) + require.NoError(t, err) + + // require the ability for a vesting account to undelegate + _, err = bankKeeper.UndelegateCoins(ctx, addr1, delCoins) + require.NoError(t, err) + + vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount) + require.Equal(t, origCoins, vacc.GetCoins()) +} diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 74705ebc74f0..059b49525073 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -1,13 +1,11 @@ package bank import ( - "encoding/json" - sdk "github.com/cosmos/cosmos-sdk/types" ) // name to identify transaction routes -const MsgRoute = "bank" +const RouterKey = "bank" // MsgSend - high level transaction of the coin module type MsgSend struct { @@ -24,7 +22,7 @@ func NewMsgSend(in []Input, out []Output) MsgSend { // Implements Msg. // nolint -func (msg MsgSend) Route() string { return MsgRoute } +func (msg MsgSend) Route() string { return RouterKey } func (msg MsgSend) Type() string { return "send" } // Implements Msg. @@ -43,24 +41,8 @@ func (msg MsgSend) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgSend) GetSignBytes() []byte { - var inputs, outputs []json.RawMessage - for _, input := range msg.Inputs { - inputs = append(inputs, input.GetSignBytes()) - } - for _, output := range msg.Outputs { - outputs = append(outputs, output.GetSignBytes()) - } - b, err := msgCdc.MarshalJSON(struct { - Inputs []json.RawMessage `json:"inputs"` - Outputs []json.RawMessage `json:"outputs"` - }{ - Inputs: inputs, - Outputs: outputs, - }) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := msgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // Implements Msg. @@ -81,15 +63,6 @@ type Input struct { Coins sdk.Coins `json:"coins"` } -// Return bytes to sign for Input -func (in Input) GetSignBytes() []byte { - bin, err := msgCdc.MarshalJSON(in) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(bin) -} - // ValidateBasic - validate transaction input func (in Input) ValidateBasic() sdk.Error { if len(in.Address) == 0 { @@ -106,11 +79,10 @@ func (in Input) ValidateBasic() sdk.Error { // NewInput - create a transaction input, used with MsgSend func NewInput(addr sdk.AccAddress, coins sdk.Coins) Input { - input := Input{ + return Input{ Address: addr, Coins: coins, } - return input } //---------------------------------------- @@ -122,15 +94,6 @@ type Output struct { Coins sdk.Coins `json:"coins"` } -// Return bytes to sign for Output -func (out Output) GetSignBytes() []byte { - bin, err := msgCdc.MarshalJSON(out) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(bin) -} - // ValidateBasic - validate transaction output func (out Output) ValidateBasic() sdk.Error { if len(out.Address) == 0 { @@ -147,11 +110,10 @@ func (out Output) ValidateBasic() sdk.Error { // NewOutput - create a transaction output, used with MsgSend func NewOutput(addr sdk.AccAddress, coins sdk.Coins) Output { - output := Output{ + return Output{ Address: addr, Coins: coins, } - return output } // ---------------------------------------------------------------------------- diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 040d12bda4ba..3f8b68fd454e 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -179,7 +179,7 @@ func TestMsgSendGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - expected := `{"inputs":[{"address":"cosmos1d9h8qat57ljhcm","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmos1da6hgur4wsmpnjyg","coins":[{"amount":"10","denom":"atom"}]}]}` + expected := `{"type":"cosmos-sdk/Send","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/msgs.go b/x/bank/simulation/msgs.go index 96df52f56f43..e7d16496b72e 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -64,7 +64,7 @@ func createSingleInputSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.A toAcc = simulation.RandomAcc(r, accs) } toAddr := toAcc.Address - initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).GetCoins() + initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time) if len(initFromCoins) == 0 { return fromAcc, "skipping, no coins at all", msg, true diff --git a/x/bank/tags.go b/x/bank/tags.go new file mode 100644 index 000000000000..264d8930971d --- /dev/null +++ b/x/bank/tags.go @@ -0,0 +1,10 @@ +package bank + +// Tag keys and values +var ( + TagActionUndelegateCoins = []byte("undelegateCoins") + TagActionDelegateCoins = []byte("delegateCoins") + + TagKeyRecipient = "recipient" + TagKeySender = "sender" +) diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go index 800ede928d88..aae399062320 100644 --- a/x/distribution/abci_app.go +++ b/x/distribution/abci_app.go @@ -10,21 +10,8 @@ import ( // set the proposer for determining distribution during endblock func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { - if ctx.BlockHeight() > 1 { - previousPercentPrecommitVotes := getPreviousPercentPrecommitVotes(req) - previousProposer := k.GetPreviousProposerConsAddr(ctx) - k.AllocateTokens(ctx, previousPercentPrecommitVotes, previousProposer) - } - - consAddr := sdk.ConsAddress(req.Header.ProposerAddress) - k.SetPreviousProposerConsAddr(ctx, consAddr) -} - -// percent precommit votes for the previous block -func getPreviousPercentPrecommitVotes(req abci.RequestBeginBlock) sdk.Dec { - - // determine the total number of signed power - totalPower, sumPrecommitPower := int64(0), int64(0) + // determine the total power signing the block + var totalPower, sumPrecommitPower int64 for _, voteInfo := range req.LastCommitInfo.GetVotes() { totalPower += voteInfo.Validator.Power if voteInfo.SignedLastBlock { @@ -32,8 +19,15 @@ func getPreviousPercentPrecommitVotes(req abci.RequestBeginBlock) sdk.Dec { } } - if totalPower == 0 { - return sdk.ZeroDec() + // TODO this is Tendermint-dependent + // ref https://github.com/cosmos/cosmos-sdk/issues/3095 + if ctx.BlockHeight() > 1 { + previousProposer := k.GetPreviousProposerConsAddr(ctx) + k.AllocateTokens(ctx, sumPrecommitPower, totalPower, previousProposer, req.LastCommitInfo.GetVotes()) } - return sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower)) + + // record the proposer for when we payout on the next block + consAddr := sdk.ConsAddress(req.Header.ProposerAddress) + k.SetPreviousProposerConsAddr(ctx, consAddr) + } diff --git a/x/distribution/alias.go b/x/distribution/alias.go index 7f4457b35a12..d050eddad568 100644 --- a/x/distribution/alias.go +++ b/x/distribution/alias.go @@ -11,63 +11,30 @@ type ( Keeper = keeper.Keeper Hooks = keeper.Hooks - DelegatorWithdrawInfo = types.DelegatorWithdrawInfo - DelegationDistInfo = types.DelegationDistInfo - ValidatorDistInfo = types.ValidatorDistInfo - TotalAccum = types.TotalAccum - FeePool = types.FeePool - DecCoin = types.DecCoin - DecCoins = types.DecCoins - MsgSetWithdrawAddress = types.MsgSetWithdrawAddress - MsgWithdrawDelegatorRewardsAll = types.MsgWithdrawDelegatorRewardsAll MsgWithdrawDelegatorReward = types.MsgWithdrawDelegatorReward - MsgWithdrawValidatorRewardsAll = types.MsgWithdrawValidatorRewardsAll + MsgWithdrawValidatorCommission = types.MsgWithdrawValidatorCommission GenesisState = types.GenesisState // expected keepers - StakeKeeper = types.StakeKeeper + StakingKeeper = types.StakingKeeper BankKeeper = types.BankKeeper FeeCollectionKeeper = types.FeeCollectionKeeper -) - -var ( - NewKeeper = keeper.NewKeeper - - GetValidatorDistInfoKey = keeper.GetValidatorDistInfoKey - GetDelegationDistInfoKey = keeper.GetDelegationDistInfoKey - GetDelegationDistInfosKey = keeper.GetDelegationDistInfosKey - GetDelegatorWithdrawAddrKey = keeper.GetDelegatorWithdrawAddrKey - FeePoolKey = keeper.FeePoolKey - ValidatorDistInfoKey = keeper.ValidatorDistInfoKey - DelegationDistInfoKey = keeper.DelegationDistInfoKey - DelegatorWithdrawInfoKey = keeper.DelegatorWithdrawInfoKey - ProposerKey = keeper.ProposerKey - DefaultParamspace = keeper.DefaultParamspace - - InitialFeePool = types.InitialFeePool - - NewGenesisState = types.NewGenesisState - ValidateGenesis = types.ValidateGenesis - DefaultGenesisState = types.DefaultGenesisState - DefaultGenesisWithValidators = types.DefaultGenesisWithValidators - - RegisterCodec = types.RegisterCodec - NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress - NewMsgWithdrawDelegatorRewardsAll = types.NewMsgWithdrawDelegatorRewardsAll - NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward - NewMsgWithdrawValidatorRewardsAll = types.NewMsgWithdrawValidatorRewardsAll - - NewDecCoins = types.NewDecCoins - - NewTotalAccum = types.NewTotalAccum + // querier param types + QueryValidatorCommissionParams = keeper.QueryValidatorCommissionParams + QueryValidatorSlashesParams = keeper.QueryValidatorSlashesParams + QueryDelegationRewardsParams = keeper.QueryDelegationRewardsParams ) const ( DefaultCodespace = types.DefaultCodespace CodeInvalidInput = types.CodeInvalidInput + StoreKey = types.StoreKey + TStoreKey = types.TStoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute ) var ( @@ -77,4 +44,20 @@ var ( TagValidator = tags.Validator TagDelegator = tags.Delegator + + NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress + NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward + NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission + + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams + NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams + NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams + DefaultParamspace = keeper.DefaultParamspace + + RegisterCodec = types.RegisterCodec + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + InitialFeePool = types.InitialFeePool ) diff --git a/x/distribution/client/cli/query.go b/x/distribution/client/cli/query.go new file mode 100644 index 000000000000..09d4b458ac2d --- /dev/null +++ b/x/distribution/client/cli/query.go @@ -0,0 +1,189 @@ +package cli + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// GetCmdQueryParams implements the query params command. +func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "params", + Args: cobra.NoArgs, + Short: "Query distribution params", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/params/community_tax", queryRoute) + retCommunityTax, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return err + } + + route = fmt.Sprintf("custom/%s/params/base_proposer_reward", queryRoute) + retBaseProposerReward, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return err + } + + route = fmt.Sprintf("custom/%s/params/bonus_proposer_reward", queryRoute) + retBonusProposerReward, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return err + } + + route = fmt.Sprintf("custom/%s/params/withdraw_addr_enabled", queryRoute) + retWithdrawAddrEnabled, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return err + } + + params := NewPrettyParams(retCommunityTax, retBaseProposerReward, + retBonusProposerReward, retWithdrawAddrEnabled) + + return cliCtx.PrintOutput(params) + }, + } +} + +// GetCmdQueryOutstandingRewards implements the query outstanding rewards command. +func GetCmdQueryOutstandingRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "outstanding-rewards", + Args: cobra.NoArgs, + Short: "Query distribution outstanding (un-withdrawn) rewards", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute) + res, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return err + } + + var outstandingRewards types.OutstandingRewards + cdc.MustUnmarshalJSON(res, &outstandingRewards) + return cliCtx.PrintOutput(outstandingRewards) + }, + } +} + +// GetCmdQueryValidatorCommission implements the query validator commission command. +func GetCmdQueryValidatorCommission(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "commission [validator]", + Args: cobra.ExactArgs(1), + Short: "Query distribution validator commission", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + validatorAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(distr.NewQueryValidatorCommissionParams(validatorAddr)) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/validator_commission", queryRoute) + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var valCom types.ValidatorAccumulatedCommission + cdc.MustUnmarshalJSON(res, &valCom) + return cliCtx.PrintOutput(valCom) + }, + } +} + +// GetCmdQueryValidatorSlashes implements the query validator slashes command. +func GetCmdQueryValidatorSlashes(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "slashes [validator] [start-height] [end-height]", + Args: cobra.ExactArgs(3), + Short: "Query distribution validator slashes", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + validatorAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + startHeight, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return fmt.Errorf("start-height %s not a valid uint, please input a valid start-height", args[1]) + } + + endHeight, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return fmt.Errorf("end-height %s not a valid uint, please input a valid end-height", args[2]) + } + + params := distr.NewQueryValidatorSlashesParams(validatorAddr, startHeight, endHeight) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_slashes", queryRoute), bz) + if err != nil { + return err + } + + var slashes types.ValidatorSlashEvent + cdc.MustUnmarshalJSON(res, &slashes) + return cliCtx.PrintOutput(slashes) + }, + } +} + +// GetCmdQueryDelegatorRewards implements the query delegator rewards command. +func GetCmdQueryDelegatorRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "rewards [delegator] [validator]", + Args: cobra.ExactArgs(2), + Short: "Query distribution delegator rewards", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + validatorAddr, err := sdk.ValAddressFromBech32(args[1]) + if err != nil { + return err + } + + params := distr.NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/delegation_rewards", queryRoute) + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var coins sdk.DecCoins + cdc.MustUnmarshalJSON(res, &coins) + return cliCtx.PrintOutput(coins) + }, + } +} diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go index 2e5cac6a611f..67113c2b92b3 100644 --- a/x/distribution/client/cli/tx.go +++ b/x/distribution/client/cli/tx.go @@ -43,7 +43,7 @@ func GetTxCmd(storeKey string, cdc *amino.Codec) *cobra.Command { func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "withdraw-rewards", - Short: "withdraw rewards for either: all-delegations, a delegation, or a validator", + Short: "withdraw rewards for either a delegation or a validator", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { @@ -68,8 +68,8 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { return err } valAddr := sdk.ValAddress(addr.Bytes()) - msg = types.NewMsgWithdrawValidatorRewardsAll(valAddr) - case onlyFromVal != "": + msg = types.NewMsgWithdrawValidatorCommission(valAddr) + default: delAddr, err := cliCtx.GetFromAddress() if err != nil { return err @@ -81,12 +81,6 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { } msg = types.NewMsgWithdrawDelegatorReward(delAddr, valAddr) - default: - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - msg = types.NewMsgWithdrawDelegatorRewardsAll(delAddr) } if cliCtx.GenerateOnly { diff --git a/x/distribution/client/cli/util.go b/x/distribution/client/cli/util.go new file mode 100644 index 000000000000..06e24a6b7e60 --- /dev/null +++ b/x/distribution/client/cli/util.go @@ -0,0 +1,34 @@ +package cli + +import ( + "encoding/json" + "fmt" +) + +// Convenience struct for CLI output +type PrettyParams struct { + CommunityTax json.RawMessage `json:"community_tax"` + BaseProposerReward json.RawMessage `json:"base_proposer_reward"` + BonusProposerReward json.RawMessage `json:"bonus_proposer_reward"` + WithdrawAddrEnabled json.RawMessage `json:"withdraw_addr_enabled"` +} + +// Construct a new PrettyParams +func NewPrettyParams(communityTax json.RawMessage, baseProposerReward json.RawMessage, bonusProposerReward json.RawMessage, withdrawAddrEnabled json.RawMessage) PrettyParams { + return PrettyParams{ + CommunityTax: communityTax, + BaseProposerReward: baseProposerReward, + BonusProposerReward: bonusProposerReward, + WithdrawAddrEnabled: withdrawAddrEnabled, + } +} + +func (pp PrettyParams) String() string { + return fmt.Sprintf(`Distribution Params: + Community Tax: %s + Base Proposer Reward: %s + Bonus Proposer Reward: %s + Withdraw Addr Enabled: %s`, pp.CommunityTax, + pp.BaseProposerReward, pp.BonusProposerReward, pp.WithdrawAddrEnabled) + +} diff --git a/x/distribution/client/module_client.go b/x/distribution/client/module_client.go index 69734b08d0be..412bf9d6a439 100644 --- a/x/distribution/client/module_client.go +++ b/x/distribution/client/module_client.go @@ -20,13 +20,26 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for this module func (mc ModuleClient) GetQueryCmd() *cobra.Command { - return &cobra.Command{Hidden: true} + distQueryCmd := &cobra.Command{ + Use: "distr", + Short: "Querying commands for the distribution module", + } + + distQueryCmd.AddCommand(client.GetCommands( + distCmds.GetCmdQueryParams(mc.storeKey, mc.cdc), + distCmds.GetCmdQueryOutstandingRewards(mc.storeKey, mc.cdc), + distCmds.GetCmdQueryValidatorCommission(mc.storeKey, mc.cdc), + distCmds.GetCmdQueryValidatorSlashes(mc.storeKey, mc.cdc), + distCmds.GetCmdQueryDelegatorRewards(mc.storeKey, mc.cdc), + )...) + + return distQueryCmd } // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { distTxCmd := &cobra.Command{ - Use: "dist", + Use: "distr", Short: "Distribution transactions subcommands", } diff --git a/x/distribution/genesis.go b/x/distribution/genesis.go index 34680891629b..9212a5aabdf4 100644 --- a/x/distribution/genesis.go +++ b/x/distribution/genesis.go @@ -11,30 +11,99 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { keeper.SetCommunityTax(ctx, data.CommunityTax) keeper.SetBaseProposerReward(ctx, data.BaseProposerReward) keeper.SetBonusProposerReward(ctx, data.BonusProposerReward) - - for _, vdi := range data.ValidatorDistInfos { - keeper.SetValidatorDistInfo(ctx, vdi) + keeper.SetWithdrawAddrEnabled(ctx, data.WithdrawAddrEnabled) + for _, dwi := range data.DelegatorWithdrawInfos { + keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddr, dwi.WithdrawAddr) } - for _, ddi := range data.DelegationDistInfos { - keeper.SetDelegationDistInfo(ctx, ddi) + keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer) + keeper.SetOutstandingRewards(ctx, data.OutstandingRewards) + for _, acc := range data.ValidatorAccumulatedCommissions { + keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddr, acc.Accumulated) } - for _, dw := range data.DelegatorWithdrawInfos { - keeper.SetDelegatorWithdrawAddr(ctx, dw.DelegatorAddr, dw.WithdrawAddr) + for _, his := range data.ValidatorHistoricalRewards { + keeper.SetValidatorHistoricalRewards(ctx, his.ValidatorAddr, his.Period, his.Rewards) + } + for _, cur := range data.ValidatorCurrentRewards { + keeper.SetValidatorCurrentRewards(ctx, cur.ValidatorAddr, cur.Rewards) + } + for _, del := range data.DelegatorStartingInfos { + keeper.SetDelegatorStartingInfo(ctx, del.ValidatorAddr, del.DelegatorAddr, del.StartingInfo) + } + for _, evt := range data.ValidatorSlashEvents { + keeper.SetValidatorSlashEvent(ctx, evt.ValidatorAddr, evt.Height, evt.Event) } - keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer) } -// ExportGenesis returns a GenesisState for a given context and keeper. The -// GenesisState will contain the pool, and validator/delegator distribution info's +// ExportGenesis returns a GenesisState for a given context and keeper. func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { feePool := keeper.GetFeePool(ctx) communityTax := keeper.GetCommunityTax(ctx) baseProposerRewards := keeper.GetBaseProposerReward(ctx) bonusProposerRewards := keeper.GetBonusProposerReward(ctx) - vdis := keeper.GetAllValidatorDistInfos(ctx) - ddis := keeper.GetAllDelegationDistInfos(ctx) - dwis := keeper.GetAllDelegatorWithdrawInfos(ctx) + withdrawAddrEnabled := keeper.GetWithdrawAddrEnabled(ctx) + 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, + }) + return false + }) pp := keeper.GetPreviousProposerConsAddr(ctx) - return NewGenesisState(feePool, communityTax, baseProposerRewards, - bonusProposerRewards, vdis, ddis, dwis, pp) + outstanding := keeper.GetOutstandingRewards(ctx) + 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, + }) + return false + }, + ) + his := make([]types.ValidatorHistoricalRewardsRecord, 0) + 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, + }) + return false + }, + ) + cur := make([]types.ValidatorCurrentRewardsRecord, 0) + keeper.IterateValidatorCurrentRewards(ctx, + func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool) { + cur = append(cur, types.ValidatorCurrentRewardsRecord{ + ValidatorAddr: val, + Rewards: rewards, + }) + return false + }, + ) + dels := make([]types.DelegatorStartingInfoRecord, 0) + 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, + }) + return false + }, + ) + slashes := make([]types.ValidatorSlashEventRecord, 0) + 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, + }) + return false + }, + ) + return types.NewGenesisState(feePool, communityTax, baseProposerRewards, bonusProposerRewards, withdrawAddrEnabled, + dwi, pp, outstanding, acc, his, cur, dels, slashes) } diff --git a/x/distribution/handler.go b/x/distribution/handler.go index c1268944ccf0..e5e2c5aa8436 100644 --- a/x/distribution/handler.go +++ b/x/distribution/handler.go @@ -13,38 +13,24 @@ func NewHandler(k keeper.Keeper) sdk.Handler { switch msg := msg.(type) { case types.MsgSetWithdrawAddress: return handleMsgModifyWithdrawAddress(ctx, msg, k) - case types.MsgWithdrawDelegatorRewardsAll: - return handleMsgWithdrawDelegatorRewardsAll(ctx, msg, k) case types.MsgWithdrawDelegatorReward: return handleMsgWithdrawDelegatorReward(ctx, msg, k) - case types.MsgWithdrawValidatorRewardsAll: - return handleMsgWithdrawValidatorRewardsAll(ctx, msg, k) + case types.MsgWithdrawValidatorCommission: + return handleMsgWithdrawValidatorCommission(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in distribution module").Result() } } } -//_____________________________________________________________________ - -// These functions assume everything has been authenticated, -// now we just perform action and save +// These functions assume everything has been authenticated (ValidateBasic passed, and signatures checked) func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAddress, k keeper.Keeper) sdk.Result { - k.SetDelegatorWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr) - - tags := sdk.NewTags( - tags.Delegator, []byte(msg.DelegatorAddr.String()), - ) - return sdk.Result{ - Tags: tags, + err := k.SetWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr) + if err != nil { + return err.Result() } -} - -func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawDelegatorRewardsAll, k keeper.Keeper) sdk.Result { - - k.WithdrawDelegationRewardsAll(ctx, msg.DelegatorAddr) tags := sdk.NewTags( tags.Delegator, []byte(msg.DelegatorAddr.String()), @@ -56,7 +42,7 @@ func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdraw func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result { - err := k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddr, msg.ValidatorAddr) if err != nil { return err.Result() } @@ -70,9 +56,9 @@ func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDele } } -func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result { +func handleMsgWithdrawValidatorCommission(ctx sdk.Context, msg types.MsgWithdrawValidatorCommission, k keeper.Keeper) sdk.Result { - err := k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr) + err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddr) if err != nil { return err.Result() } diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index 97b1e4b379db..f43b018ff453 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -1,57 +1,85 @@ package keeper import ( + abci "github.com/tendermint/tendermint/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution/types" ) -// Allocate fees handles distribution of the collected fees -func (k Keeper) AllocateTokens(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { - - // get the proposer of this block - proposerValidator := k.stakeKeeper.ValidatorByConsAddr(ctx, proposer) +// allocate fees handles distribution of the collected fees +func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower int64, proposer sdk.ConsAddress, votes []abci.VoteInfo) { - proposerDist := k.GetValidatorDistInfo(ctx, proposerValidator.GetOperator()) + // fetch collected fees & fee pool + feesCollectedInt := k.feeCollectionKeeper.GetCollectedFees(ctx) + feesCollected := sdk.NewDecCoins(feesCollectedInt) + feePool := k.GetFeePool(ctx) - // get the fees which have been getting collected through all the - // transactions in the block - feesCollected := k.feeCollectionKeeper.GetCollectedFees(ctx) - feesCollectedDec := types.NewDecCoins(feesCollected) + // clear collected fees, which will now be distributed + k.feeCollectionKeeper.ClearCollectedFees(ctx) - feePool := k.GetFeePool(ctx) - // Temporary workaround to keep CanWithdrawInvariant happy. - // General discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634 - if k.stakeKeeper.GetLastTotalPower(ctx).IsZero() { - feePool.CommunityPool = feePool.CommunityPool.Plus(feesCollectedDec) + // 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) k.SetFeePool(ctx, feePool) - k.feeCollectionKeeper.ClearCollectedFees(ctx) return } - // allocated rewards to proposer + // calculate fraction votes + fractionVotes := sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower)) + + // calculate proposer reward baseProposerReward := k.GetBaseProposerReward(ctx) bonusProposerReward := k.GetBonusProposerReward(ctx) - proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(percentVotes)) - proposerReward := feesCollectedDec.MulDec(proposerMultiplier) + proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(fractionVotes)) + proposerReward := feesCollected.MulDec(proposerMultiplier) - // apply commission - commission := proposerReward.MulDec(proposerValidator.GetCommission()) - remaining := proposerReward.Minus(commission) - proposerDist.ValCommission = proposerDist.ValCommission.Plus(commission) - proposerDist.DelPool = proposerDist.DelPool.Plus(remaining) + // pay proposer + proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer) + k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward) + remaining := feesCollected.Minus(proposerReward) - // allocate community funding + // calculate fraction allocated to validators communityTax := k.GetCommunityTax(ctx) - communityFunding := feesCollectedDec.MulDec(communityTax) - feePool.CommunityPool = feePool.CommunityPool.Plus(communityFunding) + voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax) + + // allocate tokens proportionally to voting power + // TODO consider parallelizing later, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r246276376 + for _, vote := range votes { + validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) - // set the global pool within the distribution module - poolReceived := feesCollectedDec.Minus(proposerReward).Minus(communityFunding) - feePool.ValPool = feePool.ValPool.Plus(poolReceived) + // TODO likely we should only reward validators who actually signed the block. + // 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) + k.AllocateTokensToValidator(ctx, validator, reward) + remaining = remaining.Minus(reward) + } - k.SetValidatorDistInfo(ctx, proposerDist) + // allocate community funding + feePool.CommunityPool = feePool.CommunityPool.Plus(remaining) k.SetFeePool(ctx, feePool) - // clear the now distributed fees - k.feeCollectionKeeper.ClearCollectedFees(ctx) + // 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) + + // update current commission + currentCommission := k.GetValidatorAccumulatedCommission(ctx, val.GetOperator()) + currentCommission = currentCommission.Plus(commission) + k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), currentCommission) + + // update current rewards + currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) + currentRewards.Rewards = currentRewards.Rewards.Plus(shared) + k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards) } diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go index 35ad25e66396..704e37de54fe 100644 --- a/x/distribution/keeper/allocation_test.go +++ b/x/distribution/keeper/allocation_test.go @@ -3,109 +3,106 @@ package keeper import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) -func TestAllocateTokensBasic(t *testing.T) { - - // no community tax on inputs - ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - totalPower := int64(10) - totalPowerDec := sdk.NewDec(totalPower) - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // verify everything has been set in staking correctly - validator, found := sk.GetValidator(ctx, valOpAddr1) - require.True(t, found) - require.Equal(t, sdk.Bonded, validator.Status) - assert.True(sdk.DecEq(t, totalPowerDec, validator.Tokens)) - assert.True(sdk.DecEq(t, totalPowerDec, validator.DelegatorShares)) - bondedTokens := sk.TotalPower(ctx) - assert.True(sdk.DecEq(t, totalPowerDec, bondedTokens)) - - // initial fee pool should be empty - feePool := keeper.GetFeePool(ctx) - require.Nil(t, feePool.ValPool) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // verify that these fees have been received by the feePool - percentProposer := sdk.NewDecWithPrec(5, 2) - percentRemaining := sdk.OneDec().Sub(percentProposer) - feePool = keeper.GetFeePool(ctx) - expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) - require.Equal(t, 1, len(feePool.ValPool)) - require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) +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, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + val := sk.Validator(ctx, valOpAddr1) + + // allocate tokens + tokens := sdk.DecCoins{ + {staking.DefaultBondDenom, sdk.NewDec(10)}, + } + k.AllocateTokensToValidator(ctx, val, tokens) + + // check commission + expected := sdk.DecCoins{ + {staking.DefaultBondDenom, sdk.NewDec(5)}, + } + require.Equal(t, expected, k.GetValidatorAccumulatedCommission(ctx, val.GetOperator())) + + // check current rewards + require.Equal(t, expected, k.GetValidatorCurrentRewards(ctx, val.GetOperator()).Rewards) } -func TestAllocateTokensWithCommunityTax(t *testing.T) { - communityTax := sdk.NewDecWithPrec(1, 2) //1% - ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - totalPower := int64(10) - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // verify that these fees have been received by the feePool - feePool := keeper.GetFeePool(ctx) - // 5% goes to proposer, 1% community tax - percentProposer := sdk.NewDecWithPrec(5, 2) - percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) - expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) - require.Equal(t, 1, len(feePool.ValPool)) - require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) -} - -func TestAllocateTokensWithPartialPrecommitPower(t *testing.T) { - communityTax := sdk.NewDecWithPrec(1, 2) - ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - totalPower := int64(100) - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - percentPrecommitVotes := sdk.NewDecWithPrec(25, 2) - keeper.AllocateTokens(ctx, percentPrecommitVotes, valConsAddr1) - - // verify that these fees have been received by the feePool - feePool := keeper.GetFeePool(ctx) - // 1% + 4%*0.25 to proposer + 1% community tax = 97% - percentProposer := sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(4, 2).Mul(percentPrecommitVotes)) - percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) - expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) - require.Equal(t, 1, len(feePool.ValPool)) - require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) +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, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // create second validator with 0% commission + commission = staking.NewCommissionMsg(sdk.NewDec(0), sdk.NewDec(0), sdk.NewDec(0)) + msg = staking.NewMsgCreateValidator(valOpAddr2, valConsPk2, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + abciValA := abci.Validator{ + Address: valConsPk1.Address(), + Power: 100, + } + abciValB := abci.Validator{ + Address: valConsPk2.Address(), + Power: 100, + } + + // 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.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{ + {staking.DefaultBondDenom, sdk.NewInt(100)}, + } + fck.SetCollectedFees(fees) + votes := []abci.VoteInfo{ + { + Validator: abciValA, + SignedLastBlock: true, + }, + { + Validator: abciValB, + SignedLastBlock: true, + }, + } + k.AllocateTokens(ctx, 200, 200, valConsAddr2, votes) + + // 98 outstanding rewards (100 less 2 to community pool) + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(98)}}, k.GetOutstandingRewards(ctx)) + // 2 community pool coins + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(2)}}, k.GetFeePool(ctx).CommunityPool) + // 50% commission for first proposer, (0.5 * 93%) * 100 / 2 = 23.25 + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(2325, 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) + // zero commission for second proposer + require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero()) + // just staking.proportional for first proposer less commission = (0.5 * 93%) * 100 / 2 = 23.25 + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(2325, 2)}}, k.GetValidatorCurrentRewards(ctx, valOpAddr1).Rewards) + // proposer reward + staking.proportional for second proposer = (5 % + 0.5 * (93%)) * 100 = 51.5 + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(515, 1)}}, k.GetValidatorCurrentRewards(ctx, valOpAddr2).Rewards) } diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index dc54a6e4fe5f..d75691bdf5af 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -2,229 +2,117 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" ) -// check whether a delegator distribution info exists -func (k Keeper) HasDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, - valOperatorAddr sdk.ValAddress) (has bool) { - store := ctx.KVStore(k.storeKey) - return store.Has(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) -} - -// get the delegator distribution info -func (k Keeper) GetDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, - valOperatorAddr sdk.ValAddress) (ddi types.DelegationDistInfo) { - - store := ctx.KVStore(k.storeKey) - - b := store.Get(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) - if b == nil { - panic("Stored delegation-distribution info should not have been nil") - } - - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &ddi) - return -} +// initialize starting info for a new delegation +func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) { + // period has already been incremented - we want to store the period ended by this delegation action + previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 -// set the delegator distribution info -func (k Keeper) SetDelegationDistInfo(ctx sdk.Context, ddi types.DelegationDistInfo) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinaryLengthPrefixed(ddi) - store.Set(GetDelegationDistInfoKey(ddi.DelegatorAddr, ddi.ValOperatorAddr), b) -} + // increment reference count for the period we're going to track + k.incrementReferenceCount(ctx, val, previousPeriod) -// remove a delegator distribution info -func (k Keeper) RemoveDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, - valOperatorAddr sdk.ValAddress) { + validator := k.stakingKeeper.Validator(ctx, val) + delegation := k.stakingKeeper.Delegation(ctx, del, val) - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) + // 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()) + k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) } -// remove all delegation distribution infos -func (k Keeper) RemoveDelegationDistInfos(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) +// calculate the rewards accrued by a delegation between two periods +func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Validator, + startingPeriod, endingPeriod uint64, stake sdk.Dec) (rewards sdk.DecCoins) { + // sanity check + if startingPeriod > endingPeriod { + panic("startingPeriod cannot be greater than endingPeriod") } -} -// iterate over all the validator distribution infos -func (k Keeper) IterateDelegationDistInfos(ctx sdk.Context, - fn func(index int64, distInfo types.DelegationDistInfo) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) - defer iter.Close() - index := int64(0) - for ; iter.Valid(); iter.Next() { - var ddi types.DelegationDistInfo - k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &ddi) - if fn(index, ddi) { - return - } - index++ + // sanity check + if stake.LT(sdk.ZeroDec()) { + panic("stake should not be negative") } -} - -//___________________________________________________________________________________________ - -// get the delegator withdraw address, return the delegator address if not set -func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { - store := ctx.KVStore(k.storeKey) - b := store.Get(GetDelegatorWithdrawAddrKey(delAddr)) - if b == nil { - return delAddr + // return staking * (ending - starting) + starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod) + ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod) + difference := ending.CumulativeRewardRatio.Minus(starting.CumulativeRewardRatio) + if difference.HasNegative() { + panic("negative rewards should not be possible") } - return sdk.AccAddress(b) -} - -// set the delegator withdraw address -func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { - store := ctx.KVStore(k.storeKey) - store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes()) -} - -// remove a delegator withdraw info -func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegatorWithdrawAddrKey(delAddr)) -} - -//___________________________________________________________________________________________ - -// return all rewards for a delegation -func (k Keeper) withdrawDelegationReward(ctx sdk.Context, - delAddr sdk.AccAddress, valAddr sdk.ValAddress) (types.FeePool, - types.ValidatorDistInfo, types.DelegationDistInfo, types.DecCoins) { - - wc := k.GetWithdrawContext(ctx, valAddr) - valInfo := k.GetValidatorDistInfo(ctx, valAddr) - delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr) - validator := k.stakeKeeper.Validator(ctx, valAddr) - delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr) - - delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(wc, valInfo, - validator.GetDelegatorShares(), delegation.GetShares()) - - return feePool, valInfo, delInfo, withdraw -} - -// get all rewards for all delegations of a delegator -func (k Keeper) currentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) types.DecCoins { - - wc := k.GetWithdrawContext(ctx, valAddr) - - valInfo := k.GetValidatorDistInfo(ctx, valAddr) - delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr) - validator := k.stakeKeeper.Validator(ctx, valAddr) - delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr) - - estimation := delInfo.CurrentRewards(wc, valInfo, - validator.GetDelegatorShares(), delegation.GetShares()) - - return estimation + // note: necessary to truncate so we don't allow withdrawing more rewards than owed + rewards = difference.MulDecTruncate(stake) + return } -//___________________________________________________________________________________________ +// calculate the total rewards accrued by a delegation +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()) + 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 { + 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 + return false + }, + ) + } -// withdraw all rewards for a single delegation -// NOTE: This gets called "onDelegationSharesModified", -// meaning any changes to bonded coins -func (k Keeper) WithdrawToDelegator(ctx sdk.Context, feePool types.FeePool, - delAddr sdk.AccAddress, amount types.DecCoins) { + // calculate rewards for final period + rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) - withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delAddr) - coinsToAdd, change := amount.TruncateDecimal() - feePool.CommunityPool = feePool.CommunityPool.Plus(change) - k.SetFeePool(ctx, feePool) - _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd) - if err != nil { - panic(err) - } + return } -//___________________________________________________________________________________________ - -// withdraw all rewards for a single delegation -// NOTE: This gets called "onDelegationSharesModified", -// meaning any changes to bonded coins -func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) sdk.Error { +func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error { - if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) { + // check existence of delegator starting info + if !k.HasDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) { return types.ErrNoDelegationDistInfo(k.codespace) } - feePool, valInfo, delInfo, withdraw := k.withdrawDelegationReward(ctx, delAddr, valAddr) - - k.SetValidatorDistInfo(ctx, valInfo) - k.SetDelegationDistInfo(ctx, delInfo) - k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw) - - return nil -} - -// current rewards for a single delegation -func (k Keeper) CurrentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) (sdk.Coins, sdk.Error) { - - if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) { - return sdk.Coins{}, types.ErrNoDelegationDistInfo(k.codespace) - } - estCoins := k.currentDelegationReward(ctx, delAddr, valAddr) - trucate, _ := estCoins.TruncateDecimal() - return trucate, nil -} + // end current period and calculate rewards + endingPeriod := k.incrementValidatorPeriod(ctx, val) + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) -//___________________________________________________________________________________________ + // decrement reference count of starting period + startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) + startingPeriod := startingInfo.PreviousPeriod + k.decrementReferenceCount(ctx, del.GetValidatorAddr(), startingPeriod) -// return all rewards for all delegations of a delegator -func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress) { - withdraw := k.withdrawDelegationRewardsAll(ctx, delAddr) + // truncate coins, return remainder to community pool + coins, remainder := rewards.TruncateDecimal() + outstanding := k.GetOutstandingRewards(ctx) + k.SetOutstandingRewards(ctx, outstanding.Minus(rewards)) feePool := k.GetFeePool(ctx) - k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw) -} - -func (k Keeper) withdrawDelegationRewardsAll(ctx sdk.Context, - delAddr sdk.AccAddress) types.DecCoins { - - withdraw := types.DecCoins{} - - // iterate over all the delegations - operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) { - valAddr := del.GetValidatorAddr() - feePool, valInfo, delInfo, diWithdraw := k.withdrawDelegationReward(ctx, delAddr, valAddr) - withdraw = withdraw.Plus(diWithdraw) - - k.SetFeePool(ctx, feePool) - k.SetValidatorDistInfo(ctx, valInfo) - k.SetDelegationDistInfo(ctx, delInfo) + feePool.CommunityPool = feePool.CommunityPool.Plus(remainder) + k.SetFeePool(ctx, feePool) - return false + // add coins to user account + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr()) + if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + return err } - k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation) - return withdraw -} + // remove delegator starting info + k.DeleteDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) -// get all rewards for all delegations of a delegator -func (k Keeper) CurrentDelegationRewardsAll(ctx sdk.Context, - delAddr sdk.AccAddress) types.DecCoins { - - // iterate over all the delegations - total := types.DecCoins{} - operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) { - valAddr := del.GetValidatorAddr() - est := k.currentDelegationReward(ctx, delAddr, valAddr) - total = total.Plus(est) - return false - } - k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation) - return total + return nil } diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go index 67606cc842b8..3f601ad32974 100644 --- a/x/distribution/keeper/delegation_test.go +++ b/x/distribution/keeper/delegation_test.go @@ -6,249 +6,554 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) -func TestWithdrawDelegationRewardBasic(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw delegation - ctx = ctx.WithBlockHeight(1) - sk.SetLastTotalPower(ctx, sdk.NewInt(10)) - sk.SetLastValidatorPower(ctx, valOpAddr1, sdk.NewInt(10)) - keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 - require.True(sdk.IntEq(t, expRes, amt)) +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, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // historical count should be 2 (once for validator init, once for delegation init) + require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // historical count should be 2 still + require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) + + // calculate delegation rewards + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be zero + require.True(t, rewards.IsZero()) + + // allocate some rewards + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be half the tokens + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards) + + // commission should be the other half + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) +} + +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)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be zero + require.True(t, rewards.IsZero()) + + // start out block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // slash the validator by 50% + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1)) + + // retrieve validator + val = sk.Validator(ctx, valOpAddr1) + + // increase block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // allocate some rewards + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be half the tokens + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards) + + // commission should be the other half + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) +} + +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 + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be zero + require.True(t, rewards.IsZero()) + + // start out block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // slash the validator by 50% + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1)) + + // fetch the validator again + val = sk.Validator(ctx, valOpAddr1) + + // increase block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // allocate some rewards + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // slash the validator by 50% again + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 50, sdk.NewDecWithPrec(5, 1)) + + // fetch the validator again + val = sk.Validator(ctx, valOpAddr1) + + // increase block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be half the tokens + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, rewards) + + // commission should be the other half + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) } -func TestWithdrawDelegationRewardWithCommission(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator with 10% commission - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw delegation - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100*90% tokens * 10/20 - require.True(sdk.IntEq(t, expRes, amt)) +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, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // allocate some rewards + initial := int64(20) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // second delegation + msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100))) + require.True(t, sh(ctx, msg2).IsOK()) + del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) + + // fetch updated validator + val = sk.Validator(ctx, valOpAddr1) + + // end block + staking.EndBlocker(ctx, sk) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards for del1 + rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod) + + // rewards for del1 should be 3/4 initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial * 3 / 4)}}, rewards) + + // calculate delegation rewards for del2 + rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod) + + // rewards for del2 should be 1/4 initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial * 1 / 4)}}, rewards) + + // commission should be equal to initial (50% twice) + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) +} + +func TestWithdrawDelegationRewardsBasic(t *testing.T) { + balance := int64(1000) + ctx, ak, k, sk, _ := CreateTestInputDefault(t, false, balance) + sh := staking.NewHandler(sk) + + // initialize state + k.SetOutstandingRewards(ctx, sdk.DecCoins{}) + + // create validator with 50% commission + bond := int64(100) + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(bond)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // assert correct initial balance + require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond)}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // set zero outstanding rewards + k.SetOutstandingRewards(ctx, sdk.DecCoins{}) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + + // allocate some rewards + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // historical count should be 2 (initial + latest for delegation) + require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) + + // withdraw rewards + require.Nil(t, k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)) + + // historical count should still be 2 (added one record, cleared one) + require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) + + // assert correct balance + require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + (initial / 2))}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins()) + + // withdraw commission + require.Nil(t, k.WithdrawValidatorCommission(ctx, valOpAddr1)) + + // assert correct balance + require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + initial)}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins()) } -func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator with 10% commission - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 20) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) - require.Equal(t, int64(80), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // delegator 1 withdraw delegation - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(4))).TruncateInt() // 90 + 100*90% tokens * 10/40 - require.True(sdk.IntEq(t, expRes, amt)) +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 + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be zero + require.True(t, rewards.IsZero()) + + // start out block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // allocate some rewards + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // slash the validator by 50% + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1)) + + // slash the validator by 50% again + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 50, sdk.NewDecWithPrec(5, 1)) + + // fetch the validator again + val = sk.Validator(ctx, valOpAddr1) + + // increase block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards + rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + // rewards should be half the tokens + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, rewards) + + // commission should be the other half + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) } -// this test demonstrates how two delegators with the same power can end up -// with different rewards in the end -func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator with no commission - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.ZeroDec()) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(90) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - ctx = ctx.WithBlockHeight(1) - - // delegator 1 withdraw delegation early, delegator 2 just keeps it's accum - keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - expRes1 := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(3))).TruncateInt() // 90 + 100 * 10/30 - require.True(sdk.IntEq(t, expRes1, amt)) - - // allocate 200 denom of fees - feeInputs = sdk.NewInt(180) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - ctx = ctx.WithBlockHeight(2) - - // delegator 2 now withdraws everything it's entitled to - keeper.WithdrawDelegationReward(ctx, delAddr2, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) - // existingTokens + (100+200 * (10/(20+30)) - withdrawnFromVal := sdk.NewDec(60 + 180).Mul(sdk.NewDec(2)).Quo(sdk.NewDec(5)) - expRes2 := sdk.NewDec(90).Add(withdrawnFromVal).TruncateInt() - require.True(sdk.IntEq(t, expRes2, amt)) - - // finally delegator 1 withdraws the remainder of its reward - keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - remainingInVal := sdk.NewDec(60 + 180).Sub(withdrawnFromVal) - expRes3 := sdk.NewDecFromInt(expRes1).Add(remainingInVal.Mul(sdk.NewDec(1)).Quo(sdk.NewDec(3))).TruncateInt() - require.True(sdk.IntEq(t, expRes3, amt)) - - // verify the final withdraw amounts are different - require.True(t, expRes2.GT(expRes3)) +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)) + msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // allocate some rewards + initial := int64(30) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // slash the validator + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1)) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // second delegation + msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100))) + require.True(t, sh(ctx, msg2).IsOK()) + del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) + + // end block + staking.EndBlocker(ctx, sk) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // slash the validator again + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1)) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) + + // fetch updated validator + val = sk.Validator(ctx, valOpAddr1) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards for del1 + rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod) + + // rewards for del1 should be 2/3 initial (half initial first period, 1/6 initial second period) + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec((initial / 2) + (initial / 6))}}, rewards) + + // calculate delegation rewards for del2 + rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod) + + // rewards for del2 should be initial / 3 + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 3)}}, rewards) + + // commission should be equal to initial (twice 50% commission, unaffected by slashing) + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) } -func TestWithdrawDelegationRewardsAll(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //make some validators with different commissions - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1)) - got = stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1)) - got = stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - // delegate to all the validators - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) - msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr2, 20) - require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) - msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr3, 30) - require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) - - // Update sk's LastValidatorPower/LastTotalPowers. - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // 40 tokens left after delegating 60 of them - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(40), amt.Int64()) - - // total power of each validator: - // validator 1: 10 (self) + 10 (delegator) = 20 - // validator 2: 50 (self) + 20 (delegator) = 70 - // validator 3: 40 (self) + 30 (delegator) = 70 - // grand total: 160 - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(1000) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw delegation - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawDelegationRewardsAll(ctx, delAddr1) - amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - - // orig-amount + fees *(1-proposerReward)* (val1Portion * delegatorPotion * (1-val1Commission) ... etc) - // + fees *(proposerReward) * (delegatorPotion * (1-val1Commission)) - // 40 + 1000 *(1- 0.95)* (20/160 * 10/20 * 0.9 + 70/160 * 20/70 * 0.8 + 70/160 * 30/70 * 0.7) - // 40 + 1000 *( 0.05) * (10/20 * 0.9) - feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) - feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) - feesInVal1 := feesInNonProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(9, 1)) - feesInVal2 := feesInNonProposer.Mul(sdk.NewDec(20).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(8, 1)) - feesInVal3 := feesInNonProposer.Mul(sdk.NewDec(30).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(7, 1)) - feesInVal1Proposer := feesInProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(20))).Mul(sdk.NewDecWithPrec(9, 1)) - expRes := sdk.NewDec(40).Add(feesInVal1).Add(feesInVal2).Add(feesInVal3).Add(feesInVal1Proposer).TruncateInt() - require.True(sdk.IntEq(t, expRes, amt)) +func TestCalculateRewardsMultiDelegatorMultWithdraw(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, + sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission) + require.True(t, sh(ctx, msg).IsOK()) + + // end block to bond validator + staking.EndBlocker(ctx, sk) + + // fetch validator and delegation + val := sk.Validator(ctx, valOpAddr1) + del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // allocate some rewards + initial := int64(20) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + k.AllocateTokensToValidator(ctx, val, tokens) + + // historical count should be 2 (validator init, delegation init) + require.Equal(t, uint64(2), k.GetValidatorHistoricalReferenceCount(ctx)) + + // second delegation + msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100))) + require.True(t, sh(ctx, msg2).IsOK()) + + // historical count should be 3 (second delegation init) + require.Equal(t, uint64(3), k.GetValidatorHistoricalReferenceCount(ctx)) + + // fetch updated validator + val = sk.Validator(ctx, valOpAddr1) + del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) + + // end block + staking.EndBlocker(ctx, sk) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // first delegator withdraws + k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // second delegator withdraws + k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) + + // historical count should be 3 (validator init + two delegations) + require.Equal(t, uint64(3), k.GetValidatorHistoricalReferenceCount(ctx)) + + // validator withdraws commission + k.WithdrawValidatorCommission(ctx, valOpAddr1) + + // end period + endingPeriod := k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards for del1 + rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod) + + // rewards for del1 should be zero + require.True(t, rewards.IsZero()) + + // calculate delegation rewards for del2 + rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod) + + // rewards for del2 should be zero + require.True(t, rewards.IsZero()) + + // commission should be zero + require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // first delegator withdraws again + k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards for del1 + rewards = k.calculateDelegationRewards(ctx, val, del1, endingPeriod) + + // rewards for del1 should be zero + require.True(t, rewards.IsZero()) + + // calculate delegation rewards for del2 + rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod) + + // rewards for del2 should be 1/4 initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 4)}}, rewards) + + // commission should be half initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) + + // allocate some more rewards + k.AllocateTokensToValidator(ctx, val, tokens) + + // withdraw commission + k.WithdrawValidatorCommission(ctx, valOpAddr1) + + // end period + endingPeriod = k.incrementValidatorPeriod(ctx, val) + + // calculate delegation rewards for del1 + rewards = k.calculateDelegationRewards(ctx, val, del1, endingPeriod) + + // rewards for del1 should be 1/4 initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 4)}}, rewards) + + // calculate delegation rewards for del2 + rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod) + + // rewards for del2 should be 1/2 initial + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards) + + // commission should be zero + require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) } diff --git a/x/distribution/keeper/genesis.go b/x/distribution/keeper/genesis.go deleted file mode 100644 index 8e5a37abe2f4..000000000000 --- a/x/distribution/keeper/genesis.go +++ /dev/null @@ -1,50 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution/types" -) - -// Get the set of all validator-distribution-info's with no limits, used during genesis dump -func (k Keeper) GetAllValidatorDistInfos(ctx sdk.Context) (vdis []types.ValidatorDistInfo) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - var vdi types.ValidatorDistInfo - k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vdi) - vdis = append(vdis, vdi) - } - return vdis -} - -// Get the set of all delegator-distribution-info's with no limits, used during genesis dump -func (k Keeper) GetAllDelegationDistInfos(ctx sdk.Context) (ddis []types.DelegationDistInfo) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - var ddi types.DelegationDistInfo - k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &ddi) - ddis = append(ddis, ddi) - } - return ddis -} - -// Get the set of all delegator-withdraw addresses with no limits, used during genesis dump -func (k Keeper) GetAllDelegatorWithdrawInfos(ctx sdk.Context) (dwis []types.DelegatorWithdrawInfo) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegatorWithdrawInfoKey) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - dw := types.DelegatorWithdrawInfo{ - DelegatorAddr: GetDelegatorWithdrawInfoAddress(iterator.Key()), - WithdrawAddr: sdk.AccAddress(iterator.Value()), - } - dwis = append(dwis, dw) - } - return dwis -} diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 910f6eafa262..3973b36ede6a 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -1,146 +1,90 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution/types" ) -// Create a new validator distribution record -func (k Keeper) onValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - - // defensive check for existence - if k.HasValidatorDistInfo(ctx, valAddr) { - panic("validator dist info already exists (not cleaned up properly)") - } - - height := ctx.BlockHeight() - vdi := types.ValidatorDistInfo{ - OperatorAddr: valAddr, - FeePoolWithdrawalHeight: height, - DelAccum: types.NewTotalAccum(height), - DelPool: types.DecCoins{}, - ValCommission: types.DecCoins{}, - } - k.SetValidatorDistInfo(ctx, vdi) +// Wrapper struct +type Hooks struct { + k Keeper } -// Withdraw all validator rewards -func (k Keeper) onValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { - // Move the validator's rewards from the global pool to the validator's pools - // (dist info), but without actually withdrawing the rewards. This does not - // need to happen during the genesis block. - if ctx.BlockHeight() > 0 { - if err := k.takeValidatorFeePoolRewards(ctx, valAddr); err != nil { - panic(err) - } - } -} +var _ sdk.StakingHooks = Hooks{} -// Withdraw all validator rewards -func (k Keeper) onValidatorBonded(ctx sdk.Context, valAddr sdk.ValAddress) { - lastPower := k.stakeKeeper.GetLastValidatorPower(ctx, valAddr) - if !lastPower.Equal(sdk.ZeroInt()) { - panic("expected last power to be 0 for validator entering bonded state") - } - k.onValidatorModified(ctx, valAddr) -} +// Create new distribution hooks +func (k Keeper) Hooks() Hooks { return Hooks{k} } -// Sanity check, very useful! -func (k Keeper) onValidatorPowerDidChange(ctx sdk.Context, valAddr sdk.ValAddress) { - vi := k.GetValidatorDistInfo(ctx, valAddr) - if vi.FeePoolWithdrawalHeight != ctx.BlockHeight() { - panic(fmt.Sprintf("expected validator (%v) dist info FeePoolWithdrawalHeight to be updated to %v, but was %v.", - valAddr.String(), ctx.BlockHeight(), vi.FeePoolWithdrawalHeight)) - } +// nolint +func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + val := h.k.stakingKeeper.Validator(ctx, valAddr) + h.k.initializeValidator(ctx, val) } - -// Withdrawal all validator distribution rewards and cleanup the distribution record -func (k Keeper) onValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) { - k.RemoveValidatorDistInfo(ctx, valAddr) +func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { } +func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { + // force-withdraw commission + commission := h.k.GetValidatorAccumulatedCommission(ctx, valAddr) + if !commission.IsZero() { + coins, remainder := commission.TruncateDecimal() -//_________________________________________________________________________________________ + // remainder to community pool + feePool := h.k.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Plus(remainder) + h.k.SetFeePool(ctx, feePool) -// Create a new delegator distribution record -func (k Keeper) onDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) { + // update outstanding + outstanding := h.k.GetOutstandingRewards(ctx) + h.k.SetOutstandingRewards(ctx, outstanding.Minus(commission)) - ddi := types.DelegationDistInfo{ - DelegatorAddr: delAddr, - ValOperatorAddr: valAddr, - DelPoolWithdrawalHeight: ctx.BlockHeight(), - } - k.SetDelegationDistInfo(ctx, ddi) -} + // add to validator account + accAddr := sdk.AccAddress(valAddr) + withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr) -// Withdrawal all validator rewards -func (k Keeper) onDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) { - - if err := k.WithdrawDelegationReward(ctx, delAddr, valAddr); err != nil { - panic(err) - } -} - -// Withdrawal all validator distribution rewards and cleanup the distribution record -func (k Keeper) onDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, - valAddr sdk.ValAddress) { - // Withdraw validator commission when validator self-bond is removed. - // Because we maintain the invariant that all delegations must be removed - // before a validator is deleted, this ensures that commission will be withdrawn - // before the validator is deleted (and the corresponding ValidatorDistInfo removed). - // If we change other parts of the code such that a self-delegation might remain after - // a validator is deleted, this logic will no longer be safe. - // TODO: Consider instead implementing this in a "BeforeValidatorRemoved" hook. - if valAddr.Equals(sdk.ValAddress(delAddr)) { - feePool, commission := k.withdrawValidatorCommission(ctx, valAddr) - k.WithdrawToDelegator(ctx, feePool, delAddr, commission) + if _, _, err := h.k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + panic(err) + } } + // remove commission record + h.k.DeleteValidatorAccumulatedCommission(ctx, valAddr) - k.RemoveDelegationDistInfo(ctx, delAddr, valAddr) -} + // clear slashes + h.k.DeleteValidatorSlashEvents(ctx, valAddr) -//_________________________________________________________________________________________ + // clear historical rewards + h.k.DeleteValidatorHistoricalRewards(ctx, valAddr) -// Wrapper struct -type Hooks struct { - k Keeper + // clear current rewards + h.k.DeleteValidatorCurrentRewards(ctx, valAddr) } +func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + val := h.k.stakingKeeper.Validator(ctx, valAddr) -var _ sdk.StakingHooks = Hooks{} - -// New Validator Hooks -func (k Keeper) Hooks() Hooks { return Hooks{k} } - -// nolint -func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - h.k.onValidatorCreated(ctx, valAddr) -} -func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { - h.k.onValidatorModified(ctx, valAddr) + // increment period + h.k.incrementValidatorPeriod(ctx, val) } -func (h Hooks) OnValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorRemoved(ctx, valAddr) +func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + val := h.k.stakingKeeper.Validator(ctx, valAddr) + del := h.k.stakingKeeper.Delegation(ctx, delAddr, valAddr) + + // withdraw delegation rewards (which also increments period) + if err := h.k.withdrawDelegationRewards(ctx, val, del); err != nil { + panic(err) + } } -func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.k.onValidatorModified(ctx, valAddr) - h.k.onDelegationCreated(ctx, delAddr, valAddr) +func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + // nothing needed here since BeforeDelegationSharesModified will always also be called } -func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.k.onValidatorModified(ctx, valAddr) - h.k.onDelegationSharesModified(ctx, delAddr, valAddr) +func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + // create new delegation period record + h.k.initializeDelegation(ctx, valAddr, delAddr) } -func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - h.k.onDelegationRemoved(ctx, delAddr, valAddr) +func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { } -func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorModified(ctx, valAddr) +func (h Hooks) AfterValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { } -func (h Hooks) OnValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorBonded(ctx, valAddr) +func (h Hooks) AfterValidatorPowerDidChange(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { } -func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorPowerDidChange(ctx, valAddr) +func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { + // record the slash event + h.k.updateValidatorSlashFraction(ctx, valAddr, fraction) } diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index a6fed963586a..a40e37380de4 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -7,151 +7,92 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -// keeper of the stake store +// keeper of the staking store type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec paramSpace params.Subspace bankKeeper types.BankKeeper - stakeKeeper types.StakeKeeper + stakingKeeper types.StakingKeeper feeCollectionKeeper types.FeeCollectionKeeper // codespace codespace sdk.CodespaceType } +// create a new keeper func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ck types.BankKeeper, - sk types.StakeKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper { - + sk types.StakingKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, cdc: cdc, paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), bankKeeper: ck, - stakeKeeper: sk, + stakingKeeper: sk, feeCollectionKeeper: fck, codespace: codespace, } return keeper } -//______________________________________________________________________ - -// get the global fee pool distribution info -func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) { - store := ctx.KVStore(k.storeKey) - b := store.Get(FeePoolKey) - if b == nil { - panic("Stored fee pool should not have been nil") +// set withdraw address +func (k Keeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) sdk.Error { + if !k.GetWithdrawAddrEnabled(ctx) { + return types.ErrSetWithdrawAddrDisabled(k.codespace) } - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &feePool) - return -} -// set the global fee pool distribution info -func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinaryLengthPrefixed(feePool) - store.Set(FeePoolKey, b) -} - -// get the total validator accum for the ctx height -// in the fee pool -func (k Keeper) GetFeePoolValAccum(ctx sdk.Context) sdk.Dec { + k.SetDelegatorWithdrawAddr(ctx, delegatorAddr, withdrawAddr) - // withdraw self-delegation - height := ctx.BlockHeight() - totalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx)) - fp := k.GetFeePool(ctx) - return fp.GetTotalValAccum(height, totalPower) + return nil } -//______________________________________________________________________ - -// set the proposer public key for this block -func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsAddress) { - store := ctx.KVStore(k.storeKey) - - b := store.Get(ProposerKey) - if b == nil { - panic("Previous proposer not set") +// withdraw rewards from a delegation +func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Error { + val := k.stakingKeeper.Validator(ctx, valAddr) + if val == nil { + return types.ErrNoValidatorDistInfo(k.codespace) } - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &consAddr) - return -} - -// get the proposer public key for this block -func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinaryLengthPrefixed(consAddr) - store.Set(ProposerKey, b) -} - -//______________________________________________________________________ + del := k.stakingKeeper.Delegation(ctx, delAddr, valAddr) + if del == nil { + return types.ErrNoDelegationDistInfo(k.codespace) + } -// get context required for withdraw operations -func (k Keeper) GetWithdrawContext(ctx sdk.Context, - valOperatorAddr sdk.ValAddress) types.WithdrawContext { + // withdraw rewards + if err := k.withdrawDelegationRewards(ctx, val, del); err != nil { + return err + } - feePool := k.GetFeePool(ctx) - height := ctx.BlockHeight() - validator := k.stakeKeeper.Validator(ctx, valOperatorAddr) - lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, valOperatorAddr) - lastTotalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx)) + // reinitialize the delegation + k.initializeDelegation(ctx, valAddr, delAddr) - return types.NewWithdrawContext( - feePool, height, lastTotalPower, sdk.NewDecFromInt(lastValPower), - validator.GetCommission()) + return nil } -//______________________________________________________________________ -// PARAM STORE +// withdraw validator commission +func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Error { -// Type declaration for parameters -func ParamTypeTable() params.TypeTable { - return params.NewTypeTable( - ParamStoreKeyCommunityTax, sdk.Dec{}, - ParamStoreKeyBaseProposerReward, sdk.Dec{}, - ParamStoreKeyBonusProposerReward, sdk.Dec{}, - ) -} + // fetch validator accumulated commission + commission := k.GetValidatorAccumulatedCommission(ctx, valAddr) + if commission.IsZero() { + return types.ErrNoValidatorCommission(k.codespace) + } -// Returns the current CommunityTax rate from the global param store -// nolint: errcheck -func (k Keeper) GetCommunityTax(ctx sdk.Context) sdk.Dec { - var percent sdk.Dec - k.paramSpace.Get(ctx, ParamStoreKeyCommunityTax, &percent) - return percent -} + coins, remainder := commission.TruncateDecimal() -// nolint: errcheck -func (k Keeper) SetCommunityTax(ctx sdk.Context, percent sdk.Dec) { - k.paramSpace.Set(ctx, ParamStoreKeyCommunityTax, &percent) -} + // leave remainder to withdraw later + k.SetValidatorAccumulatedCommission(ctx, valAddr, remainder) -// Returns the current BaseProposerReward rate from the global param store -// nolint: errcheck -func (k Keeper) GetBaseProposerReward(ctx sdk.Context) sdk.Dec { - var percent sdk.Dec - k.paramSpace.Get(ctx, ParamStoreKeyBaseProposerReward, &percent) - return percent -} + // update outstanding + outstanding := k.GetOutstandingRewards(ctx) + k.SetOutstandingRewards(ctx, outstanding.Minus(sdk.NewDecCoins(coins))) -// nolint: errcheck -func (k Keeper) SetBaseProposerReward(ctx sdk.Context, percent sdk.Dec) { - k.paramSpace.Set(ctx, ParamStoreKeyBaseProposerReward, &percent) -} + accAddr := sdk.AccAddress(valAddr) + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr) -// Returns the current BaseProposerReward rate from the global param store -// nolint: errcheck -func (k Keeper) GetBonusProposerReward(ctx sdk.Context) sdk.Dec { - var percent sdk.Dec - k.paramSpace.Get(ctx, ParamStoreKeyBonusProposerReward, &percent) - return percent -} + if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { + return err + } -// nolint: errcheck -func (k Keeper) SetBonusProposerReward(ctx sdk.Context, percent sdk.Dec) { - k.paramSpace.Set(ctx, ParamStoreKeyBonusProposerReward, &percent) + return nil } diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go index a8c378424fe6..702946918298 100644 --- a/x/distribution/keeper/keeper_test.go +++ b/x/distribution/keeper/keeper_test.go @@ -9,30 +9,54 @@ import ( "github.com/cosmos/cosmos-sdk/x/distribution/types" ) -func TestSetGetPreviousProposerConsAddr(t *testing.T) { - ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) +func TestSetWithdrawAddr(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 1000) - keeper.SetPreviousProposerConsAddr(ctx, valConsAddr1) - res := keeper.GetPreviousProposerConsAddr(ctx) - require.True(t, res.Equals(valConsAddr1), "expected: %v got: %v", valConsAddr1.String(), res.String()) -} - -func TestSetGetCommunityTax(t *testing.T) { - ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + keeper.SetWithdrawAddrEnabled(ctx, false) - someDec := sdk.NewDec(333) - keeper.SetCommunityTax(ctx, someDec) - res := keeper.GetCommunityTax(ctx) - require.True(sdk.DecEq(t, someDec, res)) -} + err := keeper.SetWithdrawAddr(ctx, delAddr1, delAddr2) + require.NotNil(t, err) -func TestSetGetFeePool(t *testing.T) { - ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + keeper.SetWithdrawAddrEnabled(ctx, true) - fp := types.InitialFeePool() - fp.TotalValAccum.UpdateHeight = 777 + err = keeper.SetWithdrawAddr(ctx, delAddr1, delAddr2) + require.Nil(t, err) +} - keeper.SetFeePool(ctx, fp) - res := keeper.GetFeePool(ctx) - require.Equal(t, fp.TotalValAccum, res.TotalValAccum) +func TestWithdrawValidatorCommission(t *testing.T) { + ctx, ak, keeper, _, _ := CreateTestInputDefault(t, false, 1000) + + // set zero outstanding rewards + keeper.SetOutstandingRewards(ctx, types.OutstandingRewards{}) + + // check initial balance + balance := ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins() + require.Equal(t, balance, sdk.Coins{ + {"stake", sdk.NewInt(1000)}, + }) + + // set commission + keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr3, sdk.DecCoins{ + {"mytoken", sdk.NewDec(5).Quo(sdk.NewDec(4))}, + {"stake", sdk.NewDec(3).Quo(sdk.NewDec(2))}, + }) + + // withdraw commission + keeper.WithdrawValidatorCommission(ctx, valOpAddr3) + + // check balance increase + balance = ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins() + require.Equal(t, balance, sdk.Coins{ + {"mytoken", sdk.NewInt(1)}, + {"stake", sdk.NewInt(1001)}, + }) + + // check remainder + remainder := keeper.GetValidatorAccumulatedCommission(ctx, valOpAddr3) + require.Equal(t, remainder, sdk.DecCoins{ + {"mytoken", sdk.NewDec(1).Quo(sdk.NewDec(4))}, + {"stake", sdk.NewDec(1).Quo(sdk.NewDec(2))}, + }) + + require.True(t, true) } diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go index 443d13079e3c..1b8437b4cd95 100644 --- a/x/distribution/keeper/key.go +++ b/x/distribution/keeper/key.go @@ -1,55 +1,147 @@ package keeper import ( + "encoding/binary" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// keys/key-prefixes +const ( + // default paramspace for params keeper + DefaultParamspace = "distr" +) + +// keys var ( - FeePoolKey = []byte{0x00} // key for global distribution state - ValidatorDistInfoKey = []byte{0x01} // prefix for each key to a validator distribution - DelegationDistInfoKey = []byte{0x02} // prefix for each key to a delegation distribution - DelegatorWithdrawInfoKey = []byte{0x03} // prefix for each key to a delegator withdraw info - ProposerKey = []byte{0x04} // key for storing the proposer operator address + FeePoolKey = []byte{0x00} // key for global distribution state + ProposerKey = []byte{0x01} // key for the proposer operator address + OutstandingRewardsKey = []byte{0x02} // key for outstanding rewards + + DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address + DelegatorStartingInfoPrefix = []byte{0x04} // key for delegator starting info + ValidatorHistoricalRewardsPrefix = []byte{0x05} // key for historical validators rewards / stake + ValidatorCurrentRewardsPrefix = []byte{0x06} // key for current validator rewards + ValidatorAccumulatedCommissionPrefix = []byte{0x07} // key for accumulated validator commission + ValidatorSlashEventPrefix = []byte{0x08} // key for validator slash fraction - // params store ParamStoreKeyCommunityTax = []byte("communitytax") ParamStoreKeyBaseProposerReward = []byte("baseproposerreward") ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward") + ParamStoreKeyWithdrawAddrEnabled = []byte("withdrawaddrenabled") ) -const ( - // default paramspace for params keeper - DefaultParamspace = "distr" -) - -// gets the key for the validator distribution info from address -// VALUE: distribution/types.ValidatorDistInfo -func GetValidatorDistInfoKey(operatorAddr sdk.ValAddress) []byte { - return append(ValidatorDistInfoKey, operatorAddr.Bytes()...) +// gets an address from a delegator's withdraw info key +func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.AccAddress(addr) } -// gets the key for delegator distribution for a validator -// VALUE: distribution/types.DelegationDistInfo -func GetDelegationDistInfoKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { - return append(GetDelegationDistInfosKey(delAddr), valAddr.Bytes()...) +// gets the addresses from a delegator starting info key +func GetDelegatorStartingInfoAddresses(key []byte) (valAddr sdk.ValAddress, delAddr sdk.AccAddress) { + addr := key[1 : 1+sdk.AddrLen] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + valAddr = sdk.ValAddress(addr) + addr = key[1+sdk.AddrLen:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + delAddr = sdk.AccAddress(addr) + return } -// gets the prefix for a delegator's distributions across all validators -func GetDelegationDistInfosKey(delAddr sdk.AccAddress) []byte { - return append(DelegationDistInfoKey, delAddr.Bytes()...) +// gets the address & period from a validator's historical rewards key +func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddress, period uint64) { + addr := key[1 : 1+sdk.AddrLen] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + valAddr = sdk.ValAddress(addr) + b := key[1+sdk.AddrLen:] + if len(b) != 8 { + panic("unexpected key length") + } + period = binary.LittleEndian.Uint64(b) + return } -// gets the prefix for a delegator's withdraw info -func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { - return append(DelegatorWithdrawInfoKey, delAddr.Bytes()...) +// gets the address from a validator's current rewards key +func GetValidatorCurrentRewardsAddress(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) { +// gets the address from a validator's accumulated commission key +func GetValidatorAccumulatedCommissionAddress(key []byte) (valAddr sdk.ValAddress) { addr := key[1:] if len(addr) != sdk.AddrLen { panic("unexpected key length") } - return sdk.AccAddress(addr) + return sdk.ValAddress(addr) +} + +// gets the height from a validator's slash event key +func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, height uint64) { + addr := key[1 : 1+sdk.AddrLen] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + valAddr = sdk.ValAddress(addr) + b := key[1+sdk.AddrLen:] + if len(b) != 8 { + panic("unexpected key length") + } + height = binary.BigEndian.Uint64(b) + return +} + +// gets the key for a delegator's withdraw addr +func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { + return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...) +} + +// gets the key for a delegator's starting info +func GetDelegatorStartingInfoKey(v sdk.ValAddress, d sdk.AccAddress) []byte { + return append(append(DelegatorStartingInfoPrefix, v.Bytes()...), d.Bytes()...) +} + +// gets the prefix key for a validator's historical rewards +func GetValidatorHistoricalRewardsPrefix(v sdk.ValAddress) []byte { + return append(ValidatorHistoricalRewardsPrefix, v.Bytes()...) +} + +// gets the key for a validator's historical rewards +func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, k) + return append(append(ValidatorHistoricalRewardsPrefix, v.Bytes()...), b...) +} + +// gets the key for a validator's current rewards +func GetValidatorCurrentRewardsKey(v sdk.ValAddress) []byte { + return append(ValidatorCurrentRewardsPrefix, v.Bytes()...) +} + +// gets the key for a validator's current commission +func GetValidatorAccumulatedCommissionKey(v sdk.ValAddress) []byte { + return append(ValidatorAccumulatedCommissionPrefix, v.Bytes()...) +} + +// gets the prefix key for a validator's slash fractions +func GetValidatorSlashEventPrefix(v sdk.ValAddress) []byte { + return append(ValidatorSlashEventPrefix, v.Bytes()...) +} + +// gets the key for a validator's slash fraction +func GetValidatorSlashEventKey(v sdk.ValAddress, height uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, height) + return append(append(ValidatorSlashEventPrefix, v.Bytes()...), b...) } diff --git a/x/distribution/keeper/params.go b/x/distribution/keeper/params.go new file mode 100644 index 000000000000..fb1b23ed5833 --- /dev/null +++ b/x/distribution/keeper/params.go @@ -0,0 +1,68 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// type declaration for parameters +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyCommunityTax, sdk.Dec{}, + ParamStoreKeyBaseProposerReward, sdk.Dec{}, + ParamStoreKeyBonusProposerReward, sdk.Dec{}, + ParamStoreKeyWithdrawAddrEnabled, false, + ) +} + +// returns the current CommunityTax rate from the global param store +// nolint: errcheck +func (k Keeper) GetCommunityTax(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyCommunityTax, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetCommunityTax(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyCommunityTax, &percent) +} + +// returns the current BaseProposerReward rate from the global param store +// nolint: errcheck +func (k Keeper) GetBaseProposerReward(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyBaseProposerReward, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetBaseProposerReward(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyBaseProposerReward, &percent) +} + +// returns the current BaseProposerReward rate from the global param store +// nolint: errcheck +func (k Keeper) GetBonusProposerReward(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyBonusProposerReward, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetBonusProposerReward(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyBonusProposerReward, &percent) +} + +// returns the current WithdrawAddrEnabled +// nolint: errcheck +func (k Keeper) GetWithdrawAddrEnabled(ctx sdk.Context) bool { + var enabled bool + k.paramSpace.Get(ctx, ParamStoreKeyWithdrawAddrEnabled, &enabled) + return enabled +} + +// nolint: errcheck +func (k Keeper) SetWithdrawAddrEnabled(ctx sdk.Context, enabled bool) { + k.paramSpace.Set(ctx, ParamStoreKeyWithdrawAddrEnabled, &enabled) +} diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go new file mode 100644 index 000000000000..e1323aa34318 --- /dev/null +++ b/x/distribution/keeper/querier.go @@ -0,0 +1,177 @@ +package keeper + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// nolint +const ( + QueryParams = "params" + QueryOutstandingRewards = "outstanding_rewards" + QueryValidatorCommission = "validator_commission" + QueryValidatorSlashes = "validator_slashes" + QueryDelegationRewards = "delegation_rewards" + + ParamCommunityTax = "community_tax" + ParamBaseProposerReward = "base_proposer_reward" + ParamBonusProposerReward = "bonus_proposer_reward" + ParamWithdrawAddrEnabled = "withdraw_addr_enabled" +) + +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QueryParams: + return queryParams(ctx, path[1:], req, k) + case QueryOutstandingRewards: + return queryOutstandingRewards(ctx, path[1:], req, k) + case QueryValidatorCommission: + return queryValidatorCommission(ctx, path[1:], req, k) + case QueryValidatorSlashes: + return queryValidatorSlashes(ctx, path[1:], req, k) + case QueryDelegationRewards: + return queryDelegationRewards(ctx, path[1:], req, k) + default: + return nil, sdk.ErrUnknownRequest("unknown distr query endpoint") + } + } +} + +func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + switch path[0] { + case ParamCommunityTax: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetCommunityTax(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + case ParamBaseProposerReward: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetBaseProposerReward(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + case ParamBonusProposerReward: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetBonusProposerReward(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + case ParamWithdrawAddrEnabled: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetWithdrawAddrEnabled(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + default: + return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) + } +} + +func queryOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetOutstandingRewards(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// params for query 'custom/distr/validator_commission' +type QueryValidatorCommissionParams struct { + ValidatorAddr sdk.ValAddress `json:"validator_addr"` +} + +// creates a new instance of QueryValidatorCommissionParams +func NewQueryValidatorCommissionParams(validatorAddr sdk.ValAddress) QueryValidatorCommissionParams { + return QueryValidatorCommissionParams{ + ValidatorAddr: validatorAddr, + } +} + +func queryValidatorCommission(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryValidatorCommissionParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + commission := k.GetValidatorAccumulatedCommission(ctx, params.ValidatorAddr) + bz, err := codec.MarshalJSONIndent(k.cdc, commission) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// 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"` +} + +// creates a new instance of QueryValidatorSlashesParams +func NewQueryValidatorSlashesParams(validatorAddr sdk.ValAddress, startingHeight uint64, endingHeight uint64) QueryValidatorSlashesParams { + return QueryValidatorSlashesParams{ + ValidatorAddr: validatorAddr, + StartingHeight: startingHeight, + EndingHeight: endingHeight, + } +} + +func queryValidatorSlashes(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryValidatorSlashesParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + 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, + func(height uint64, event types.ValidatorSlashEvent) (stop bool) { + events = append(events, event) + return false + }, + ) + bz, err := codec.MarshalJSONIndent(k.cdc, events) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// params for query 'custom/distr/delegation_rewards' +type QueryDelegationRewardsParams struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.ValAddress `json:"validator_addr"` +} + +// creates a new instance of QueryDelegationRewardsParams +func NewQueryDelegationRewardsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryDelegationRewardsParams { + return QueryDelegationRewardsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + +func queryDelegationRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryDelegationRewardsParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + ctx, _ = ctx.CacheContext() + val := k.stakingKeeper.Validator(ctx, params.ValidatorAddr) + del := k.stakingKeeper.Delegation(ctx, params.DelegatorAddr, params.ValidatorAddr) + endingPeriod := k.incrementValidatorPeriod(ctx, val) + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + bz, err := codec.MarshalJSONIndent(k.cdc, rewards) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} diff --git a/x/distribution/keeper/querier_test.go b/x/distribution/keeper/querier_test.go new file mode 100644 index 000000000000..eae5324edcd6 --- /dev/null +++ b/x/distribution/keeper/querier_test.go @@ -0,0 +1,172 @@ +package keeper + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const custom = "custom" + +func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (communityTax sdk.Dec, baseProposerReward sdk.Dec, bonusProposerReward sdk.Dec, withdrawAddrEnabled bool) { + + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryParams, ParamCommunityTax}, "/"), + Data: []byte{}, + } + + bz, err := querier(ctx, []string{QueryParams, ParamCommunityTax}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &communityTax)) + + query = abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryParams, ParamBaseProposerReward}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{QueryParams, ParamBaseProposerReward}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &baseProposerReward)) + + query = abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryParams, ParamBonusProposerReward}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{QueryParams, ParamBonusProposerReward}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &bonusProposerReward)) + + query = abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryParams, ParamWithdrawAddrEnabled}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{QueryParams, ParamWithdrawAddrEnabled}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &withdrawAddrEnabled)) + + return +} + +func getQueriedOutstandingRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (outstandingRewards sdk.DecCoins) { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryOutstandingRewards}, "/"), + Data: []byte{}, + } + + bz, err := querier(ctx, []string{QueryOutstandingRewards}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &outstandingRewards)) + + return +} + +func getQueriedValidatorCommission(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, validatorAddr sdk.ValAddress) (validatorCommission sdk.DecCoins) { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryValidatorCommission}, "/"), + Data: cdc.MustMarshalJSON(NewQueryValidatorCommissionParams(validatorAddr)), + } + + bz, err := querier(ctx, []string{QueryValidatorCommission}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &validatorCommission)) + + return +} + +func getQueriedValidatorSlashes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, validatorAddr sdk.ValAddress, startHeight uint64, endHeight uint64) (slashes []types.ValidatorSlashEvent) { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryValidatorSlashes}, "/"), + Data: cdc.MustMarshalJSON(NewQueryValidatorSlashesParams(validatorAddr, startHeight, endHeight)), + } + + bz, err := querier(ctx, []string{QueryValidatorSlashes}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &slashes)) + + return +} + +func getQueriedDelegationRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) (rewards sdk.DecCoins) { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, QueryDelegationRewards}, "/"), + Data: cdc.MustMarshalJSON(NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr)), + } + + bz, err := querier(ctx, []string{QueryDelegationRewards}, query) + require.Nil(t, err) + require.Nil(t, cdc.UnmarshalJSON(bz, &rewards)) + + return +} + +func TestQueries(t *testing.T) { + cdc := codec.New() + ctx, _, keeper, sk, _ := CreateTestInputDefault(t, false, 100) + querier := NewQuerier(keeper) + + // test param queries + communityTax := sdk.NewDecWithPrec(3, 1) + baseProposerReward := sdk.NewDecWithPrec(2, 1) + bonusProposerReward := sdk.NewDecWithPrec(1, 1) + withdrawAddrEnabled := true + keeper.SetCommunityTax(ctx, communityTax) + keeper.SetBaseProposerReward(ctx, baseProposerReward) + keeper.SetBonusProposerReward(ctx, bonusProposerReward) + keeper.SetWithdrawAddrEnabled(ctx, withdrawAddrEnabled) + retCommunityTax, retBaseProposerReward, retBonusProposerReward, retWithdrawAddrEnabled := getQueriedParams(t, ctx, cdc, querier) + require.Equal(t, communityTax, retCommunityTax) + require.Equal(t, baseProposerReward, retBaseProposerReward) + require.Equal(t, bonusProposerReward, retBonusProposerReward) + require.Equal(t, withdrawAddrEnabled, retWithdrawAddrEnabled) + + // 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) + require.Equal(t, outstandingRewards, retOutstandingRewards) + + // test validator commission query + commission := sdk.DecCoins{{"token1", sdk.NewDec(4)}, {"token2", sdk.NewDec(2)}} + keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr1, commission) + retCommission := getQueriedValidatorCommission(t, ctx, cdc, querier, valOpAddr1) + require.Equal(t, commission, retCommission) + + // test validator slashes query with height range + slashOne := types.NewValidatorSlashEvent(3, sdk.NewDecWithPrec(5, 1)) + slashTwo := types.NewValidatorSlashEvent(7, sdk.NewDecWithPrec(6, 1)) + keeper.SetValidatorSlashEvent(ctx, valOpAddr1, 3, slashOne) + keeper.SetValidatorSlashEvent(ctx, valOpAddr1, 7, slashTwo) + slashes := getQueriedValidatorSlashes(t, ctx, cdc, querier, valOpAddr1, 0, 2) + require.Equal(t, 0, len(slashes)) + slashes = getQueriedValidatorSlashes(t, ctx, cdc, querier, valOpAddr1, 0, 5) + require.Equal(t, []types.ValidatorSlashEvent{slashOne}, slashes) + slashes = getQueriedValidatorSlashes(t, ctx, cdc, querier, valOpAddr1, 0, 10) + require.Equal(t, []types.ValidatorSlashEvent{slashOne, slashTwo}, slashes) + + // 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(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, comm) + require.True(t, sh(ctx, msg).IsOK()) + staking.EndBlocker(ctx, sk) + val := sk.Validator(ctx, valOpAddr1) + rewards := getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) + require.True(t, rewards.IsZero()) + initial := int64(10) + tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} + keeper.AllocateTokensToValidator(ctx, val, tokens) + rewards = getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) + require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards) +} diff --git a/x/distribution/keeper/store.go b/x/distribution/keeper/store.go new file mode 100644 index 000000000000..ef1f7c783c34 --- /dev/null +++ b/x/distribution/keeper/store.go @@ -0,0 +1,351 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// get the delegator withdraw address, defaulting to the delegator address +func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetDelegatorWithdrawAddrKey(delAddr)) + if b == nil { + return delAddr + } + return sdk.AccAddress(b) +} + +// set the delegator withdraw address +func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes()) +} + +// delete a delegator withdraw addr +func (k Keeper) DeleteDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegatorWithdrawAddrKey(delAddr)) +} + +// iterate over delegator withdraw addrs +func (k Keeper) IterateDelegatorWithdrawAddrs(ctx sdk.Context, handler func(del sdk.AccAddress, addr sdk.AccAddress) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, DelegatorWithdrawAddrPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + addr := sdk.AccAddress(iter.Value()) + del := GetDelegatorWithdrawInfoAddress(iter.Key()) + if handler(del, addr) { + break + } + } +} + +// get the global fee pool distribution info +func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(FeePoolKey) + if b == nil { + panic("Stored fee pool should not have been nil") + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &feePool) + return +} + +// set the global fee pool distribution info +func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(feePool) + store.Set(FeePoolKey, b) +} + +// get the proposer public key for this block +func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + b := store.Get(ProposerKey) + if b == nil { + panic("Previous proposer not set") + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &consAddr) + return +} + +// set the proposer public key for this block +func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(consAddr) + store.Set(ProposerKey, b) +} + +// get the starting info associated with a delegator +func (k Keeper) GetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) (period types.DelegatorStartingInfo) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetDelegatorStartingInfoKey(val, del)) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &period) + return +} + +// set the starting info associated with a delegator +func (k Keeper) SetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress, period types.DelegatorStartingInfo) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(period) + store.Set(GetDelegatorStartingInfoKey(val, del), b) +} + +// check existence of the starting info associated with a delegator +func (k Keeper) HasDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(GetDelegatorStartingInfoKey(val, del)) +} + +// delete the starting info associated with a delegator +func (k Keeper) DeleteDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegatorStartingInfoKey(val, del)) +} + +// iterate over delegator starting infos +func (k Keeper) IterateDelegatorStartingInfos(ctx sdk.Context, handler func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, DelegatorStartingInfoPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var info types.DelegatorStartingInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &info) + val, del := GetDelegatorStartingInfoAddresses(iter.Key()) + if handler(val, del, info) { + break + } + } +} + +// get historical rewards for a particular period +func (k Keeper) GetValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress, period uint64) (rewards types.ValidatorHistoricalRewards) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorHistoricalRewardsKey(val, period)) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards) + return +} + +// set historical rewards for a particular period +func (k Keeper) SetValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards) + store.Set(GetValidatorHistoricalRewardsKey(val, period), b) +} + +// iterate over historical rewards +func (k Keeper) IterateValidatorHistoricalRewards(ctx sdk.Context, handler func(val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var rewards types.ValidatorHistoricalRewards + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards) + addr, period := GetValidatorHistoricalRewardsAddressPeriod(iter.Key()) + if handler(addr, period, rewards) { + break + } + } +} + +// delete a historical reward +func (k Keeper) DeleteValidatorHistoricalReward(ctx sdk.Context, val sdk.ValAddress, period uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorHistoricalRewardsKey(val, period)) +} + +// delete historical rewards for a validator +func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, GetValidatorHistoricalRewardsPrefix(val)) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + +// delete all historical rewards +func (k Keeper) DeleteAllValidatorHistoricalRewards(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + +// historical reference count (used for testcases) +func (k Keeper) GetValidatorHistoricalReferenceCount(ctx sdk.Context) (count uint64) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var rewards types.ValidatorHistoricalRewards + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards) + count += uint64(rewards.ReferenceCount) + } + return +} + +// get current rewards for a validator +func (k Keeper) GetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorCurrentRewards) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorCurrentRewardsKey(val)) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards) + return +} + +// set current rewards for a validator +func (k Keeper) SetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress, rewards types.ValidatorCurrentRewards) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards) + store.Set(GetValidatorCurrentRewardsKey(val), b) +} + +// delete current rewards for a validator +func (k Keeper) DeleteValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorCurrentRewardsKey(val)) +} + +// iterate over current rewards +func (k Keeper) IterateValidatorCurrentRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorCurrentRewardsPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var rewards types.ValidatorCurrentRewards + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards) + addr := GetValidatorCurrentRewardsAddress(iter.Key()) + if handler(addr, rewards) { + break + } + } +} + +// get accumulated commission for a validator +func (k Keeper) GetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress) (commission types.ValidatorAccumulatedCommission) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorAccumulatedCommissionKey(val)) + if b == nil { + return types.ValidatorAccumulatedCommission{} + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &commission) + return +} + +// set accumulated commission for a validator +func (k Keeper) SetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(commission) + store.Set(GetValidatorAccumulatedCommissionKey(val), b) +} + +// delete accumulated commission for a validator +func (k Keeper) DeleteValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorAccumulatedCommissionKey(val)) +} + +// iterate over accumulated commissions +func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler func(val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorAccumulatedCommissionPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var commission types.ValidatorAccumulatedCommission + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &commission) + addr := GetValidatorAccumulatedCommissionAddress(iter.Key()) + if handler(addr, commission) { + break + } + } +} + +// get outstanding rewards +func (k Keeper) GetOutstandingRewards(ctx sdk.Context) (rewards types.OutstandingRewards) { + store := ctx.KVStore(k.storeKey) + b := store.Get(OutstandingRewardsKey) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards) + return +} + +// set outstanding rewards +func (k Keeper) SetOutstandingRewards(ctx sdk.Context, rewards types.OutstandingRewards) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards) + store.Set(OutstandingRewardsKey, b) +} + +// get slash event for height +func (k Keeper) GetValidatorSlashEvent(ctx sdk.Context, val sdk.ValAddress, height uint64) (event types.ValidatorSlashEvent, found bool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorSlashEventKey(val, height)) + if b == nil { + return types.ValidatorSlashEvent{}, false + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &event) + return event, true +} + +// set slash event for height +func (k Keeper) SetValidatorSlashEvent(ctx sdk.Context, val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(event) + store.Set(GetValidatorSlashEventKey(val, height), b) +} + +// iterate over slash events between heights, inclusive +func (k Keeper) IterateValidatorSlashEventsBetween(ctx sdk.Context, val sdk.ValAddress, startingHeight uint64, endingHeight uint64, + handler func(height uint64, event types.ValidatorSlashEvent) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := store.Iterator( + GetValidatorSlashEventKey(val, startingHeight), + GetValidatorSlashEventKey(val, endingHeight+1), + ) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var event types.ValidatorSlashEvent + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &event) + _, height := GetValidatorSlashEventAddressHeight(iter.Key()) + if handler(height, event) { + break + } + } +} + +// iterate over all slash events +func (k Keeper) IterateValidatorSlashEvents(ctx sdk.Context, handler func(val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + var event types.ValidatorSlashEvent + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &event) + val, height := GetValidatorSlashEventAddressHeight(iter.Key()) + if handler(val, height, event) { + break + } + } +} + +// delete slash events for a particular validator +func (k Keeper) DeleteValidatorSlashEvents(ctx sdk.Context, val sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, GetValidatorSlashEventPrefix(val)) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + +// delete all slash events +func (k Keeper) DeleteAllValidatorSlashEvents(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 7cc68fcc42d9..420f40ac4943 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -17,7 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) @@ -61,7 +61,7 @@ var ( func MakeTestCodec() *codec.Codec { var cdc = codec.New() bank.RegisterCodec(cdc) - stake.RegisterCodec(cdc) + staking.RegisterCodec(cdc) auth.RegisterCodec(cdc) sdk.RegisterCodec(cdc) codec.RegisterCrypto(cdc) @@ -72,7 +72,7 @@ func MakeTestCodec() *codec.Codec { // test input with default values func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( - sdk.Context, auth.AccountKeeper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { + sdk.Context, auth.AccountKeeper, Keeper, staking.Keeper, DummyFeeCollectionKeeper) { communityTax := sdk.NewDecWithPrec(2, 2) return CreateTestInputAdvanced(t, isCheckTx, initCoins, communityTax) @@ -81,22 +81,22 @@ func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( // hogpodge of all sorts of input required for testing func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, communityTax sdk.Dec) ( - sdk.Context, auth.AccountKeeper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { + sdk.Context, auth.AccountKeeper, Keeper, staking.Keeper, DummyFeeCollectionKeeper) { - keyDistr := sdk.NewKVStoreKey("distr") - keyStake := sdk.NewKVStoreKey("stake") - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keyAcc := sdk.NewKVStoreKey("acc") - keyFeeCollection := sdk.NewKVStoreKey("fee") - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") + keyDistr := sdk.NewKVStoreKey(types.StoreKey) + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) - ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) @@ -109,11 +109,11 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, pk := params.NewKeeper(cdc, keyParams, tkeyParams) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) - accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, auth.ProtoBaseAccount) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) ck := bank.NewBaseKeeper(accountKeeper) - sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - sk.SetPool(ctx, stake.InitialPool()) - sk.SetParams(ctx, stake.DefaultParams()) + sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + sk.SetPool(ctx, staking.InitialPool()) + sk.SetParams(ctx, staking.DefaultParams()) // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range addrs { @@ -122,7 +122,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, {sk.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, }) require.Nil(t, err) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + pool.NotBondedTokens = pool.NotBondedTokens.Add(sdk.NewInt(initCoins)) sk.SetPool(ctx, pool) } diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 8d861fef8043..05326ff12262 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -2,170 +2,110 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" ) -// check whether a validator has distribution info -func (k Keeper) HasValidatorDistInfo(ctx sdk.Context, - operatorAddr sdk.ValAddress) (exists bool) { - store := ctx.KVStore(k.storeKey) - return store.Has(GetValidatorDistInfoKey(operatorAddr)) -} +// initialize rewards for a new validator +func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) { + // set initial historical rewards (period 0) with reference count of 1 + k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), 0, types.NewValidatorHistoricalRewards(sdk.DecCoins{}, 1)) -// get the validator distribution info -func (k Keeper) GetValidatorDistInfo(ctx sdk.Context, - operatorAddr sdk.ValAddress) (vdi types.ValidatorDistInfo) { + // set current rewards (starting at period 1) + k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, 1)) - store := ctx.KVStore(k.storeKey) + // set accumulated commission + k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), types.InitialValidatorAccumulatedCommission()) +} - b := store.Get(GetValidatorDistInfoKey(operatorAddr)) - if b == nil { - panic("Stored validator-distribution info should not have been nil") +// increment validator period, returning the period just ended +func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uint64 { + // fetch current rewards + rewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) + + // calculate current ratio + var current sdk.DecCoins + if val.GetTokens().IsZero() { + + // 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) + k.SetFeePool(ctx, feePool) + k.SetOutstandingRewards(ctx, 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())) } - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &vdi) - return -} + // fetch historical rewards for last period + historical := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period-1).CumulativeRewardRatio -// set the validator distribution info -func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInfo) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinaryLengthPrefixed(vdi) - store.Set(GetValidatorDistInfoKey(vdi.OperatorAddr), b) -} + // decrement reference count + k.decrementReferenceCount(ctx, val.GetOperator(), rewards.Period-1) -// remove a validator distribution info -func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) { + // set new historical rewards with reference count of 1 + k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, types.NewValidatorHistoricalRewards(historical.Plus(current), 1)) - // defensive check - vdi := k.GetValidatorDistInfo(ctx, valAddr) - if vdi.DelAccum.Accum.IsPositive() { - panic("Should not delete validator with unwithdrawn delegator accum") - } - if !vdi.ValCommission.IsZero() { - panic("Should not delete validator with unwithdrawn validator commission") - } + // set current rewards, incrementing period by 1 + k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, rewards.Period+1)) - store := ctx.KVStore(k.storeKey) - store.Delete(GetValidatorDistInfoKey(valAddr)) + return rewards.Period } -// remove all validator distribution infos -func (k Keeper) RemoveValidatorDistInfos(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) +// increment the reference count for a historical rewards value +func (k Keeper) incrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) { + historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period) + if historical.ReferenceCount > 2 { + panic("reference count should never exceed 2") } + historical.ReferenceCount++ + k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical) } -// iterate over all the validator distribution infos -func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context, - fn func(index int64, distInfo types.ValidatorDistInfo) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey) - defer iter.Close() - index := int64(0) - for ; iter.Valid(); iter.Next() { - var vdi types.ValidatorDistInfo - k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vdi) - if fn(index, vdi) { - return - } - index++ +// decrement the reference count for a historical rewards value, and delete if zero references remain +func (k Keeper) decrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) { + historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period) + if historical.ReferenceCount == 0 { + panic("cannot set negative reference count") } -} - -// Get the calculated accum of a validator at the current block -// without affecting the state. -func (k Keeper) GetValidatorAccum(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Dec, sdk.Error) { - if !k.HasValidatorDistInfo(ctx, operatorAddr) { - return sdk.Dec{}, types.ErrNoValidatorDistInfo(k.codespace) + historical.ReferenceCount-- + if historical.ReferenceCount == 0 { + k.DeleteValidatorHistoricalReward(ctx, valAddr, period) + } else { + k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical) } - - // withdraw self-delegation - height := ctx.BlockHeight() - lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, operatorAddr) - valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) - accum := valInfo.GetValAccum(height, sdk.NewDecFromInt(lastValPower)) - - return accum, nil } -// takeValidatorFeePoolRewards updates the validator's distribution info -// from the global fee pool without withdrawing any rewards. This will be called -// from a onValidatorModified hook. -func (k Keeper) takeValidatorFeePoolRewards(ctx sdk.Context, operatorAddr sdk.ValAddress) sdk.Error { - if !k.HasValidatorDistInfo(ctx, operatorAddr) { - return types.ErrNoValidatorDistInfo(k.codespace) +func (k Keeper) updateValidatorSlashFraction(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { + if fraction.GT(sdk.OneDec()) { + panic("fraction greater than one") } - accAddr := sdk.AccAddress(operatorAddr.Bytes()) - - // withdraw reward for self-delegation - if k.HasDelegationDistInfo(ctx, accAddr, operatorAddr) { - fp, vi, di, withdraw := - k.withdrawDelegationReward(ctx, accAddr, operatorAddr) - k.SetFeePool(ctx, fp) - k.SetValidatorDistInfo(ctx, vi) - k.SetDelegationDistInfo(ctx, di) - k.WithdrawToDelegator(ctx, fp, accAddr, withdraw) + height := uint64(ctx.BlockHeight()) + currentFraction := sdk.ZeroDec() + endedPeriod := k.GetValidatorCurrentRewards(ctx, valAddr).Period - 1 + current, found := k.GetValidatorSlashEvent(ctx, valAddr, height) + if found { + // there has already been a slash event this height, + // and we don't need to store more than one, + // so just update the current slash fraction + currentFraction = current.Fraction + } else { + val := k.stakingKeeper.Validator(ctx, valAddr) + // increment current period + endedPeriod = k.incrementValidatorPeriod(ctx, val) + // increment reference count on period we need to track + k.incrementReferenceCount(ctx, valAddr, endedPeriod) } - - // withdrawal validator commission rewards - valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) - wc := k.GetWithdrawContext(ctx, operatorAddr) - valInfo, feePool := valInfo.TakeFeePoolRewards(wc) - k.SetFeePool(ctx, feePool) - k.SetValidatorDistInfo(ctx, valInfo) - return nil -} - -func (k Keeper) withdrawValidatorCommission(ctx sdk.Context, operatorAddr sdk.ValAddress) (types.FeePool, types.DecCoins) { - valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) - wc := k.GetWithdrawContext(ctx, operatorAddr) - valInfo, feePool, commission := valInfo.WithdrawCommission(wc) - k.SetValidatorDistInfo(ctx, valInfo) - - return feePool, commission -} - -// withdrawal all the validator rewards including the commission -func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) sdk.Error { - if !k.HasValidatorDistInfo(ctx, operatorAddr) { - return types.ErrNoValidatorDistInfo(k.codespace) + currentMultiplicand := sdk.OneDec().Sub(currentFraction) + newMultiplicand := sdk.OneDec().Sub(fraction) + updatedFraction := sdk.OneDec().Sub(currentMultiplicand.Mul(newMultiplicand)) + if updatedFraction.LT(sdk.ZeroDec()) { + panic("negative slash fraction") } - - // withdraw self-delegation - accAddr := sdk.AccAddress(operatorAddr.Bytes()) - withdraw := k.withdrawDelegationRewardsAll(ctx, accAddr) - - // withdrawal validator commission rewards - feePool, commission := k.withdrawValidatorCommission(ctx, operatorAddr) - withdraw = withdraw.Plus(commission) - - k.WithdrawToDelegator(ctx, feePool, accAddr, withdraw) - return nil -} - -// get all the validator rewards including the commission -func (k Keeper) CurrentValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Coins, sdk.Error) { - - if !k.HasValidatorDistInfo(ctx, operatorAddr) { - return sdk.Coins{}, types.ErrNoValidatorDistInfo(k.codespace) - } - - // withdraw self-delegation - accAddr := sdk.AccAddress(operatorAddr.Bytes()) - withdraw := k.CurrentDelegationRewardsAll(ctx, accAddr) - - // withdrawal validator commission rewards - valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) - - wc := k.GetWithdrawContext(ctx, operatorAddr) - commission := valInfo.CurrentCommissionRewards(wc) - withdraw = withdraw.Plus(commission) - truncated, _ := withdraw.TruncateDecimal() - return truncated, nil + k.SetValidatorSlashEvent(ctx, valAddr, height, types.NewValidatorSlashEvent(endedPeriod, updatedFraction)) } diff --git a/x/distribution/keeper/validator_test.go b/x/distribution/keeper/validator_test.go deleted file mode 100644 index 58079241cc42..000000000000 --- a/x/distribution/keeper/validator_test.go +++ /dev/null @@ -1,201 +0,0 @@ -package keeper - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" -) - -func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - // first make a validator - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw self-delegation reward - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) - amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) - expRes := sdk.NewDec(90).Add(sdk.NewDec(100)).TruncateInt() - require.True(sdk.IntEq(t, expRes, amt)) -} - -func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw self-delegation reward - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) - amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) - expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 - require.True(sdk.IntEq(t, expRes, amt)) -} - -func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator - commissionRate := sdk.NewDecWithPrec(1, 1) - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, commissionRate) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw validator reward - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) - amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) - commissionTaken := sdk.NewDec(100).Mul(commissionRate) - afterCommission := sdk.NewDec(100).Sub(commissionTaken) - selfDelegationReward := afterCommission.Quo(sdk.NewDec(2)) - expRes := sdk.NewDec(90).Add(commissionTaken).Add(selfDelegationReward).TruncateInt() // 90 + 100 tokens * 10/20 - require.True(sdk.IntEq(t, expRes, amt)) -} - -func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - // Make some validators with different commissions. - // Bond 10 of 100 with 0.1 commission. - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - // Bond 50 of 100 with 0.2 commission. - msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1)) - got = stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - // Bond 40 of 100 with 0.3 commission. - msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1)) - got = stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // Allocate 1000 denom of fees. - feeInputs := sdk.NewInt(1000) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - // Collect proposer reward for 100% of votes. - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // Withdraw validator reward. - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) - amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) - - feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) - feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) - // NOTE: the non-proposer rewards (95) and proposer rewards (50) add up to - // 145. During computation, this is further split into 130.5 and 14.5, - // which is the non-commission and commission respectively, but the - // commission is for self so the result is just 145. - expRes := sdk.NewDec(90). // orig tokens (100) - bonded (10) - Add(feesInNonProposer.Quo(sdk.NewDec(10))). // validator 1 has 1/10 total power (non-proposer rewards = 95) - Add(feesInProposer). // (proposer rewards = 50) - TruncateInt() - require.True(sdk.IntEq(t, expRes, amt)) -} - -func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) { - ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) - stakeHandler := stake.NewHandler(sk) - denom := sk.GetParams(ctx).BondDenom - - //first make a validator with 10% commission - commissionRate := sdk.NewDecWithPrec(1, 1) - msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( - valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) - got := stakeHandler(ctx, msgCreateValidator) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - - // delegate - msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) - require.Equal(t, int64(90), amt.Int64()) - - msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 20) - got = stakeHandler(ctx, msgDelegate) - require.True(t, got.IsOK()) - amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) - require.Equal(t, int64(80), amt.Int64()) - - // allocate 100 denom of fees - feeInputs := sdk.NewInt(100) - fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) - - // withdraw validator reward - ctx = ctx.WithBlockHeight(1) - keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) - amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) - - commissionTaken := sdk.NewDec(100).Mul(commissionRate) - afterCommission := sdk.NewDec(100).Sub(commissionTaken) - expRes := sdk.NewDec(90). - Add(afterCommission.Quo(sdk.NewDec(4))). - Add(commissionTaken). - TruncateInt() // 90 + 100*90% tokens * 10/40 - require.True(sdk.IntEq(t, expRes, amt)) -} diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 2f954d6e1204..2f33d02b2fa4 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -5,24 +5,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) // AllInvariants runs all invariants of the distribution module -// Currently: total supply, positive power -func AllInvariants(d distr.Keeper, stk stake.Keeper) simulation.Invariant { - sk := distr.StakeKeeper(stk) +func AllInvariants(d distr.Keeper, stk staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { - err := ValAccumInvariants(d, sk)(ctx) + err := CanWithdrawInvariant(d, stk)(ctx) if err != nil { return err } - err = DelAccumInvariants(d, sk)(ctx) + err = NonNegativeOutstandingInvariant(d)(ctx) if err != nil { return err } - err = CanWithdrawInvariant(d, stk)(ctx) + err = ReferenceCountInvariant(d, stk)(ctx) if err != nil { return err } @@ -30,146 +29,70 @@ func AllInvariants(d distr.Keeper, stk stake.Keeper) simulation.Invariant { } } -// ValAccumInvariants checks that the fee pool accum == sum all validators' accum -func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { - +// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative +func NonNegativeOutstandingInvariant(k distr.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { - height := ctx.BlockHeight() - - valAccum := sdk.ZeroDec() - k.IterateValidatorDistInfos(ctx, func(_ int64, vdi distr.ValidatorDistInfo) bool { - lastValPower := sk.GetLastValidatorPower(ctx, vdi.OperatorAddr) - valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower))) - return false - }) - - lastTotalPower := sdk.NewDecFromInt(sk.GetLastTotalPower(ctx)) - totalAccum := k.GetFeePool(ctx).GetTotalValAccum(height, lastTotalPower) - - if !totalAccum.Equal(valAccum) { - return fmt.Errorf("validator accum invariance: \n\tfee pool totalAccum: %v"+ - "\n\tvalidator accum \t%v\n", totalAccum.String(), valAccum.String()) + outstanding := k.GetOutstandingRewards(ctx) + if outstanding.HasNegative() { + return fmt.Errorf("negative outstanding coins: %v", outstanding) } - return nil } } -// DelAccumInvariants checks that each validator del accum == sum all delegators' accum -func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { - +// CanWithdrawInvariant checks that current rewards can be completely withdrawn +func CanWithdrawInvariant(k distr.Keeper, sk staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { - height := ctx.BlockHeight() - - totalDelAccumFromVal := make(map[string]sdk.Dec) // key is the valOpAddr string - totalDelAccum := make(map[string]sdk.Dec) - // iterate the validators - iterVal := func(_ int64, vdi distr.ValidatorDistInfo) bool { - key := vdi.OperatorAddr.String() - validator := sk.Validator(ctx, vdi.OperatorAddr) - totalDelAccumFromVal[key] = vdi.GetTotalDelAccum(height, - validator.GetDelegatorShares()) - - // also initialize the delegation map - totalDelAccum[key] = sdk.ZeroDec() + // 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 + }) + + // iterate over all current delegations, withdraw rewards + dels := sk.GetAllDelegations(ctx) + for _, delegation := range dels { + _ = k.WithdrawDelegationRewards(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) } - k.IterateValidatorDistInfos(ctx, iterVal) - - // iterate the delegations - iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool { - key := ddi.ValOperatorAddr.String() - delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr) - totalDelAccum[key] = totalDelAccum[key].Add( - ddi.GetDelAccum(height, delegation.GetShares())) - return false - } - k.IterateDelegationDistInfos(ctx, iterDel) - - // compare - for key, delAccumFromVal := range totalDelAccumFromVal { - sumDelAccum := totalDelAccum[key] - - if !sumDelAccum.Equal(delAccumFromVal) { - - logDelAccums := "" - iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool { - keyLog := ddi.ValOperatorAddr.String() - if keyLog == key { - delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr) - accum := ddi.GetDelAccum(height, delegation.GetShares()) - if accum.IsPositive() { - logDelAccums += fmt.Sprintf("\n\t\tdel: %v, accum: %v, power: %v", - ddi.DelegatorAddr.String(), - accum.String(), - delegation.GetShares()) - } - } - return false - } - k.IterateDelegationDistInfos(ctx, iterDel) - - operAddr, err := sdk.ValAddressFromBech32(key) - if err != nil { - panic(err) - } - validator := sk.Validator(ctx, operAddr) - - return fmt.Errorf("delegator accum invariance: \n"+ - "\tvalidator key: %v\n"+ - "\tvalidator: %+v\n"+ - "\tsum delegator accum: %v\n"+ - "\tvalidator's total delegator accum: %v\n"+ - "\tlog of delegations with accum: %v\n", - key, validator, sumDelAccum.String(), - delAccumFromVal.String(), logDelAccums) - } + + remaining := k.GetOutstandingRewards(ctx) + + if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) { + return fmt.Errorf("negative remaining coins: %v", remaining) } return nil } } -// CanWithdrawInvariant checks that current rewards can be completely withdrawn -func CanWithdrawInvariant(k distr.Keeper, sk stake.Keeper) simulation.Invariant { +// ReferenceCountInvariant checks that the number of historical rewards records is correct +func ReferenceCountInvariant(k distr.Keeper, sk staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { - // we don't want to write the changes - ctx, _ = ctx.CacheContext() - // withdraw all delegator & validator rewards - vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { - err := k.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) - if err != nil { - panic(err) - } + valCount := uint64(0) + sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + valCount++ return false - } - k.IterateValidatorDistInfos(ctx, vdiIter) - - ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { - err := k.WithdrawDelegationReward( - ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) - if err != nil { - panic(err) - } + }) + dels := sk.GetAllDelegations(ctx) + slashCount := uint64(0) + k.IterateValidatorSlashEvents(ctx, func(_ sdk.ValAddress, _ uint64, _ types.ValidatorSlashEvent) (stop bool) { + slashCount++ return false - } - k.IterateDelegationDistInfos(ctx, ddiIter) + }) - // assert that the fee pool is empty - feePool := k.GetFeePool(ctx) - if !feePool.TotalValAccum.Accum.IsZero() { - return fmt.Errorf("unexpected leftover validator accum") - } - bondDenom := sk.GetParams(ctx).BondDenom - if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - return fmt.Errorf("unexpected leftover validator pool coins: %v", - feePool.ValPool.AmountOf(bondDenom).String()) + // one record per validator (last tracked period), one record per delegation (previous period), one record per slash (previous period) + expected := valCount + uint64(len(dels)) + slashCount + count := k.GetValidatorHistoricalReferenceCount(ctx) + + if count != expected { + return fmt.Errorf("unexpected number of historical rewards records: expected %v (%v vals + %v dels + %v slashes), got %v", expected, valCount, len(dels), slashCount, count) } - // all ok return nil } } diff --git a/x/distribution/simulation/msgs.go b/x/distribution/simulation/msgs.go index 62881c5f958e..9f979d86d47c 100644 --- a/x/distribution/simulation/msgs.go +++ b/x/distribution/simulation/msgs.go @@ -39,33 +39,6 @@ func SimulateMsgSetWithdrawAddress(m auth.AccountKeeper, k distribution.Keeper) } } -// SimulateMsgWithdrawDelegatorRewardsAll -func SimulateMsgWithdrawDelegatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { - handler := distribution.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accs []simulation.Account, event func(string)) ( - action string, fOp []simulation.FutureOperation, err error) { - - account := simulation.RandomAcc(r, accs) - msg := distribution.NewMsgWithdrawDelegatorRewardsAll(account.Address) - - if msg.ValidateBasic() != nil { - return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - } - - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - if result.IsOK() { - write() - } - - event(fmt.Sprintf("distribution/MsgWithdrawDelegatorRewardsAll/%v", result.IsOK())) - - action = fmt.Sprintf("TestMsgWithdrawDelegatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) - return action, nil, nil - } -} - // SimulateMsgWithdrawDelegatorReward func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { handler := distribution.NewHandler(k) @@ -94,15 +67,15 @@ func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Kee } } -// SimulateMsgWithdrawValidatorRewardsAll -func SimulateMsgWithdrawValidatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { +// SimulateMsgWithdrawValidatorCommission +func SimulateMsgWithdrawValidatorCommission(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { handler := distribution.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { account := simulation.RandomAcc(r, accs) - msg := distribution.NewMsgWithdrawValidatorRewardsAll(sdk.ValAddress(account.Address)) + msg := distribution.NewMsgWithdrawValidatorCommission(sdk.ValAddress(account.Address)) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) @@ -114,9 +87,9 @@ func SimulateMsgWithdrawValidatorRewardsAll(m auth.AccountKeeper, k distribution write() } - event(fmt.Sprintf("distribution/MsgWithdrawValidatorRewardsAll/%v", result.IsOK())) + event(fmt.Sprintf("distribution/MsgWithdrawValidatorCommission/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgWithdrawValidatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgWithdrawValidatorCommission: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil } } diff --git a/x/distribution/types/codec.go b/x/distribution/types/codec.go index 10b2dc2fb5ea..30ae415720f0 100644 --- a/x/distribution/types/codec.go +++ b/x/distribution/types/codec.go @@ -6,9 +6,8 @@ import ( // Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgWithdrawDelegatorRewardsAll{}, "cosmos-sdk/MsgWithdrawDelegationRewardsAll", nil) cdc.RegisterConcrete(MsgWithdrawDelegatorReward{}, "cosmos-sdk/MsgWithdrawDelegationReward", nil) - cdc.RegisterConcrete(MsgWithdrawValidatorRewardsAll{}, "cosmos-sdk/MsgWithdrawValidatorRewardsAll", nil) + cdc.RegisterConcrete(MsgWithdrawValidatorCommission{}, "cosmos-sdk/MsgWithdrawValidatorCommission", nil) cdc.RegisterConcrete(MsgSetWithdrawAddress{}, "cosmos-sdk/MsgModifyWithdrawAddress", nil) } diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go deleted file mode 100644 index 8a9e9f0d0be1..000000000000 --- a/x/distribution/types/dec_coin.go +++ /dev/null @@ -1,205 +0,0 @@ -package types - -import ( - "fmt" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Coins which can have additional decimal points -type DecCoin struct { - Denom string `json:"denom"` - Amount sdk.Dec `json:"amount"` -} - -func NewDecCoin(denom string, amount int64) DecCoin { - return DecCoin{ - Denom: denom, - Amount: sdk.NewDec(amount), - } -} - -func NewDecCoinFromDec(denom string, amount sdk.Dec) DecCoin { - return DecCoin{ - Denom: denom, - Amount: amount, - } -} - -func NewDecCoinFromCoin(coin sdk.Coin) DecCoin { - return DecCoin{ - Denom: coin.Denom, - Amount: sdk.NewDecFromInt(coin.Amount), - } -} - -// Adds amounts of two coins with same denom -func (coin DecCoin) Plus(coinB DecCoin) DecCoin { - if coin.Denom != coinB.Denom { - panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) - } - return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)} -} - -// Subtracts amounts of two coins with same denom -func (coin DecCoin) Minus(coinB DecCoin) DecCoin { - if coin.Denom != coinB.Denom { - panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) - } - return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} -} - -// return the decimal coins with trunctated decimals, and return the change -func (coin DecCoin) TruncateDecimal() (sdk.Coin, DecCoin) { - truncated := coin.Amount.TruncateInt() - change := coin.Amount.Sub(sdk.NewDecFromInt(truncated)) - return sdk.NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change} -} - -//_______________________________________________________________________ - -// coins with decimal -type DecCoins []DecCoin - -func NewDecCoins(coins sdk.Coins) DecCoins { - dcs := make(DecCoins, len(coins)) - for i, coin := range coins { - dcs[i] = NewDecCoinFromCoin(coin) - } - return dcs -} - -// return the coins with trunctated decimals, and return the change -func (coins DecCoins) TruncateDecimal() (sdk.Coins, DecCoins) { - changeSum := DecCoins{} - out := make(sdk.Coins, len(coins)) - for i, coin := range coins { - truncated, change := coin.TruncateDecimal() - out[i] = truncated - changeSum = changeSum.Plus(DecCoins{change}) - } - return out, changeSum -} - -// Plus combines two sets of coins -// CONTRACT: Plus will never return Coins where one Coin has a 0 amount. -func (coins DecCoins) Plus(coinsB DecCoins) DecCoins { - sum := ([]DecCoin)(nil) - indexA, indexB := 0, 0 - lenA, lenB := len(coins), len(coinsB) - for { - if indexA == lenA { - if indexB == lenB { - return sum - } - return append(sum, coinsB[indexB:]...) - } else if indexB == lenB { - return append(sum, coins[indexA:]...) - } - coinA, coinB := coins[indexA], coinsB[indexB] - switch strings.Compare(coinA.Denom, coinB.Denom) { - case -1: - sum = append(sum, coinA) - indexA++ - case 0: - if coinA.Amount.Add(coinB.Amount).IsZero() { - // ignore 0 sum coin type - } else { - sum = append(sum, coinA.Plus(coinB)) - } - indexA++ - indexB++ - case 1: - sum = append(sum, coinB) - indexB++ - } - } -} - -// Negative returns a set of coins with all amount negative -func (coins DecCoins) Negative() DecCoins { - res := make([]DecCoin, 0, len(coins)) - for _, coin := range coins { - res = append(res, DecCoin{ - Denom: coin.Denom, - Amount: coin.Amount.Neg(), - }) - } - return res -} - -// Minus subtracts a set of coins from another (adds the inverse) -func (coins DecCoins) Minus(coinsB DecCoins) DecCoins { - return coins.Plus(coinsB.Negative()) -} - -// multiply all the coins by a decimal -func (coins DecCoins) MulDec(d sdk.Dec) DecCoins { - res := make([]DecCoin, len(coins)) - for i, coin := range coins { - product := DecCoin{ - Denom: coin.Denom, - Amount: coin.Amount.Mul(d), - } - res[i] = product - } - return res -} - -// divide all the coins by a decimal -func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins { - res := make([]DecCoin, len(coins)) - for i, coin := range coins { - quotient := DecCoin{ - Denom: coin.Denom, - Amount: coin.Amount.Quo(d), - } - res[i] = quotient - } - return res -} - -// returns the amount of a denom from deccoins -func (coins DecCoins) AmountOf(denom string) sdk.Dec { - switch len(coins) { - case 0: - return sdk.ZeroDec() - case 1: - coin := coins[0] - if coin.Denom == denom { - return coin.Amount - } - return sdk.ZeroDec() - default: - midIdx := len(coins) / 2 // binary search - coin := coins[midIdx] - if denom < coin.Denom { - return coins[:midIdx].AmountOf(denom) - } else if denom == coin.Denom { - return coin.Amount - } else { - return coins[midIdx+1:].AmountOf(denom) - } - } -} - -// has a negative DecCoin amount -func (coins DecCoins) HasNegative() bool { - for _, coin := range coins { - if coin.Amount.IsNegative() { - return true - } - } - return false -} - -// return whether all coins are zero -func (coins DecCoins) IsZero() bool { - for _, coin := range coins { - if !coin.Amount.IsZero() { - return false - } - } - return true -} diff --git a/x/distribution/types/dec_coin_test.go b/x/distribution/types/dec_coin_test.go deleted file mode 100644 index 9b942f07cebc..000000000000 --- a/x/distribution/types/dec_coin_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestPlusDecCoin(t *testing.T) { - decCoinA1 := DecCoin{"A", sdk.NewDecWithPrec(11, 1)} - decCoinA2 := DecCoin{"A", sdk.NewDecWithPrec(22, 1)} - decCoinB1 := DecCoin{"B", sdk.NewDecWithPrec(11, 1)} - - // regular add - res := decCoinA1.Plus(decCoinA1) - require.Equal(t, decCoinA2, res, "sum of coins is incorrect") - - // bad denom add - assert.Panics(t, func() { - decCoinA1.Plus(decCoinB1) - }, "expected panic on sum of different denoms") - -} - -func TestPlusCoins(t *testing.T) { - one := sdk.NewDec(1) - zero := sdk.NewDec(0) - negone := sdk.NewDec(-1) - two := sdk.NewDec(2) - - cases := []struct { - inputOne DecCoins - inputTwo DecCoins - expected DecCoins - }{ - {DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", two}, {"B", two}}}, - {DecCoins{{"A", zero}, {"B", one}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"B", one}}}, - {DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins(nil)}, - {DecCoins{{"A", one}, {"B", zero}}, DecCoins{{"A", negone}, {"B", zero}}, DecCoins(nil)}, - {DecCoins{{"A", negone}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", negone}}}, - } - - for tcIndex, tc := range cases { - res := tc.inputOne.Plus(tc.inputTwo) - require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) - } -} diff --git a/x/distribution/types/delegation_info.go b/x/distribution/types/delegation_info.go deleted file mode 100644 index 6068752f21c3..000000000000 --- a/x/distribution/types/delegation_info.go +++ /dev/null @@ -1,122 +0,0 @@ -package types - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// distribution info for a delegation - used to determine entitled rewards -type DelegationDistInfo struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValOperatorAddr sdk.ValAddress `json:"val_operator_addr"` - DelPoolWithdrawalHeight int64 `json:"del_pool_withdrawal_height"` // last time this delegation withdrew rewards -} - -func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, - currentHeight int64) DelegationDistInfo { - - return DelegationDistInfo{ - DelegatorAddr: delegatorAddr, - ValOperatorAddr: valOperatorAddr, - DelPoolWithdrawalHeight: currentHeight, - } -} - -// Get the calculated accum of this delegator at the provided height -func (di DelegationDistInfo) GetDelAccum(height int64, delegatorShares sdk.Dec) sdk.Dec { - blocks := height - di.DelPoolWithdrawalHeight - accum := delegatorShares.MulInt(sdk.NewInt(blocks)) - - // defensive check - if accum.IsNegative() { - panic(fmt.Sprintf("negative accum: %v\n"+ - "\theight: %v\n"+ - "\tdelegation_dist_info: %v\n"+ - "\tdelegator_shares: %v\n", - accum.String(), height, di, delegatorShares)) - } - return accum -} - -// Withdraw rewards from delegator. -// Among many things, it does: -// * updates validator info's total del accum -// * calls vi.TakeFeePoolRewards, which: -// * updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0 -// * updates fee pool to latest height and total val accum w/ given totalBonded -// (see comment on TakeFeePoolRewards for more info) -func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDistInfo, - totalDelShares, delegatorShares sdk.Dec) ( - DelegationDistInfo, ValidatorDistInfo, FeePool, DecCoins) { - - fp := wc.FeePool - vi = vi.UpdateTotalDelAccum(wc.Height, totalDelShares) - - // Break out to prevent a divide by zero. - if vi.DelAccum.Accum.IsZero() { - di.DelPoolWithdrawalHeight = wc.Height - return di, vi, fp, DecCoins{} - } - - vi, fp = vi.TakeFeePoolRewards(wc) - - accum := di.GetDelAccum(wc.Height, delegatorShares) - di.DelPoolWithdrawalHeight = wc.Height - - withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) - - // Clip withdrawal tokens by pool, due to possible rounding errors. - // This rounding error may be introduced upon multiplication since - // we're clipping decimal digits, and then when we divide by a number ~1 or - // < 1, the error doesn't get "buried", and if << 1 it'll get amplified. - // more: https://github.com/cosmos/cosmos-sdk/issues/2888#issuecomment-441387987 - for i, decCoin := range withdrawalTokens { - poolDenomAmount := vi.DelPool.AmountOf(decCoin.Denom) - if decCoin.Amount.GT(poolDenomAmount) { - withdrawalTokens[i] = NewDecCoinFromDec(decCoin.Denom, poolDenomAmount) - } - } - - // defensive check for impossible accum ratios - if accum.GT(vi.DelAccum.Accum) { - panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n"+ - "\taccum\t\t\t%v\n"+ - "\tvi.DelAccum.Accum\t%v\n", - accum, vi.DelAccum.Accum)) - } - - remDelPool := vi.DelPool.Minus(withdrawalTokens) - - // defensive check - if remDelPool.HasNegative() { - panic(fmt.Sprintf("negative remDelPool: %v\n"+ - "\tvi.DelPool\t\t%v\n"+ - "\taccum\t\t\t%v\n"+ - "\tvi.DelAccum.Accum\t%v\n"+ - "\twithdrawalTokens\t%v\n", - remDelPool, vi.DelPool, accum, - vi.DelAccum.Accum, withdrawalTokens)) - } - - vi.DelPool = remDelPool - vi.DelAccum.Accum = vi.DelAccum.Accum.Sub(accum) - - return di, vi, fp, withdrawalTokens -} - -// get the delegators rewards at this current state, -func (di DelegationDistInfo) CurrentRewards(wc WithdrawContext, vi ValidatorDistInfo, - totalDelShares, delegatorShares sdk.Dec) DecCoins { - - totalDelAccum := vi.GetTotalDelAccum(wc.Height, totalDelShares) - - if vi.DelAccum.Accum.IsZero() { - return DecCoins{} - } - - rewards := vi.CurrentPoolRewards(wc) - accum := di.GetDelAccum(wc.Height, delegatorShares) - tokens := rewards.MulDec(accum).QuoDec(totalDelAccum) - return tokens -} diff --git a/x/distribution/types/delegation_info_test.go b/x/distribution/types/delegation_info_test.go deleted file mode 100644 index 5619fb4d13b4..000000000000 --- a/x/distribution/types/delegation_info_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestWithdrawRewards(t *testing.T) { - - // initialize - height := int64(0) - fp := InitialFeePool() - vi := NewValidatorDistInfo(valAddr1, height) - commissionRate := sdk.NewDecWithPrec(2, 2) - validatorTokens := sdk.NewDec(10) - validatorDelShares := sdk.NewDec(10) - totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power - - di1 := NewDelegationDistInfo(delAddr1, valAddr1, height) - di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator - - di2 := NewDelegationDistInfo(delAddr2, valAddr1, height) - di2Shares := sdk.NewDec(5) - - // simulate adding some stake for inflation - height = 10 - fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} - - // withdraw rewards - wc := NewWithdrawContext(fp, height, - totalBondedTokens, validatorTokens, commissionRate) - di1, vi, fp, rewardRecv1 := di1.WithdrawRewards(wc, vi, - validatorDelShares, di1Shares) - - assert.Equal(t, height, di1.DelPoolWithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), rewardRecv1[0].Amount)) - - // add more blocks and inflation - height = 20 - fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) - - // withdraw rewards - wc = NewWithdrawContext(fp, height, - totalBondedTokens, validatorTokens, commissionRate) - di2, vi, fp, rewardRecv2 := di2.WithdrawRewards(wc, vi, - validatorDelShares, di2Shares) - - assert.Equal(t, height, di2.DelPoolWithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.ValCommission[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount)) -} diff --git a/x/distribution/types/delegator.go b/x/distribution/types/delegator.go new file mode 100644 index 000000000000..550dab1f14a2 --- /dev/null +++ b/x/distribution/types/delegator.go @@ -0,0 +1,27 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// starting info for a delegator reward period +// tracks the previous validator period, the delegation's amount +// of staking token, and the creation height (to check later on +// if any slashes have occurred) +// NOTE that even though validators are slashed to whole staking tokens, the +// delegators within the validator may be left with less than a full token, +// thus sdk.Dec is used +type DelegatorStartingInfo struct { + PreviousPeriod uint64 `json:"previous_period"` // period at which the delegation should withdraw starting from + Stake sdk.Dec `json:"stake"` // amount of staking token delegated + Height uint64 `json:"height"` // height at which delegation was created +} + +// create a new DelegatorStartingInfo +func NewDelegatorStartingInfo(previousPeriod uint64, stake sdk.Dec, height uint64) DelegatorStartingInfo { + return DelegatorStartingInfo{ + PreviousPeriod: previousPeriod, + Stake: stake, + Height: height, + } +} diff --git a/x/distribution/types/errors.go b/x/distribution/types/errors.go index 36de11f740c8..907ad226e205 100644 --- a/x/distribution/types/errors.go +++ b/x/distribution/types/errors.go @@ -8,9 +8,11 @@ import ( type CodeType = sdk.CodeType const ( - DefaultCodespace sdk.CodespaceType = "DISTR" - CodeInvalidInput CodeType = 103 - CodeNoDistributionInfo CodeType = 104 + DefaultCodespace sdk.CodespaceType = "DISTR" + CodeInvalidInput CodeType = 103 + CodeNoDistributionInfo CodeType = 104 + CodeNoValidatorCommission CodeType = 105 + CodeSetWithdrawAddrDisabled CodeType = 106 ) func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { @@ -28,3 +30,9 @@ func ErrNoDelegationDistInfo(codespace sdk.CodespaceType) sdk.Error { func ErrNoValidatorDistInfo(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeNoDistributionInfo, "no validator distribution info") } +func ErrNoValidatorCommission(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNoValidatorCommission, "no validator commission to withdraw") +} +func ErrSetWithdrawAddrDisabled(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeSetWithdrawAddrDisabled, "set withdraw address disabled") +} diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go index 2f921220c772..1bf2083c93c1 100644 --- a/x/distribution/types/fee_pool.go +++ b/x/distribution/types/fee_pool.go @@ -8,46 +8,18 @@ import ( // global fee pool for distribution type FeePool struct { - TotalValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators - ValPool DecCoins `json:"val_pool"` // funds for all validators which have yet to be withdrawn - CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent -} - -// update total validator accumulation factor -// NOTE: Do not call this except from ValidatorDistInfo.TakeFeePoolRewards(). -func (f FeePool) UpdateTotalValAccum(height int64, totalBondedTokens sdk.Dec) FeePool { - f.TotalValAccum = f.TotalValAccum.UpdateForNewHeight(height, totalBondedTokens) - return f -} - -// get the total validator accum for the fee pool without modifying the state -func (f FeePool) GetTotalValAccum(height int64, totalBondedTokens sdk.Dec) sdk.Dec { - return f.TotalValAccum.GetAccum(height, totalBondedTokens) + CommunityPool sdk.DecCoins `json:"community_pool"` // pool for community funds yet to be spent } // zero fee pool func InitialFeePool() FeePool { return FeePool{ - TotalValAccum: NewTotalAccum(0), - ValPool: DecCoins{}, - CommunityPool: DecCoins{}, + CommunityPool: sdk.DecCoins{}, } } // ValidateGenesis validates the fee pool for a genesis state func (f FeePool) ValidateGenesis() error { - if f.TotalValAccum.Accum.IsNegative() { - return fmt.Errorf("negative accum in distribution fee pool, is %v", - f.TotalValAccum.Accum.String()) - } - if f.TotalValAccum.UpdateHeight < 0 { - return fmt.Errorf("negative update height in distribution fee pool, is %v", - f.TotalValAccum.UpdateHeight) - } - if f.ValPool.HasNegative() { - return fmt.Errorf("negative ValPool in distribution fee pool, is %v", - f.ValPool) - } if f.CommunityPool.HasNegative() { return fmt.Errorf("negative CommunityPool in distribution fee pool, is %v", f.CommunityPool) @@ -55,3 +27,8 @@ 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/fee_pool_test.go b/x/distribution/types/fee_pool_test.go index ceb908d7f70d..bc85e56df2bf 100644 --- a/x/distribution/types/fee_pool_test.go +++ b/x/distribution/types/fee_pool_test.go @@ -8,13 +8,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func TestUpdateTotalValAccum(t *testing.T) { +func TestValidateGenesis(t *testing.T) { fp := InitialFeePool() + require.Nil(t, fp.ValidateGenesis()) - fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3)) - require.True(sdk.DecEq(t, sdk.NewDec(15), fp.TotalValAccum.Accum)) + fp2 := FeePool{CommunityPool: sdk.DecCoins{{"stake", sdk.NewDec(-1)}}} + require.NotNil(t, fp2.ValidateGenesis()) - fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2)) - require.True(sdk.DecEq(t, sdk.NewDec(21), fp.TotalValAccum.Accum)) } diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go index a94eb7005fb6..2ae3bd6c6199 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -13,62 +13,95 @@ type DelegatorWithdrawInfo struct { WithdrawAddr sdk.AccAddress `json:"withdraw_addr"` } +// used for import / export via genesis json +type ValidatorAccumulatedCommissionRecord struct { + ValidatorAddr sdk.ValAddress `json:"validator_addr"` + 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"` +} + +// used for import / export via genesis json +type ValidatorCurrentRewardsRecord struct { + ValidatorAddr sdk.ValAddress `json:"validator_addr"` + 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"` +} + +// 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"` +} + // GenesisState - all distribution state that must be provided at genesis type GenesisState struct { - FeePool FeePool `json:"fee_pool"` - CommunityTax sdk.Dec `json:"community_tax"` - BaseProposerReward sdk.Dec `json:"base_proposer_reward"` - BonusProposerReward sdk.Dec `json:"bonus_proposer_reward"` - ValidatorDistInfos []ValidatorDistInfo `json:"validator_dist_infos"` - DelegationDistInfos []DelegationDistInfo `json:"delegator_dist_infos"` - DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` - PreviousProposer sdk.ConsAddress `json:"previous_proposer"` + FeePool FeePool `json:"fee_pool"` + CommunityTax sdk.Dec `json:"community_tax"` + BaseProposerReward sdk.Dec `json:"base_proposer_reward"` + BonusProposerReward sdk.Dec `json:"bonus_proposer_reward"` + WithdrawAddrEnabled bool `json:"withdraw_addr_enabled"` + DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` + PreviousProposer sdk.ConsAddress `json:"previous_proposer"` + OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"` + ValidatorAccumulatedCommissions []ValidatorAccumulatedCommissionRecord `json:"validator_accumulated_commissions"` + ValidatorHistoricalRewards []ValidatorHistoricalRewardsRecord `json:"validator_historical_rewards"` + ValidatorCurrentRewards []ValidatorCurrentRewardsRecord `json:"validator_current_rewards"` + DelegatorStartingInfos []DelegatorStartingInfoRecord `json:"delegator_starting_infos"` + ValidatorSlashEvents []ValidatorSlashEventRecord `json:"validator_slash_events"` } func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, - vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress) GenesisState { + withdrawAddrEnabled bool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r OutstandingRewards, + acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord, + cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord, + slashes []ValidatorSlashEventRecord) GenesisState { return GenesisState{ - FeePool: feePool, - CommunityTax: communityTax, - BaseProposerReward: baseProposerReward, - BonusProposerReward: bonusProposerReward, - ValidatorDistInfos: vdis, - DelegationDistInfos: ddis, - DelegatorWithdrawInfos: dwis, - PreviousProposer: pp, + FeePool: feePool, + CommunityTax: communityTax, + BaseProposerReward: baseProposerReward, + BonusProposerReward: bonusProposerReward, + WithdrawAddrEnabled: withdrawAddrEnabled, + DelegatorWithdrawInfos: dwis, + PreviousProposer: pp, + OutstandingRewards: r, + ValidatorAccumulatedCommissions: acc, + ValidatorHistoricalRewards: historical, + ValidatorCurrentRewards: cur, + DelegatorStartingInfos: dels, + ValidatorSlashEvents: slashes, } } // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - FeePool: InitialFeePool(), - CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% - BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% - BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% - } -} - -// default genesis utility function, initialize for starting validator set -func DefaultGenesisWithValidators(valAddrs []sdk.ValAddress) GenesisState { - - vdis := make([]ValidatorDistInfo, len(valAddrs)) - ddis := make([]DelegationDistInfo, len(valAddrs)) - - for i, valAddr := range valAddrs { - vdis[i] = NewValidatorDistInfo(valAddr, 0) - accAddr := sdk.AccAddress(valAddr) - ddis[i] = NewDelegationDistInfo(accAddr, valAddr, 0) - } - - return GenesisState{ - FeePool: InitialFeePool(), - CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% - BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% - BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% - ValidatorDistInfos: vdis, - DelegationDistInfos: ddis, + FeePool: InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% + BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% + BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% + WithdrawAddrEnabled: true, + DelegatorWithdrawInfos: []DelegatorWithdrawInfo{}, + PreviousProposer: nil, + OutstandingRewards: sdk.DecCoins{}, + ValidatorAccumulatedCommissions: []ValidatorAccumulatedCommissionRecord{}, + ValidatorHistoricalRewards: []ValidatorHistoricalRewardsRecord{}, + ValidatorCurrentRewards: []ValidatorCurrentRewardsRecord{}, + DelegatorStartingInfos: []DelegatorStartingInfoRecord{}, + ValidatorSlashEvents: []ValidatorSlashEventRecord{}, } } diff --git a/x/distribution/types/keepers.go b/x/distribution/types/keepers.go index 2578ea411360..e734d47fcdfe 100644 --- a/x/distribution/types/keepers.go +++ b/x/distribution/types/keepers.go @@ -2,14 +2,14 @@ package types import sdk "github.com/cosmos/cosmos-sdk/types" -// expected stake keeper -type StakeKeeper interface { +// expected staking keeper +type StakingKeeper interface { IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, fn func(index int64, delegation sdk.Delegation) (stop bool)) Delegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Delegation Validator(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Validator ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) sdk.Validator - TotalPower(ctx sdk.Context) sdk.Dec + TotalPower(ctx sdk.Context) sdk.Int GetLastTotalPower(ctx sdk.Context) sdk.Int GetLastValidatorPower(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Int } @@ -19,7 +19,7 @@ type BankKeeper interface { AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) } -// from ante handler +// expected fee collection keeper type FeeCollectionKeeper interface { GetCollectedFees(ctx sdk.Context) sdk.Coins ClearCollectedFees(ctx sdk.Context) diff --git a/x/distribution/types/keys.go b/x/distribution/types/keys.go new file mode 100644 index 000000000000..7bd90425d71d --- /dev/null +++ b/x/distribution/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // StoreKey is the store key string for distribution + StoreKey = "distr" + + // TStoreKey is the transient store key for distribution + TStoreKey = "transient_distr" + + // RouterKey is the message route for distribution + RouterKey = "distr" + + // QuerierRoute is the querier route for distribution + QuerierRoute = "distr" +) diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go index 167b3b4c5cf4..9c977ea91085 100644 --- a/x/distribution/types/msg.go +++ b/x/distribution/types/msg.go @@ -9,10 +9,7 @@ import ( const MsgRoute = "distr" // Verify interface at compile time -var _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorRewardsAll{} -var _, _ sdk.Msg = &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorRewardsAll{} - -//______________________________________________________________________ +var _, _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorCommission{} // msg struct for changing the withdraw address for a delegator (or validator self-delegation) type MsgSetWithdrawAddress struct { @@ -37,11 +34,8 @@ func (msg MsgSetWithdrawAddress) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgSetWithdrawAddress) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -55,46 +49,6 @@ func (msg MsgSetWithdrawAddress) ValidateBasic() sdk.Error { return nil } -//______________________________________________________________________ - -// msg struct for delegation withdraw for all of the delegator's delegations -type MsgWithdrawDelegatorRewardsAll struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` -} - -func NewMsgWithdrawDelegatorRewardsAll(delAddr sdk.AccAddress) MsgWithdrawDelegatorRewardsAll { - return MsgWithdrawDelegatorRewardsAll{ - DelegatorAddr: delAddr, - } -} - -func (msg MsgWithdrawDelegatorRewardsAll) Route() string { return MsgRoute } -func (msg MsgWithdrawDelegatorRewardsAll) Type() string { return "withdraw_delegation_rewards_all" } - -// Return address that must sign over msg.GetSignBytes() -func (msg MsgWithdrawDelegatorRewardsAll) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} -} - -// get the bytes for the message signer to sign on -func (msg MsgWithdrawDelegatorRewardsAll) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -// quick validity check -func (msg MsgWithdrawDelegatorRewardsAll) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrNilDelegatorAddr(DefaultCodespace) - } - return nil -} - -//______________________________________________________________________ - // msg struct for delegation withdraw from a single validator type MsgWithdrawDelegatorReward struct { DelegatorAddr sdk.AccAddress `json:"delegator_addr"` @@ -118,11 +72,8 @@ func (msg MsgWithdrawDelegatorReward) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgWithdrawDelegatorReward) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -136,38 +87,33 @@ func (msg MsgWithdrawDelegatorReward) ValidateBasic() sdk.Error { return nil } -//______________________________________________________________________ - // msg struct for validator withdraw -type MsgWithdrawValidatorRewardsAll struct { +type MsgWithdrawValidatorCommission struct { ValidatorAddr sdk.ValAddress `json:"validator_addr"` } -func NewMsgWithdrawValidatorRewardsAll(valAddr sdk.ValAddress) MsgWithdrawValidatorRewardsAll { - return MsgWithdrawValidatorRewardsAll{ +func NewMsgWithdrawValidatorCommission(valAddr sdk.ValAddress) MsgWithdrawValidatorCommission { + return MsgWithdrawValidatorCommission{ ValidatorAddr: valAddr, } } -func (msg MsgWithdrawValidatorRewardsAll) Route() string { return MsgRoute } -func (msg MsgWithdrawValidatorRewardsAll) Type() string { return "withdraw_validator_rewards_all" } +func (msg MsgWithdrawValidatorCommission) Route() string { return MsgRoute } +func (msg MsgWithdrawValidatorCommission) Type() string { return "withdraw_validator_rewards_all" } // Return address that must sign over msg.GetSignBytes() -func (msg MsgWithdrawValidatorRewardsAll) GetSigners() []sdk.AccAddress { +func (msg MsgWithdrawValidatorCommission) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr.Bytes())} } // get the bytes for the message signer to sign on -func (msg MsgWithdrawValidatorRewardsAll) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) +func (msg MsgWithdrawValidatorCommission) GetSignBytes() []byte { + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check -func (msg MsgWithdrawValidatorRewardsAll) ValidateBasic() sdk.Error { +func (msg MsgWithdrawValidatorCommission) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrNilValidatorAddr(DefaultCodespace) } diff --git a/x/distribution/types/msg_test.go b/x/distribution/types/msg_test.go index b0c0dd9eb4af..4fe6125a2cb8 100644 --- a/x/distribution/types/msg_test.go +++ b/x/distribution/types/msg_test.go @@ -8,7 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// test ValidateBasic for MsgCreateValidator +// test ValidateBasic for MsgSetWithdrawAddress func TestMsgSetWithdrawAddress(t *testing.T) { tests := []struct { delegatorAddr sdk.AccAddress @@ -32,7 +32,7 @@ func TestMsgSetWithdrawAddress(t *testing.T) { } } -// test ValidateBasic for MsgEditValidator +// test ValidateBasic for MsgWithdrawDelegatorReward func TestMsgWithdrawDelegatorReward(t *testing.T) { tests := []struct { delegatorAddr sdk.AccAddress @@ -54,27 +54,8 @@ func TestMsgWithdrawDelegatorReward(t *testing.T) { } } -// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf -func TestMsgWithdrawDelegatorRewardsAll(t *testing.T) { - tests := []struct { - delegatorAddr sdk.AccAddress - expectPass bool - }{ - {delAddr1, true}, - {emptyDelAddr, false}, - } - for i, tc := range tests { - msg := NewMsgWithdrawDelegatorRewardsAll(tc.delegatorAddr) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test index: %v", i) - } else { - require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) - } - } -} - -// test ValidateBasic for MsgDelegate -func TestMsgWithdrawValidatorRewardsAll(t *testing.T) { +// test ValidateBasic for MsgWithdrawValidatorCommission +func TestMsgWithdrawValidatorCommission(t *testing.T) { tests := []struct { validatorAddr sdk.ValAddress expectPass bool @@ -83,7 +64,7 @@ func TestMsgWithdrawValidatorRewardsAll(t *testing.T) { {emptyValAddr, false}, } for i, tc := range tests { - msg := NewMsgWithdrawValidatorRewardsAll(tc.validatorAddr) + msg := NewMsgWithdrawValidatorCommission(tc.validatorAddr) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test index: %v", i) } else { diff --git a/x/distribution/types/total_accum.go b/x/distribution/types/total_accum.go deleted file mode 100644 index 0915847a5ef6..000000000000 --- a/x/distribution/types/total_accum.go +++ /dev/null @@ -1,40 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// total accumulation tracker -type TotalAccum struct { - UpdateHeight int64 `json:"update_height"` - Accum sdk.Dec `json:"accum"` -} - -func NewTotalAccum(height int64) TotalAccum { - return TotalAccum{ - UpdateHeight: height, - Accum: sdk.ZeroDec(), - } -} - -// update total accumulation factor for the new height -// CONTRACT: height should be greater than the old height -func (ta TotalAccum) UpdateForNewHeight(height int64, accumCreatedPerBlock sdk.Dec) TotalAccum { - blocks := height - ta.UpdateHeight - if blocks < 0 { - panic("reverse updated for new height") - } - ta.Accum = ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks))) - ta.UpdateHeight = height - return ta -} - -// get total accumulation factor for the given height -// CONTRACT: height should be greater than the old height -func (ta TotalAccum) GetAccum(height int64, accumCreatedPerBlock sdk.Dec) sdk.Dec { - blocks := height - ta.UpdateHeight - if blocks < 0 { - panic("reverse updated for new height") - } - return ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks))) -} diff --git a/x/distribution/types/total_accum_test.go b/x/distribution/types/total_accum_test.go deleted file mode 100644 index 3984612add43..000000000000 --- a/x/distribution/types/total_accum_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestTotalAccumUpdateForNewHeight(t *testing.T) { - - ta := NewTotalAccum(0) - - ta = ta.UpdateForNewHeight(5, sdk.NewDec(3)) - require.True(sdk.DecEq(t, sdk.NewDec(15), ta.Accum)) - - ta = ta.UpdateForNewHeight(8, sdk.NewDec(2)) - require.True(sdk.DecEq(t, sdk.NewDec(21), ta.Accum)) -} diff --git a/x/distribution/types/validator.go b/x/distribution/types/validator.go new file mode 100644 index 000000000000..0bde7fc073ee --- /dev/null +++ b/x/distribution/types/validator.go @@ -0,0 +1,93 @@ +package types + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// historical rewards for a validator +// height is implicit within the store key +// cumulative reward ratio is the sum from the zeroeth period +// until this period of rewards / tokens, per the spec +// The reference count indicates the number of objects +// which might need to reference this historical entry +// at any point. +// ReferenceCount = +// number of outstanding delegations which ended the associated period (and might need to read that record) +// + number of slashes which ended the associated period (and might need to read that record) +// + one per validator for the zeroeth period, set on initialization +type ValidatorHistoricalRewards struct { + CumulativeRewardRatio sdk.DecCoins `json:"cumulative_reward_ratio"` + ReferenceCount uint16 `json:"reference_count"` +} + +// create a new ValidatorHistoricalRewards +func NewValidatorHistoricalRewards(cumulativeRewardRatio sdk.DecCoins, referenceCount uint16) ValidatorHistoricalRewards { + return ValidatorHistoricalRewards{ + CumulativeRewardRatio: cumulativeRewardRatio, + ReferenceCount: referenceCount, + } +} + +// current rewards and current period for a validator +// kept as a running counter and incremented each block +// as long as the validator's tokens remain constant +type ValidatorCurrentRewards struct { + Rewards sdk.DecCoins `json:"rewards"` // current rewards + Period uint64 `json:"period"` // current period +} + +// create a new ValidatorCurrentRewards +func NewValidatorCurrentRewards(rewards sdk.DecCoins, period uint64) ValidatorCurrentRewards { + return ValidatorCurrentRewards{ + Rewards: rewards, + Period: period, + } +} + +// accumulated commission for a validator +// kept as a running counter, can be withdrawn at any time +type ValidatorAccumulatedCommission = sdk.DecCoins + +// return the initial accumulated commission (zero) +func InitialValidatorAccumulatedCommission() ValidatorAccumulatedCommission { + return ValidatorAccumulatedCommission{} +} + +// validator slash event +// height is implicit within the store key +// needed to calculate appropriate amounts of staking token +// for delegations which withdraw after a slash has occurred +type ValidatorSlashEvent struct { + ValidatorPeriod uint64 `json:"validator_period"` // period when the slash occurred + Fraction sdk.Dec `json:"fraction"` // slash fraction +} + +// create a new ValidatorSlashEvent +func NewValidatorSlashEvent(validatorPeriod uint64, fraction sdk.Dec) ValidatorSlashEvent { + return ValidatorSlashEvent{ + ValidatorPeriod: validatorPeriod, + Fraction: fraction, + } +} + +func (vs ValidatorSlashEvent) String() string { + return fmt.Sprintf(`Period: %d +Fraction: %s`, vs.ValidatorPeriod, vs.Fraction) +} + +// ValidatorSlashEvents is a collection of ValidatorSlashEvent +type ValidatorSlashEvents []ValidatorSlashEvent + +func (vs ValidatorSlashEvents) String() string { + out := "Validator Slash Events:\n" + for i, sl := range vs { + out += fmt.Sprintf(` Slash %d: + Period: %d + Fraction: %s +`, i, sl.ValidatorPeriod, sl.Fraction) + } + return strings.TrimSpace(out) +} diff --git a/x/distribution/types/validator_info.go b/x/distribution/types/validator_info.go deleted file mode 100644 index fe4b755d0c70..000000000000 --- a/x/distribution/types/validator_info.go +++ /dev/null @@ -1,153 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// common parameters used in withdraws from validators -type WithdrawContext struct { - FeePool FeePool - Height int64 // block height - TotalPower sdk.Dec // total bonded tokens in the network - ValPower sdk.Dec // validator's bonded tokens - CommissionRate sdk.Dec // validator commission rate -} - -func NewWithdrawContext(feePool FeePool, height int64, totalPower, - valPower, commissionRate sdk.Dec) WithdrawContext { - - return WithdrawContext{ - FeePool: feePool, - Height: height, - TotalPower: totalPower, - ValPower: valPower, - CommissionRate: commissionRate, - } -} - -//_____________________________________________________________________________ - -// distribution info for a particular validator -type ValidatorDistInfo struct { - OperatorAddr sdk.ValAddress `json:"operator_addr"` - - FeePoolWithdrawalHeight int64 `json:"fee_pool_withdrawal_height"` // last height this validator withdrew from the global pool - - DelAccum TotalAccum `json:"del_accum"` // total accumulation factor held by delegators - DelPool DecCoins `json:"del_pool"` // rewards owed to delegators, commission has already been charged (includes proposer reward) - ValCommission DecCoins `json:"val_commission"` // commission collected by this validator (pending withdrawal) -} - -func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) ValidatorDistInfo { - return ValidatorDistInfo{ - OperatorAddr: operatorAddr, - FeePoolWithdrawalHeight: currentHeight, - DelPool: DecCoins{}, - DelAccum: NewTotalAccum(currentHeight), - ValCommission: DecCoins{}, - } -} - -// update total delegator accumululation -func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk.Dec) ValidatorDistInfo { - vi.DelAccum = vi.DelAccum.UpdateForNewHeight(height, totalDelShares) - return vi -} - -// Get the total delegator accum within this validator at the provided height -func (vi ValidatorDistInfo) GetTotalDelAccum(height int64, totalDelShares sdk.Dec) sdk.Dec { - return vi.DelAccum.GetAccum(height, totalDelShares) -} - -// Get the validator accum at the provided height -func (vi ValidatorDistInfo) GetValAccum(height int64, valTokens sdk.Dec) sdk.Dec { - blocks := height - vi.FeePoolWithdrawalHeight - return valTokens.MulInt(sdk.NewInt(blocks)) -} - -// Move any available accumulated fees in the FeePool to the validator's pool -// - updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0 -// - updates fee pool to latest height and total val accum w/ given totalBonded -// This is the only way to update the FeePool's validator TotalAccum. -// NOTE: This algorithm works as long as TakeFeePoolRewards is called after every power change. -// - called in ValidationDistInfo.WithdrawCommission -// - called in DelegationDistInfo.WithdrawRewards -// NOTE: When a delegator unbonds, say, onDelegationSharesModified -> -// WithdrawDelegationReward -> WithdrawRewards -func (vi ValidatorDistInfo) TakeFeePoolRewards(wc WithdrawContext) ( - ValidatorDistInfo, FeePool) { - - fp := wc.FeePool.UpdateTotalValAccum(wc.Height, wc.TotalPower) - - if fp.TotalValAccum.Accum.IsZero() { - vi.FeePoolWithdrawalHeight = wc.Height - return vi, fp - } - - // update the validators pool - accum := vi.GetValAccum(wc.Height, wc.ValPower) - vi.FeePoolWithdrawalHeight = wc.Height - - if accum.GT(fp.TotalValAccum.Accum) { - panic("individual accum should never be greater than the total") - } - withdrawalTokens := fp.ValPool.MulDec(accum).QuoDec(fp.TotalValAccum.Accum) // XXX ensure this doesn't cause problems - remValPool := fp.ValPool.Minus(withdrawalTokens) - - commission := withdrawalTokens.MulDec(wc.CommissionRate) - afterCommission := withdrawalTokens.Minus(commission) - - fp.TotalValAccum.Accum = fp.TotalValAccum.Accum.Sub(accum) - fp.ValPool = remValPool - vi.ValCommission = vi.ValCommission.Plus(commission) - vi.DelPool = vi.DelPool.Plus(afterCommission) - - return vi, fp -} - -// withdraw commission rewards -func (vi ValidatorDistInfo) WithdrawCommission(wc WithdrawContext) ( - vio ValidatorDistInfo, fpo FeePool, withdrawn DecCoins) { - - vi, fp := vi.TakeFeePoolRewards(wc) - - withdrawalTokens := vi.ValCommission - vi.ValCommission = DecCoins{} // zero - - return vi, fp, withdrawalTokens -} - -// get the validator's pool rewards at this current state, -func (vi ValidatorDistInfo) CurrentPoolRewards( - wc WithdrawContext) DecCoins { - - fp := wc.FeePool - totalValAccum := fp.GetTotalValAccum(wc.Height, wc.TotalPower) - valAccum := vi.GetValAccum(wc.Height, wc.ValPower) - - if valAccum.GT(totalValAccum) { - panic("individual accum should never be greater than the total") - } - withdrawalTokens := fp.ValPool.MulDec(valAccum).QuoDec(totalValAccum) - commission := withdrawalTokens.MulDec(wc.CommissionRate) - afterCommission := withdrawalTokens.Minus(commission) - pool := vi.DelPool.Plus(afterCommission) - return pool -} - -// get the validator's commission pool rewards at this current state, -func (vi ValidatorDistInfo) CurrentCommissionRewards( - wc WithdrawContext) DecCoins { - - fp := wc.FeePool - totalValAccum := fp.GetTotalValAccum(wc.Height, wc.TotalPower) - valAccum := vi.GetValAccum(wc.Height, wc.ValPower) - - if valAccum.GT(totalValAccum) { - panic("individual accum should never be greater than the total") - } - withdrawalTokens := fp.ValPool.MulDec(valAccum).QuoDec(totalValAccum) - commission := withdrawalTokens.MulDec(wc.CommissionRate) - commissionPool := vi.ValCommission.Plus(commission) - return commissionPool -} diff --git a/x/distribution/types/validator_info_test.go b/x/distribution/types/validator_info_test.go deleted file mode 100644 index 119eb63431a4..000000000000 --- a/x/distribution/types/validator_info_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestTakeFeePoolRewards(t *testing.T) { - - // initialize - height := int64(0) - fp := InitialFeePool() - vi1 := NewValidatorDistInfo(valAddr1, height) - vi2 := NewValidatorDistInfo(valAddr2, height) - vi3 := NewValidatorDistInfo(valAddr3, height) - commissionRate1 := sdk.NewDecWithPrec(2, 2) - commissionRate2 := sdk.NewDecWithPrec(3, 2) - commissionRate3 := sdk.NewDecWithPrec(4, 2) - validatorTokens1 := sdk.NewDec(10) - validatorTokens2 := sdk.NewDec(40) - validatorTokens3 := sdk.NewDec(50) - totalBondedTokens := validatorTokens1.Add(validatorTokens2).Add(validatorTokens3) - - // simulate adding some stake for inflation - height = 10 - fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} - - vi1, fp = vi1.TakeFeePoolRewards(NewWithdrawContext( - fp, height, totalBondedTokens, validatorTokens1, commissionRate1)) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.ValCommission[0].Amount)) - - vi2, fp = vi2.TakeFeePoolRewards(NewWithdrawContext( - fp, height, totalBondedTokens, validatorTokens2, commissionRate2)) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, vi2.ValCommission[0].Amount, sdk.NewDec(12))) - - // add more blocks and inflation - height = 20 - fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) - - vi3, fp = vi3.TakeFeePoolRewards(NewWithdrawContext( - fp, height, totalBondedTokens, validatorTokens3, commissionRate3)) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, vi3.ValCommission[0].Amount, sdk.NewDec(40))) -} - -func TestWithdrawCommission(t *testing.T) { - - // initialize - height := int64(0) - fp := InitialFeePool() - vi := NewValidatorDistInfo(valAddr1, height) - commissionRate := sdk.NewDecWithPrec(2, 2) - validatorTokens := sdk.NewDec(10) - totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power - - // simulate adding some stake for inflation - height = 10 - fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} - - // for a more fun staring condition, have an non-withdraw update - vi, fp = vi.TakeFeePoolRewards(NewWithdrawContext( - fp, height, totalBondedTokens, validatorTokens, commissionRate)) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.DelPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount)) - - // add more blocks and inflation - height = 20 - fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) - - vi, fp, commissionRecv := vi.WithdrawCommission(NewWithdrawContext( - fp, height, totalBondedTokens, validatorTokens, commissionRate)) - require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.DelPool[0].Amount)) - assert.Zero(t, len(vi.ValCommission)) - assert.True(sdk.DecEq(t, sdk.NewDec(4), commissionRecv[0].Amount)) -} diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 61cc00242891..fd8c9a9651e3 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -17,7 +17,7 @@ import ( // GetCmdQueryProposal implements the query proposal command. func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "proposal [proposal-id]", Args: cobra.ExactArgs(1), Short: "Query details of a single proposal", @@ -36,33 +36,16 @@ $ gaiacli query gov proposal 1 } // Query the proposal - res, err := queryProposal(proposalID, cliCtx, cdc, queryRoute) + res, err := gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return err } - fmt.Println(string(res)) - return nil + var proposal gov.Proposal + cdc.MustUnmarshalJSON(res, &proposal) + return cliCtx.PrintOutput(proposal) }, } - - return cmd -} - -func queryProposal(proposalID uint64, cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string) ([]byte, error) { - // Construct query - params := gov.NewQueryProposalParams(proposalID) - bz, err := cdc.MarshalJSON(params) - if err != nil { - return nil, err - } - - // Query store - res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz) - if err != nil { - return nil, err - } - return res, err } // GetCmdQueryProposals implements a query proposals command. @@ -125,22 +108,17 @@ $ gaiacli query gov proposals --status (DepositPeriod|VotingPeriod|Passed|Reject return err } - var matchingProposals []gov.Proposal + var matchingProposals gov.Proposals err = cdc.UnmarshalJSON(res, &matchingProposals) if err != nil { return err } if len(matchingProposals) == 0 { - fmt.Println("No matching proposals found") - return nil + return fmt.Errorf("No matching proposals found") } - for _, proposal := range matchingProposals { - fmt.Printf(" %d - %s\n", proposal.GetProposalID(), proposal.GetTitle()) - } - - return nil + return cliCtx.PrintOutput(matchingProposals) }, } @@ -155,7 +133,7 @@ $ gaiacli query gov proposals --status (DepositPeriod|VotingPeriod|Passed|Reject // Command to Get a Proposal Information // GetCmdQueryVote implements the query proposal vote command. func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "vote [proposal-id] [voter-address]", Args: cobra.ExactArgs(2), Short: "Query details of a single vote", @@ -175,7 +153,7 @@ $ gaiacli query gov vote 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk } // check to see if the proposal is in the store - _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + _, err = gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } @@ -204,19 +182,17 @@ $ gaiacli query gov vote 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk if err != nil { return err } + cdc.UnmarshalJSON(res, &vote) } - fmt.Println(string(res)) - return nil + return cliCtx.PrintOutput(vote) }, } - - return cmd } // GetCmdQueryVotes implements the command to query for proposal votes. func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "votes [proposal-id]", Args: cobra.ExactArgs(1), Short: "Query votes on a proposal", @@ -242,15 +218,13 @@ $ gaiacli query gov votes 1 } // check to see if the proposal is in the store - res, err := queryProposal(proposalID, cliCtx, cdc, queryRoute) + res, err := gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } var proposal gov.Proposal - if err := cdc.UnmarshalJSON(res, &proposal); err != nil { - return err - } + cdc.MustUnmarshalJSON(res, &proposal) propStatus := proposal.GetStatus() if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { @@ -263,18 +237,17 @@ $ gaiacli query gov votes 1 return err } - fmt.Println(string(res)) - return nil + var votes gov.Votes + cdc.MustUnmarshalJSON(res, &votes) + return cliCtx.PrintOutput(votes) }, } - - return cmd } // Command to Get a specific Deposit Information // GetCmdQueryDeposit implements the query proposal deposit command. func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "deposit [proposal-id] [depositer-address]", Args: cobra.ExactArgs(2), Short: "Query details of a deposit", @@ -294,7 +267,7 @@ $ gaiacli query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk } // check to see if the proposal is in the store - _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + _, err = gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } @@ -316,26 +289,24 @@ $ gaiacli query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk } var deposit gov.Deposit - cdc.UnmarshalJSON(res, &deposit) + cdc.MustUnmarshalJSON(res, &deposit) if deposit.Empty() { res, err = gcutils.QueryDepositByTxQuery(cdc, cliCtx, params) if err != nil { return err } + cdc.MustUnmarshalJSON(res, &deposit) } - fmt.Println(string(res)) - return nil + return cliCtx.PrintOutput(deposit) }, } - - return cmd } // GetCmdQueryDeposits implements the command to query for proposal deposits. func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "deposits [proposal-id]", Args: cobra.ExactArgs(1), Short: "Query deposits on a proposal", @@ -360,15 +331,13 @@ $ gaiacli query gov deposits 1 } // check to see if the proposal is in the store - res, err := queryProposal(proposalID, cliCtx, cdc, queryRoute) + res, err := gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { - return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + return fmt.Errorf("Failed to fetch proposal with id %d: %s", proposalID, err) } var proposal gov.Proposal - if err := cdc.UnmarshalJSON(res, &proposal); err != nil { - return err - } + cdc.MustUnmarshalJSON(res, &proposal) propStatus := proposal.GetStatus() if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { @@ -381,17 +350,16 @@ $ gaiacli query gov deposits 1 return err } - fmt.Println(string(res)) - return nil + var dep gov.Deposits + cdc.MustUnmarshalJSON(res, &dep) + return cliCtx.PrintOutput(dep) }, } - - return cmd } // GetCmdQueryTally implements the command to query for proposal tally result. func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "tally [proposal-id]", Args: cobra.ExactArgs(1), Short: "Get the tally of a proposal vote", @@ -410,7 +378,7 @@ $ gaiacli query gov tally 1 } // check to see if the proposal is in the store - _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + _, err = gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } @@ -428,17 +396,48 @@ $ gaiacli query gov tally 1 return err } - fmt.Println(string(res)) - return nil + var tally gov.TallyResult + cdc.MustUnmarshalJSON(res, &tally) + return cliCtx.PrintOutput(tally) }, } - - return cmd } // GetCmdQueryProposal implements the query proposal command. func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ + Use: "params", + Short: "Query the parameters (voting|tallying|deposit) of the governance process", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + tp, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/tallying", queryRoute), nil) + if err != nil { + return err + } + dp, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/deposit", queryRoute), nil) + if err != nil { + return err + } + vp, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/voting", queryRoute), nil) + if err != nil { + return err + } + + var tallyParams gov.TallyParams + cdc.MustUnmarshalJSON(tp, &tallyParams) + var depositParams gov.DepositParams + cdc.MustUnmarshalJSON(dp, &depositParams) + var votingParams gov.VotingParams + cdc.MustUnmarshalJSON(vp, &votingParams) + + return cliCtx.PrintOutput(gov.NewParams(votingParams, tallyParams, depositParams)) + }, + } +} + +// GetCmdQueryProposal implements the query proposal command. +func GetCmdQueryParam(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ Use: "param [param-type]", Args: cobra.ExactArgs(1), Short: "Query the parameters (voting|tallying|deposit) of the governance process", @@ -450,11 +449,50 @@ func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { if err != nil { return err } - - fmt.Println(string(res)) - return nil + var out fmt.Stringer + switch args[0] { + case "voting": + var param gov.VotingParams + cdc.MustUnmarshalJSON(res, ¶m) + out = param + case "tallying": + var param gov.TallyParams + cdc.MustUnmarshalJSON(res, ¶m) + out = param + case "deposit": + var param gov.DepositParams + cdc.MustUnmarshalJSON(res, ¶m) + out = param + default: + return fmt.Errorf("Argument must be one of (voting|tallying|deposit), was %s", args[0]) + } + + return cliCtx.PrintOutput(out) }, } +} - return cmd +// GetCmdQueryProposer implements the query proposer command. +func GetCmdQueryProposer(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "proposer [proposal-id]", + Args: cobra.ExactArgs(1), + Short: "Query the proposer of a governance proposal", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + // validate that the proposalID is a uint + proposalID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("proposal-id %s is not a valid uint", args[0]) + } + + prop, err := gcutils.QueryProposerByTxQuery(cdc, cliCtx, proposalID) + if err != nil { + return err + } + + return cliCtx.PrintOutput(prop) + }, + } } diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index ed99edaac186..89deae745672 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -179,7 +179,7 @@ func GetCmdDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { Long: strings.TrimSpace(` Submit a deposit for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals: -$ gaiacli tx gov deposit 1 10STAKE --from mykey +$ gaiacli tx gov deposit 1 10stake --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) @@ -194,7 +194,7 @@ $ gaiacli tx gov deposit 1 10STAKE --from mykey } // check to see if the proposal is in the store - _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + _, err = govClientUtils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } @@ -270,7 +270,7 @@ $ gaiacli tx gov vote 1 yes --from mykey } // check to see if the proposal is in the store - _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + _, err = govClientUtils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } diff --git a/x/gov/client/module_client.go b/x/gov/client/module_client.go index 1f67c9c07100..f66546ba92c2 100644 --- a/x/gov/client/module_client.go +++ b/x/gov/client/module_client.go @@ -31,7 +31,9 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { govCli.GetCmdQueryProposals(mc.storeKey, mc.cdc), govCli.GetCmdQueryVote(mc.storeKey, mc.cdc), govCli.GetCmdQueryVotes(mc.storeKey, mc.cdc), + govCli.GetCmdQueryParam(mc.storeKey, mc.cdc), govCli.GetCmdQueryParams(mc.storeKey, mc.cdc), + govCli.GetCmdQueryProposer(mc.storeKey, mc.cdc), govCli.GetCmdQueryDeposit(mc.storeKey, mc.cdc), govCli.GetCmdQueryDeposits(mc.storeKey, mc.cdc), govCli.GetCmdQueryTally(mc.storeKey, mc.cdc))...) diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 1b51566f8442..96142941ef07 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -26,7 +26,6 @@ const ( RestVoter = "voter" RestProposalStatus = "status" RestNumLimit = "limit" - storeName = "gov" ) // RegisterRoutes - Central function to define routes that get registered by the main application @@ -42,6 +41,10 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc( + fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), + queryProposerHandlerFn(cdc, cliCtx), + ).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cdc, cliCtx)).Methods("GET") @@ -79,11 +82,8 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } @@ -101,7 +101,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -127,11 +127,8 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } @@ -143,7 +140,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -169,11 +166,8 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } @@ -191,7 +185,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -292,6 +286,26 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha } } +func queryProposerHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + res, err := gcutils.QueryProposerByTxQuery(cdc, cliCtx, proposalID) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index fdae6f3b059f..dadd7dd534ee 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -10,6 +10,27 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov/tags" ) +const ( + defaultPage = 1 + defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19 +) + +// Proposer contains metadata of a governance proposal used for querying a +// proposer. +type Proposer struct { + ProposalID uint64 `json:"proposal_id"` + Proposer string `json:"proposer"` +} + +// NewProposer returns a new Proposer given id and proposer +func NewProposer(proposalID uint64, proposer string) Proposer { + return Proposer{proposalID, proposer} +} + +func (p Proposer) String() string { + return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer) +} + // QueryDepositsByTxQuery will query for deposits via a direct txs tags query. It // will fetch and build deposits directly from the returned txs and return a // JSON marshalled result or any error that occurred. @@ -21,11 +42,13 @@ func QueryDepositsByTxQuery( ) ([]byte, error) { tags := []string{ - fmt.Sprintf("%s='%s'", tags.Action, tags.ActionProposalDeposit), + fmt.Sprintf("%s='%s'", tags.Action, gov.MsgDeposit{}.Type()), fmt.Sprintf("%s='%s'", tags.ProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), } - infos, err := tx.SearchTxs(cliCtx, cdc, tags) + // NOTE: SearchTxs is used to facilitate the txs query which does not currently + // support configurable pagination. + infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } @@ -64,11 +87,13 @@ func QueryVotesByTxQuery( ) ([]byte, error) { tags := []string{ - fmt.Sprintf("%s='%s'", tags.Action, tags.ActionProposalVote), + fmt.Sprintf("%s='%s'", tags.Action, gov.MsgVote{}.Type()), fmt.Sprintf("%s='%s'", tags.ProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), } - infos, err := tx.SearchTxs(cliCtx, cdc, tags) + // NOTE: SearchTxs is used to facilitate the txs query which does not currently + // support configurable pagination. + infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } @@ -102,12 +127,14 @@ func QueryVoteByTxQuery( ) ([]byte, error) { tags := []string{ - fmt.Sprintf("%s='%s'", tags.Action, tags.ActionProposalVote), + fmt.Sprintf("%s='%s'", tags.Action, gov.MsgVote{}.Type()), fmt.Sprintf("%s='%s'", tags.ProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s='%s'", tags.Voter, []byte(params.Voter.String())), } - infos, err := tx.SearchTxs(cliCtx, cdc, tags) + // NOTE: SearchTxs is used to facilitate the txs query which does not currently + // support configurable pagination. + infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } @@ -133,8 +160,7 @@ func QueryVoteByTxQuery( } } - err = fmt.Errorf("address '%s' did not vote on proposalID %d", params.Voter, params.ProposalID) - return nil, err + return nil, fmt.Errorf("address '%s' did not vote on proposalID %d", params.Voter, params.ProposalID) } // QueryDepositByTxQuery will query for a single deposit via a direct txs tags @@ -144,12 +170,14 @@ func QueryDepositByTxQuery( ) ([]byte, error) { tags := []string{ - fmt.Sprintf("%s='%s'", tags.Action, tags.ActionProposalDeposit), + fmt.Sprintf("%s='%s'", tags.Action, gov.MsgDeposit{}.Type()), fmt.Sprintf("%s='%s'", tags.ProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s='%s'", tags.Depositor, []byte(params.Depositor.String())), } - infos, err := tx.SearchTxs(cliCtx, cdc, tags) + // NOTE: SearchTxs is used to facilitate the txs query which does not currently + // support configurable pagination. + infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } @@ -175,6 +203,50 @@ func QueryDepositByTxQuery( } } - err = fmt.Errorf("address '%s' did not deposit to proposalID %d", params.Depositor, params.ProposalID) - return nil, err + return nil, fmt.Errorf("address '%s' did not deposit to proposalID %d", params.Depositor, params.ProposalID) +} + +// QueryProposerByTxQuery will query for a proposer of a governance proposal by +// ID. +func QueryProposerByTxQuery( + cdc *codec.Codec, cliCtx context.CLIContext, proposalID uint64, +) (Proposer, error) { + + tags := []string{ + fmt.Sprintf("%s='%s'", tags.Action, gov.MsgSubmitProposal{}.Type()), + fmt.Sprintf("%s='%s'", tags.ProposalID, []byte(fmt.Sprintf("%d", proposalID))), + } + + // NOTE: SearchTxs is used to facilitate the txs query which does not currently + // support configurable pagination. + infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + if err != nil { + return Proposer{}, err + } + + for _, info := range infos { + for _, msg := range info.Tx.GetMsgs() { + // there should only be a single proposal under the given conditions + if msg.Type() == gov.TypeMsgSubmitProposal { + subMsg := msg.(gov.MsgSubmitProposal) + return NewProposer(proposalID, subMsg.Proposer.String()), nil + } + } + } + return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID) +} + +// QueryProposalByID takes a proposalID and returns a proposal +func QueryProposalByID(proposalID uint64, cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string) ([]byte, error) { + params := gov.NewQueryProposalParams(proposalID) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return nil, err + } + + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz) + if err != nil { + return nil, err + } + return res, err } diff --git a/x/gov/depositsvotes.go b/x/gov/depositsvotes.go index 3e2ac56737e8..869ad77dbeee 100644 --- a/x/gov/depositsvotes.go +++ b/x/gov/depositsvotes.go @@ -16,15 +16,29 @@ type Vote struct { Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } +func (v Vote) String() string { + return fmt.Sprintf("Voter %s voted with option %s on proposal %d", v.Voter, v.Option, v.ProposalID) +} + +// Votes is a collection of Vote +type Votes []Vote + +func (v Votes) String() string { + out := fmt.Sprintf("Votes for Proposal %d:", v[0].ProposalID) + for _, vot := range v { + out += fmt.Sprintf("\n %s: %s", vot.Voter, vot.Option) + } + return out +} + // Returns whether 2 votes are equal -func (voteA Vote) Equals(voteB Vote) bool { - return voteA.Voter.Equals(voteB.Voter) && voteA.ProposalID == voteB.ProposalID && voteA.Option == voteB.Option +func (v Vote) Equals(comp Vote) bool { + return v.Voter.Equals(comp.Voter) && v.ProposalID == comp.ProposalID && v.Option == comp.Option } // Returns whether a vote is empty -func (voteA Vote) Empty() bool { - voteB := Vote{} - return voteA.Equals(voteB) +func (v Vote) Empty() bool { + return v.Equals(Vote{}) } // Deposit @@ -34,15 +48,30 @@ type Deposit struct { Amount sdk.Coins `json:"amount"` // Deposit amount } +func (d Deposit) String() string { + return fmt.Sprintf("Deposit by %s on Proposal %d is for the amount %s", + d.Depositor, d.ProposalID, d.Amount) +} + +// Deposits is a collection of depoist +type Deposits []Deposit + +func (d Deposits) String() string { + out := fmt.Sprintf("Deposits for Proposal %d:", d[0].ProposalID) + for _, dep := range d { + out += fmt.Sprintf("\n %s: %s", dep.Depositor, dep.Amount) + } + return out +} + // Returns whether 2 deposits are equal -func (depositA Deposit) Equals(depositB Deposit) bool { - return depositA.Depositor.Equals(depositB.Depositor) && depositA.ProposalID == depositB.ProposalID && depositA.Amount.IsEqual(depositB.Amount) +func (d Deposit) Equals(comp Deposit) bool { + return d.Depositor.Equals(comp.Depositor) && d.ProposalID == comp.ProposalID && d.Amount.IsEqual(comp.Amount) } // Returns whether a deposit is empty -func (depositA Deposit) Empty() bool { - depositB := Deposit{} - return depositA.Equals(depositB) +func (d Deposit) Empty() bool { + return d.Equals(Deposit{}) } // Type that represents VoteOption as a byte diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 622d0968f085..1eb60c4f2b8a 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -9,11 +9,11 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) func TestTickExpiredDepositPeriod(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) @@ -22,7 +22,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { require.False(t, inactiveQueue.Valid()) inactiveQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -55,7 +55,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) @@ -64,7 +64,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { require.False(t, inactiveQueue.Valid()) inactiveQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -81,7 +81,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { require.False(t, inactiveQueue.Valid()) inactiveQueue.Close() - newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newProposalMsg2) require.True(t, res.IsOK()) @@ -111,7 +111,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { } func TestTickPassedDepositPeriod(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) @@ -123,7 +123,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.False(t, activeQueue.Valid()) activeQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -142,7 +142,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.False(t, inactiveQueue.Valid()) inactiveQueue.Close() - newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) @@ -152,7 +152,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { } func TestTickPassedVotingPeriod(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) SortAddresses(addrs) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -165,7 +165,7 @@ func TestTickPassedVotingPeriod(t *testing.T) { require.False(t, activeQueue.Valid()) activeQueue.Close() - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -176,7 +176,7 @@ func TestTickPassedVotingPeriod(t *testing.T) { newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)}) + newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 7a8fab0b63d6..e06d79b21fea 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -5,7 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // GenesisState - all staking state that must be provided at genesis @@ -45,7 +45,7 @@ func DefaultGenesisState() GenesisState { return GenesisState{ StartingProposalID: 1, DepositParams: DepositParams{ - MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)}, + MinDeposit: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)}, MaxDepositPeriod: time.Duration(172800) * time.Second, }, VotingParams: VotingParams{ @@ -60,6 +60,58 @@ func DefaultGenesisState() GenesisState { } } +// Checks whether 2 GenesisState structs are equivalent. +func (data GenesisState) Equal(data2 GenesisState) bool { + if data.StartingProposalID != data.StartingProposalID || + !data.DepositParams.Equal(data2.DepositParams) || + data.VotingParams != data2.VotingParams || + data.TallyParams != data2.TallyParams { + return false + } + + if len(data.Deposits) != len(data2.Deposits) { + return false + } + for i := range data.Deposits { + deposit1 := data.Deposits[i] + deposit2 := data2.Deposits[i] + if deposit1.ProposalID != deposit2.ProposalID || + !deposit1.Deposit.Equals(deposit2.Deposit) { + return false + } + } + + if len(data.Votes) != len(data2.Votes) { + return false + } + for i := range data.Votes { + vote1 := data.Votes[i] + vote2 := data2.Votes[i] + if vote1.ProposalID != vote2.ProposalID || + !vote1.Vote.Equals(vote2.Vote) { + return false + } + } + + if len(data.Proposals) != len(data2.Proposals) { + return false + } + for i := range data.Proposals { + if data.Proposals[i] != data.Proposals[i] { + return false + } + } + + return true + +} + +// Returns if a GenesisState is empty or has data in it +func (data GenesisState) IsEmpty() bool { + emptyGenState := GenesisState{} + return data.Equal(emptyGenState) +} + // ValidateGenesis TODO https://github.com/cosmos/cosmos-sdk/issues/3007 func ValidateGenesis(data GenesisState) error { threshold := data.TallyParams.Threshold @@ -110,6 +162,12 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { k.setVote(ctx, vote.ProposalID, vote.Vote.Voter, vote.Vote) } for _, proposal := range data.Proposals { + switch proposal.GetStatus() { + case StatusDepositPeriod: + k.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + case StatusVotingPeriod: + k.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) + } k.SetProposal(ctx, proposal) } } diff --git a/x/gov/genesis_test.go b/x/gov/genesis_test.go new file mode 100644 index 000000000000..87d466b5f179 --- /dev/null +++ b/x/gov/genesis_test.go @@ -0,0 +1,55 @@ +package gov + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/mock" + + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestImportExportQueues(t *testing.T) { + + // Generate mock app and keepers + mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil) + SortAddresses(addrs) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + + // Create two proposals, put the second into the voting period + proposal1 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID1 := proposal1.GetProposalID() + + proposal2 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + proposalID2 := proposal2.GetProposalID() + + _, votingStarted := keeper.AddDeposit(ctx, proposalID2, addrs[0], keeper.GetDepositParams(ctx).MinDeposit) + require.True(t, votingStarted) + + require.True(t, keeper.GetProposal(ctx, proposalID1).GetStatus() == StatusDepositPeriod) + require.True(t, keeper.GetProposal(ctx, proposalID2).GetStatus() == StatusVotingPeriod) + + genAccs := mock.GetAllAccounts(mapp.AccountKeeper, ctx) + + // Export the state and import it into a new Mock App + genState := ExportGenesis(ctx, keeper) + mapp2, keeper2, _, _, _, _ := getMockApp(t, 2, genState, genAccs) + + mapp2.BeginBlock(abci.RequestBeginBlock{}) + ctx2 := mapp2.BaseApp.NewContext(false, abci.Header{}) + + // Jump the time forward past the DepositPeriod and VotingPeriod + ctx2 = ctx2.WithBlockTime(ctx2.BlockHeader().Time.Add(keeper2.GetDepositParams(ctx2).MaxDepositPeriod).Add(keeper2.GetVotingParams(ctx2).VotingPeriod)) + + // Make sure that they are still in the DepositPeriod and VotingPeriod respectively + require.True(t, keeper2.GetProposal(ctx2, proposalID1).GetStatus() == StatusDepositPeriod) + require.True(t, keeper2.GetProposal(ctx2, proposalID2).GetStatus() == StatusVotingPeriod) + + // Run the endblocker. Check to make sure that proposal1 is removed from state, and proposal2 is finished VotingPeriod. + EndBlocker(ctx2, keeper2) + + require.Nil(t, keeper2.GetProposal(ctx2, proposalID1)) + require.True(t, keeper2.GetProposal(ctx2, proposalID2).GetStatus() == StatusRejected) +} diff --git a/x/gov/handler.go b/x/gov/handler.go index 4c2d298ff37c..947ce5407bdd 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -35,7 +35,6 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos } resTags := sdk.NewTags( - tags.Action, tags.ActionProposalSubmitted, tags.Proposer, []byte(msg.Proposer.String()), tags.ProposalID, proposalIDBytes, ) @@ -58,7 +57,6 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result proposalIDBytes := []byte(fmt.Sprintf("%d", msg.ProposalID)) resTags := sdk.NewTags( - tags.Action, tags.ActionProposalDeposit, tags.Depositor, []byte(msg.Depositor.String()), tags.ProposalID, proposalIDBytes, ) @@ -80,7 +78,6 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { return sdk.Result{ Tags: sdk.NewTags( - tags.Action, tags.ActionProposalVote, tags.Voter, []byte(msg.Voter.String()), tags.ProposalID, []byte(fmt.Sprintf("%d", msg.ProposalID)), ), @@ -88,9 +85,9 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { } // Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { +func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags { logger := ctx.Logger().With("module", "x/gov") - resTags = sdk.NewTags() + resTags := sdk.NewTags() inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) for ; inactiveIterator.Valid(); inactiveIterator.Next() { @@ -98,11 +95,12 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) inactiveProposal := keeper.GetProposal(ctx, proposalID) + keeper.DeleteProposal(ctx, proposalID) keeper.DeleteDeposits(ctx, proposalID) // delete any associated deposits (burned) - resTags = resTags.AppendTag(tags.Action, tags.ActionProposalDropped) - resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) + resTags = resTags.AppendTag(tags.ProposalID, []byte(fmt.Sprintf("%d", proposalID))) + resTags = resTags.AppendTag(tags.ProposalResult, tags.ActionProposalDropped) logger.Info( fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", @@ -124,26 +122,30 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal := keeper.GetProposal(ctx, proposalID) passes, tallyResults := tally(ctx, keeper, activeProposal) - var action []byte + var tagValue []byte if passes { keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) activeProposal.SetStatus(StatusPassed) - action = tags.ActionProposalPassed + tagValue = tags.ActionProposalPassed } else { keeper.DeleteDeposits(ctx, activeProposal.GetProposalID()) activeProposal.SetStatus(StatusRejected) - action = tags.ActionProposalRejected + tagValue = tags.ActionProposalRejected } - activeProposal.SetTallyResult(tallyResults) + activeProposal.SetFinalTallyResult(tallyResults) keeper.SetProposal(ctx, activeProposal) keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.GetVotingEndTime(), activeProposal.GetProposalID()) - logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", - activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) + logger.Info( + fmt.Sprintf( + "proposal %d (%s) tallied; passed: %v", + activeProposal.GetProposalID(), activeProposal.GetTitle(), passes, + ), + ) - resTags = resTags.AppendTag(tags.Action, action) - resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) + resTags = resTags.AppendTag(tags.ProposalID, []byte(fmt.Sprintf("%d", proposalID))) + resTags = resTags.AppendTag(tags.ProposalResult, tagValue) } activeIterator.Close() diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 0a3c8e1064b2..19fd65734bf1 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -11,8 +11,17 @@ import ( "github.com/tendermint/tendermint/crypto" ) -// Parameter store default namestore const ( + // StoreKey is the store key string for gov + StoreKey = "gov" + + // RouterKey is the message route for gov + RouterKey = "gov" + + // QuerierRoute is the querier route for gov + QuerierRoute = "gov" + + // Parameter store default namestore DefaultParamspace = "gov" ) @@ -90,14 +99,14 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description return nil } var proposal Proposal = &TextProposal{ - ProposalID: proposalID, - Title: title, - Description: description, - ProposalType: proposalType, - Status: StatusDepositPeriod, - TallyResult: EmptyTallyResult(), - TotalDeposit: sdk.Coins{}, - SubmitTime: ctx.BlockHeader().Time, + ProposalID: proposalID, + Title: title, + Description: description, + ProposalType: proposalType, + Status: StatusDepositPeriod, + FinalTallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: ctx.BlockHeader().Time, } depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 83702920e1c8..f4308ef19cc0 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -9,11 +9,11 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) func TestGetSetProposal(t *testing.T) { - mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -26,7 +26,7 @@ func TestGetSetProposal(t *testing.T) { } func TestIncrementProposalNumber(t *testing.T) { - mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -41,7 +41,7 @@ func TestIncrementProposalNumber(t *testing.T) { } func TestActivateVotingPeriod(t *testing.T) { - mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -62,7 +62,7 @@ func TestActivateVotingPeriod(t *testing.T) { } func TestDeposits(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 2) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil) SortAddresses(addrs) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -70,14 +70,14 @@ func TestDeposits(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() - fourSteak := sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 4)} - fiveSteak := sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 5)} + fourSteak := sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 4)} + fiveSteak := sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 5)} addr0Initial := keeper.ck.GetCoins(ctx, addrs[0]) addr1Initial := keeper.ck.GetCoins(ctx, addrs[1]) - // require.True(t, addr0Initial.IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42)})) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42)}, addr0Initial) + // require.True(t, addr0Initial.IsEqual(sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 42)})) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 42)}, addr0Initial) require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{})) @@ -149,7 +149,7 @@ func TestDeposits(t *testing.T) { } func TestVotes(t *testing.T) { - mapp, keeper, _, addrs, _, _ := getMockApp(t, 2) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil) SortAddresses(addrs) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) @@ -205,7 +205,7 @@ func TestVotes(t *testing.T) { } func TestProposalQueues(t *testing.T) { - mapp, keeper, _, _, _, _ := getMockApp(t, 0) + mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) mapp.InitChainer(ctx, abci.RequestInitChain{}) diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 12f5fd33af26..3be7ab583a15 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -8,7 +8,6 @@ import ( // Governance message types and routes const ( - MsgRoute = "gov" TypeMsgDeposit = "deposit" TypeMsgVote = "vote" TypeMsgSubmitProposal = "submit_proposal" @@ -37,7 +36,7 @@ func NewMsgSubmitProposal(title string, description string, proposalType Proposa } //nolint -func (msg MsgSubmitProposal) Route() string { return MsgRoute } +func (msg MsgSubmitProposal) Route() string { return RouterKey } func (msg MsgSubmitProposal) Type() string { return TypeMsgSubmitProposal } // Implements Msg. @@ -74,11 +73,8 @@ func (msg MsgSubmitProposal) Get(key interface{}) (value interface{}) { // Implements Msg. func (msg MsgSubmitProposal) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := msgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // Implements Msg. @@ -104,7 +100,7 @@ func NewMsgDeposit(depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins // Implements Msg. // nolint -func (msg MsgDeposit) Route() string { return MsgRoute } +func (msg MsgDeposit) Route() string { return RouterKey } func (msg MsgDeposit) Type() string { return TypeMsgDeposit } // Implements Msg. @@ -135,11 +131,8 @@ func (msg MsgDeposit) Get(key interface{}) (value interface{}) { // Implements Msg. func (msg MsgDeposit) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := msgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // Implements Msg. @@ -165,7 +158,7 @@ func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgV // Implements Msg. // nolint -func (msg MsgVote) Route() string { return MsgRoute } +func (msg MsgVote) Route() string { return RouterKey } func (msg MsgVote) Type() string { return TypeMsgVote } // Implements Msg. @@ -193,11 +186,8 @@ func (msg MsgVote) Get(key interface{}) (value interface{}) { // Implements Msg. func (msg MsgVote) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := msgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // Implements Msg. diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index 36bc10a6a076..83881834f86a 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -7,14 +7,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( - coinsPos = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1000)} + coinsPos = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1000)} coinsZero = sdk.Coins{} coinsPosNotAtoms = sdk.Coins{sdk.NewInt64Coin("foo", 10000)} - coinsMulti = sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 1000), sdk.NewInt64Coin("foo", 10000)} + coinsMulti = sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1000), sdk.NewInt64Coin("foo", 10000)} ) func init() { diff --git a/x/gov/params.go b/x/gov/params.go index 01da184d533a..15692041a96b 100644 --- a/x/gov/params.go +++ b/x/gov/params.go @@ -1,6 +1,7 @@ package gov import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,6 +13,17 @@ type DepositParams struct { MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } +func (dp DepositParams) String() string { + return fmt.Sprintf(`Deposit Params: + Min Deposit: %s + Max Deposit Period: %s`, dp.MinDeposit, dp.MaxDepositPeriod) +} + +// Checks equality of DepositParams +func (dp DepositParams) Equal(dp2 DepositParams) bool { + return dp.MinDeposit.IsEqual(dp2.MinDeposit) && dp.MaxDepositPeriod == dp2.MaxDepositPeriod +} + // 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 @@ -20,7 +32,41 @@ type TallyParams struct { GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote } +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) +} + // Param around Voting in governance type VotingParams struct { VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period. } + +func (vp VotingParams) String() string { + return fmt.Sprintf(`Voting Params: + Voting Period: %s`, vp.VotingPeriod) +} + +// Params returns all of the governance params +type Params struct { + VotingParams VotingParams `json:"voting_params"` + TallyParams TallyParams `json:"tally_params"` + DepositParams DepositParams `json:"deposit_params"` +} + +func (gp Params) String() string { + return gp.VotingParams.String() + "\n" + + gp.TallyParams.String() + "\n" + gp.DepositParams.String() +} + +func NewParams(vp VotingParams, tp TallyParams, dp DepositParams) Params { + return Params{ + VotingParams: vp, + DepositParams: dp, + TallyParams: tp, + } +} diff --git a/x/gov/proposals.go b/x/gov/proposals.go index e943fe1169ab..e5dccfb02cf0 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -3,6 +3,7 @@ package gov import ( "encoding/json" "fmt" + "strings" "time" "github.com/pkg/errors" @@ -28,8 +29,8 @@ type Proposal interface { GetStatus() ProposalStatus SetStatus(ProposalStatus) - GetTallyResult() TallyResult - SetTallyResult(TallyResult) + GetFinalTallyResult() TallyResult + SetFinalTallyResult(TallyResult) GetSubmitTime() time.Time SetSubmitTime(time.Time) @@ -45,6 +46,21 @@ type Proposal interface { GetVotingEndTime() time.Time SetVotingEndTime(time.Time) + + String() string +} + +// Proposals is an array of proposal +type Proposals []Proposal + +func (p Proposals) String() string { + out := "ID - (Status) [Type] Title\n" + for _, prop := range p { + out += fmt.Sprintf("%d - (%s) [%s] %s\n", + prop.GetProposalID(), prop.GetStatus(), + prop.GetProposalType(), prop.GetTitle()) + } + return strings.TrimSpace(out) } // checks if two proposals are equal @@ -54,7 +70,7 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { proposalA.GetDescription() == proposalB.GetDescription() && proposalA.GetProposalType() == proposalB.GetProposalType() && proposalA.GetStatus() == proposalB.GetStatus() && - proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && + proposalA.GetFinalTallyResult().Equals(proposalB.GetFinalTallyResult()) && proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && proposalA.GetDepositEndTime().Equal(proposalB.GetDepositEndTime()) && proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && @@ -73,8 +89,8 @@ type TextProposal struct { Description string `json:"description"` // Description of the proposal ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} - TallyResult TallyResult `json:"tally_result"` // Result of Tallys + Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} + FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met @@ -98,11 +114,13 @@ func (tp TextProposal) GetProposalType() ProposalKind { return tp.P func (tp *TextProposal) SetProposalType(proposalType ProposalKind) { tp.ProposalType = proposalType } func (tp TextProposal) GetStatus() ProposalStatus { return tp.Status } func (tp *TextProposal) SetStatus(status ProposalStatus) { tp.Status = status } -func (tp TextProposal) GetTallyResult() TallyResult { return tp.TallyResult } -func (tp *TextProposal) SetTallyResult(tallyResult TallyResult) { tp.TallyResult = tallyResult } -func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } -func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } -func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } +func (tp TextProposal) GetFinalTallyResult() TallyResult { return tp.FinalTallyResult } +func (tp *TextProposal) SetFinalTallyResult(tallyResult TallyResult) { + tp.FinalTallyResult = tallyResult +} +func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } +func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } +func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { tp.DepositEndTime = depositEndTime } @@ -117,6 +135,20 @@ func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { tp.VotingEndTime = votingEndTime } +func (tp TextProposal) String() string { + return fmt.Sprintf(`Proposal %d: + Title: %s + Type: %s + Status: %s + Submit Time: %s + Deposit End Time: %s + Total Deposit: %s + Voting Start Time: %s + Voting End Time: %s`, tp.ProposalID, tp.Title, tp.ProposalType, + tp.Status, tp.SubmitTime, tp.DepositEndTime, + tp.TotalDeposit, tp.VotingStartTime, tp.VotingEndTime) +} + //----------------------------------------------------------- // ProposalQueue type ProposalQueue []uint64 @@ -341,9 +373,17 @@ func EmptyTallyResult() TallyResult { } // checks if two proposals are equal -func (resultA TallyResult) Equals(resultB TallyResult) bool { - return (resultA.Yes.Equal(resultB.Yes) && - resultA.Abstain.Equal(resultB.Abstain) && - resultA.No.Equal(resultB.No) && - resultA.NoWithVeto.Equal(resultB.NoWithVeto)) +func (tr TallyResult) Equals(comp TallyResult) bool { + return (tr.Yes.Equal(comp.Yes) && + tr.Abstain.Equal(comp.Abstain) && + tr.No.Equal(comp.No) && + tr.NoWithVeto.Equal(comp.NoWithVeto)) +} + +func (tr TallyResult) String() string { + return fmt.Sprintf(`Tally Result: + Yes: %s + Abstain: %s + No: %s + NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto) } diff --git a/x/gov/querier.go b/x/gov/querier.go index 8b985929ed86..bc4eb0d2baa7 100644 --- a/x/gov/querier.go +++ b/x/gov/querier.go @@ -214,7 +214,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke if proposal.GetStatus() == StatusDepositPeriod { tallyResult = EmptyTallyResult() } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { - tallyResult = proposal.GetTallyResult() + tallyResult = proposal.GetFinalTallyResult() } else { // proposal is in voting period _, tallyResult = tally(ctx, keeper, proposal) diff --git a/x/gov/querier_test.go b/x/gov/querier_test.go index 01b611e19fd8..25e8220b56a6 100644 --- a/x/gov/querier_test.go +++ b/x/gov/querier_test.go @@ -11,9 +11,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const custom = "custom" + func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (DepositParams, VotingParams, TallyParams) { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryParams, ParamDeposit}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamDeposit}, "/"), Data: []byte{}, } @@ -26,7 +28,7 @@ func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier s require.Nil(t, err2) query = abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryParams, ParamVoting}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamVoting}, "/"), Data: []byte{}, } @@ -39,7 +41,7 @@ func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier s require.Nil(t, err2) query = abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryParams, ParamTallying}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamTallying}, "/"), Data: []byte{}, } @@ -56,7 +58,7 @@ func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier s func getQueriedProposal(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) Proposal { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryProposal}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryProposal}, "/"), Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), } @@ -72,7 +74,7 @@ func getQueriedProposal(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, depositor, voter sdk.AccAddress, status ProposalStatus, limit uint64) []Proposal { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryProposals}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryProposals}, "/"), Data: cdc.MustMarshalJSON(NewQueryProposalsParams(status, limit, voter, depositor)), } @@ -88,7 +90,7 @@ func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querie func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, depositor sdk.AccAddress) Deposit { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryDeposit}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryDeposit}, "/"), Data: cdc.MustMarshalJSON(NewQueryDepositParams(proposalID, depositor)), } @@ -104,7 +106,7 @@ func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier func getQueriedDeposits(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Deposit { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryDeposits}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryDeposits}, "/"), Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), } @@ -120,7 +122,7 @@ func getQueriedDeposits(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier func getQueriedVote(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, voter sdk.AccAddress) Vote { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryVote}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryVote}, "/"), Data: cdc.MustMarshalJSON(NewQueryVoteParams(proposalID, voter)), } @@ -136,7 +138,7 @@ func getQueriedVote(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk func getQueriedVotes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Vote { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryVote}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryVote}, "/"), Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), } @@ -152,7 +154,7 @@ func getQueriedVotes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sd func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) TallyResult { query := abci.RequestQuery{ - Path: strings.Join([]string{"custom", "gov", QueryTally}, "/"), + Path: strings.Join([]string{custom, QuerierRoute, QueryTally}, "/"), Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), } @@ -168,7 +170,7 @@ func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sd func testQueryParams(t *testing.T) { cdc := codec.New() - mapp, keeper, _, _, _, _ := getMockApp(t, 1000) + mapp, keeper, _, _, _, _ := getMockApp(t, 1000, GenesisState{}, nil) querier := NewQuerier(keeper) ctx := mapp.NewContext(false, abci.Header{}) @@ -177,7 +179,7 @@ func testQueryParams(t *testing.T) { func testQueries(t *testing.T) { cdc := codec.New() - mapp, keeper, _, addrs, _, _ := getMockApp(t, 1000) + mapp, keeper, _, addrs, _, _ := getMockApp(t, 1000, GenesisState{}, nil) querier := NewQuerier(keeper) handler := NewHandler(keeper) ctx := mapp.NewContext(false, abci.Header{}) diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 5319ee7b2c0c..faca7ec8e9e6 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -10,12 +10,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ( - denom = stakeTypes.DefaultBondDenom + denom = stakingTypes.DefaultBondDenom ) // SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal @@ -23,7 +23,7 @@ const ( // future operations. // TODO: Vote more intelligently, so we can actually do some checks regarding votes passing or failing // TODO: Actually check that validator slashings happened -func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { +func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk staking.Keeper) simulation.Operation { handler := gov.NewHandler(k) // The states are: // column 1: All validators vote diff --git a/x/gov/tags/tags.go b/x/gov/tags/tags.go index 33986f9720d3..a4b58d8b2ba7 100644 --- a/x/gov/tags/tags.go +++ b/x/gov/tags/tags.go @@ -6,12 +6,9 @@ import ( // Governance tags var ( - ActionProposalDropped = []byte("proposal-dropped") - ActionProposalPassed = []byte("proposal-passed") - ActionProposalRejected = []byte("proposal-rejected") - ActionProposalSubmitted = []byte("proposal-submitted") - ActionProposalVote = []byte("proposal-vote") - ActionProposalDeposit = []byte("proposal-deposit") + ActionProposalDropped = []byte("proposal-dropped") + ActionProposalPassed = []byte("proposal-passed") + ActionProposalRejected = []byte("proposal-rejected") Action = sdk.TagAction Proposer = "proposer" @@ -19,4 +16,5 @@ var ( VotingPeriodStart = "voting-period-start" Depositor = "depositor" Voter = "voter" + ProposalResult = "proposal-result" ) diff --git a/x/gov/tally.go b/x/gov/tally.go index 199370e93369..8b44b034c84d 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -26,7 +26,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { currValidators[validator.GetOperator().String()] = validatorGovInfo{ Address: validator.GetOperator(), - Power: validator.GetPower(), + Power: sdk.NewDecFromInt(validator.GetPower()), DelegatorShares: validator.GetDelegatorShares(), Minus: sdk.ZeroDec(), Vote: OptionEmpty, @@ -94,11 +94,12 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall } // If there is no staked coins, the proposal fails - if keeper.vs.TotalPower(ctx).Equal(sdk.ZeroDec()) { + if keeper.vs.TotalPower(ctx).IsZero() { return false, tallyResults } // If there is not enough quorum of votes, the proposal fails - if totalVotingPower.Quo(keeper.vs.TotalPower(ctx)).LT(tallyParams.Quorum) { + percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(keeper.vs.TotalPower(ctx))) + if percentVoting.LT(tallyParams.Quorum) { return false, tallyResults } // If no one votes (everyone abstains), proposal fails diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index fc5cac91082b..77a22996b0ad 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -11,43 +11,43 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( pubkeys = []crypto.PubKey{ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey()} - testDescription = stake.NewDescription("T", "E", "S", "T") - testCommissionMsg = stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + testDescription = staking.NewDescription("T", "E", "S", "T") + testCommissionMsg = staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) -func createValidators(t *testing.T, stakeHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, coinAmt []int64) { +func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, coinAmt []int64) { require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.") for i := 0; i < len(addrs); i++ { - valCreateMsg := stake.NewMsgCreateValidator( - addrs[i], pubkeys[i], sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, coinAmt[i]), testDescription, testCommissionMsg, + valCreateMsg := staking.NewMsgCreateValidator( + addrs[i], pubkeys[i], sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, coinAmt[i]), testDescription, testCommissionMsg, ) - res := stakeHandler(ctx, valCreateMsg) + res := stakingHandler(ctx, valCreateMsg) require.True(t, res.IsOK()) } } func TestTallyNoOneVotes(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:2])) for i, addr := range addrs[:2] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -61,18 +61,18 @@ func TestTallyNoOneVotes(t *testing.T) { } func TestTallyNoQuorum(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:2])) for i, addr := range addrs[:2] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{2, 5}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{2, 5}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -87,18 +87,18 @@ func TestTallyNoQuorum(t *testing.T) { } func TestTallyOnlyValidatorsAllYes(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:2])) for i, addr := range addrs[:2] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -117,18 +117,18 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { } func TestTallyOnlyValidators51No(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:2])) for i, addr := range addrs[:2] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -146,18 +146,18 @@ func TestTallyOnlyValidators51No(t *testing.T) { } func TestTallyOnlyValidators51Yes(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -178,18 +178,18 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { } func TestTallyOnlyValidatorsVetoed(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -210,18 +210,18 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { } func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -242,18 +242,18 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { } func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -274,18 +274,18 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { } func TestTallyOnlyValidatorsNonVoter(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -304,21 +304,21 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { } func TestTallyDelgatorOverride(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) + staking.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 30)) - stakeHandler(ctx, delegator1Msg) + delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 30)) + stakingHandler(ctx, delegator1Msg) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -341,21 +341,21 @@ func TestTallyDelgatorOverride(t *testing.T) { } func TestTallyDelgatorInherit(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) + staking.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 30)) - stakeHandler(ctx, delegator1Msg) + delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 30)) + stakingHandler(ctx, delegator1Msg) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -376,23 +376,23 @@ func TestTallyDelgatorInherit(t *testing.T) { } func TestTallyDelgatorMultipleOverride(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) + staking.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg) - delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg2) + delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg) + delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg2) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -415,33 +415,33 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { } func TestTallyDelgatorMultipleInherit(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) - val1CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 25), testDescription, testCommissionMsg, + val1CreateMsg := staking.NewMsgCreateValidator( + sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 25), testDescription, testCommissionMsg, ) - stakeHandler(ctx, val1CreateMsg) + stakingHandler(ctx, val1CreateMsg) - val2CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 6), testDescription, testCommissionMsg, + val2CreateMsg := staking.NewMsgCreateValidator( + sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 6), testDescription, testCommissionMsg, ) - stakeHandler(ctx, val2CreateMsg) + stakingHandler(ctx, val2CreateMsg) - val3CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 7), testDescription, testCommissionMsg, + val3CreateMsg := staking.NewMsgCreateValidator( + sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 7), testDescription, testCommissionMsg, ) - stakeHandler(ctx, val3CreateMsg) + stakingHandler(ctx, val3CreateMsg) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg) + delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg) - delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg2) + delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg2) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -462,30 +462,30 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { } func TestTallyJailedValidator(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - stakeHandler := stake.NewHandler(sk) + stakingHandler := staking.NewHandler(sk) valAddrs := make([]sdk.ValAddress, len(addrs[:3])) for i, addr := range addrs[:3] { valAddrs[i] = sdk.ValAddress(addr) } - createValidators(t, stakeHandler, ctx, valAddrs, []int64{25, 6, 7}) - stake.EndBlocker(ctx, sk) + createValidators(t, stakingHandler, ctx, valAddrs, []int64{25, 6, 7}) + staking.EndBlocker(ctx, sk) - delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg) + delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg) - delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)) - stakeHandler(ctx, delegator1Msg2) + delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10)) + stakingHandler(ctx, delegator1Msg2) val2, found := sk.GetValidator(ctx, sdk.ValAddress(addrs[1])) require.True(t, found) sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 67b9fd902e63..f030d7b84ce6 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -6,54 +6,53 @@ import ( "sort" "testing" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // initialize the mock application for this module -func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.AccAddress, []crypto.PubKey, []crypto.PrivKey) { - mapp := mock.NewApp() +func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) (mapp *mock.App, keeper Keeper, sk staking.Keeper, addrs []sdk.AccAddress, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { + mapp = mock.NewApp() - stake.RegisterCodec(mapp.Cdc) + staking.RegisterCodec(mapp.Cdc) RegisterCodec(mapp.Cdc) - keyGlobalParams := sdk.NewKVStoreKey("params") - tkeyGlobalParams := sdk.NewTransientStoreKey("transient_params") - keyStake := sdk.NewKVStoreKey("stake") - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keyGov := sdk.NewKVStoreKey("gov") + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keyGov := sdk.NewKVStoreKey(StoreKey) - pk := params.NewKeeper(mapp.Cdc, keyGlobalParams, tkeyGlobalParams) + pk := mapp.ParamsKeeper ck := bank.NewBaseKeeper(mapp.AccountKeeper) - sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - keeper := NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace) + sk = staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + keeper = NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace) - mapp.Router().AddRoute("gov", NewHandler(keeper)) - mapp.QueryRouter().AddRoute("gov", NewQuerier(keeper)) + mapp.Router().AddRoute(RouterKey, NewHandler(keeper)) + mapp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper)) mapp.SetEndBlocker(getEndBlocker(keeper)) - mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) + mapp.SetInitChainer(getInitChainer(mapp, keeper, sk, genState)) - require.NoError(t, mapp.CompleteSetup(keyStake, tkeyStake, keyGov, keyGlobalParams, tkeyGlobalParams)) + require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keyGov)) - genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42)}) + if genAccs == nil || len(genAccs) == 0 { + genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 42)}) + } mock.SetGenesis(mapp, genAccs) return mapp, keeper, sk, addrs, pubKeys, privKeys } -// gov and stake endblocker +// gov and staking endblocker func getEndBlocker(keeper Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { tags := EndBlocker(ctx, keeper) @@ -63,19 +62,23 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { } } -// gov and stake initchainer -func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer { +// gov and staking initchainer +func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, genState GenesisState) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stakeGenesis := stake.DefaultGenesisState() - stakeGenesis.Pool.LooseTokens = sdk.NewDec(100000) + stakingGenesis := staking.DefaultGenesisState() + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt(100000) - validators, err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) + validators, err := staking.InitGenesis(ctx, stakingKeeper, stakingGenesis) if err != nil { panic(err) } - InitGenesis(ctx, keeper, DefaultGenesisState()) + if genState.IsEmpty() { + InitGenesis(ctx, keeper, DefaultGenesisState()) + } else { + InitGenesis(ctx, keeper, genState) + } return abci.ResponseInitChain{ Validators: validators, } diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index 725b2d3b9632..f9928c8ef904 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -1,10 +1,12 @@ package cli import ( - "github.com/cosmos/cosmos-sdk/client/utils" "os" "time" + "github.com/cosmos/cosmos-sdk/client/utils" + + bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" @@ -44,8 +46,8 @@ func IBCRelayCmd(cdc *codec.Codec) *cobra.Command { cdc: cdc, decoder: context.GetAccountDecoder(cdc), ibcStore: "ibc", - mainStore: "main", - accStore: "acc", + mainStore: bam.MainStoreKey, + accStore: auth.StoreKey, logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)), } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 704b663e62d0..ab26b6e42046 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -43,15 +43,12 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -59,10 +56,10 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. packet := ibc.NewIBCPacket( sdk.AccAddress(info.GetPubKey().Address()), to, - req.Amount, baseReq.ChainID, destChainID, + req.Amount, req.BaseReq.ChainID, destChainID, ) msg := ibc.IBCTransferMsg{IBCPacket: packet} - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 2fa24a6c7c3c..9b9bbf93a4c9 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -15,27 +15,47 @@ import ( 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" ) // AccountKeeper(/Keeper) and IBCMapper should use different StoreKey later -func defaultContext(key sdk.StoreKey) sdk.Context { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) - return ctx +type testInput struct { + cdc *codec.Codec + ctx sdk.Context + ak auth.AccountKeeper + bk bank.BaseKeeper + ibcKey *sdk.KVStoreKey } -func newAddress() sdk.AccAddress { - return sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) -} +func setupTestInput() testInput { + db := dbm.NewMemDB() + cdc := makeCodec() -func getCoins(ck bank.Keeper, ctx sdk.Context, addr sdk.AccAddress) (sdk.Coins, sdk.Error) { - zero := sdk.Coins(nil) - coins, _, err := ck.AddCoins(ctx, addr, zero) - return coins, err + ibcKey := sdk.NewKVStoreKey("ibcCapKey") + authCapKey := sdk.NewKVStoreKey("authCapKey") + fckCapKey := sdk.NewKVStoreKey("fckCapKey") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(ibcKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(fckCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + ms.LoadLatestVersion() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + ak := auth.NewAccountKeeper( + cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, + ) + bk := bank.NewBaseKeeper(ak) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger()) + + ak.SetParams(ctx, auth.DefaultParams()) + + return testInput{cdc: cdc, ctx: ctx, ak: ak, bk: bk, ibcKey: ibcKey} } func makeCodec() *codec.Codec { @@ -57,14 +77,19 @@ func makeCodec() *codec.Codec { return cdc } -func TestIBC(t *testing.T) { - cdc := makeCodec() +func newAddress() sdk.AccAddress { + return sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) +} - key := sdk.NewKVStoreKey("ibc") - ctx := defaultContext(key) +func getCoins(ck bank.Keeper, ctx sdk.Context, addr sdk.AccAddress) (sdk.Coins, sdk.Error) { + zero := sdk.Coins(nil) + coins, _, err := ck.AddCoins(ctx, addr, zero) + return coins, err +} - am := auth.NewAccountKeeper(cdc, key, auth.ProtoBaseAccount) - ck := bank.NewBaseKeeper(am) +func TestIBC(t *testing.T) { + input := setupTestInput() + ctx := input.ctx src := newAddress() dest := newAddress() @@ -72,12 +97,12 @@ func TestIBC(t *testing.T) { zero := sdk.Coins(nil) mycoins := sdk.Coins{sdk.NewInt64Coin("mycoin", 10)} - coins, _, err := ck.AddCoins(ctx, src, mycoins) + coins, _, err := input.bk.AddCoins(ctx, src, mycoins) require.Nil(t, err) require.Equal(t, mycoins, coins) - ibcm := NewMapper(cdc, key, DefaultCodespace) - h := NewHandler(ibcm, ck) + ibcm := NewMapper(input.cdc, input.ibcKey, DefaultCodespace) + h := NewHandler(ibcm, input.bk) packet := IBCPacket{ SrcAddr: src, DestAddr: dest, @@ -86,7 +111,7 @@ func TestIBC(t *testing.T) { DestChain: chainid, } - store := ctx.KVStore(key) + store := ctx.KVStore(input.ibcKey) var msg sdk.Msg var res sdk.Result @@ -102,7 +127,7 @@ func TestIBC(t *testing.T) { res = h(ctx, msg) require.True(t, res.IsOK()) - coins, err = getCoins(ck, ctx, src) + coins, err = getCoins(input.bk, ctx, src) require.Nil(t, err) require.Equal(t, zero, coins) @@ -120,7 +145,7 @@ func TestIBC(t *testing.T) { res = h(ctx, msg) require.True(t, res.IsOK()) - coins, err = getCoins(ck, ctx, dest) + coins, err = getCoins(input.bk, ctx, dest) require.Nil(t, err) require.Equal(t, mycoins, coins) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index 58b5fa4cd5fd..1758a5c170d3 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -21,6 +21,6 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { // mint coins, add to collected fees, update supply mintedCoin := minter.BlockProvision(params) k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) - k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) + k.sk.InflateSupply(ctx, mintedCoin.Amount) } diff --git a/x/mint/expected_keepers.go b/x/mint/expected_keepers.go index 150e155cb3b5..72bc36a5f5cd 100644 --- a/x/mint/expected_keepers.go +++ b/x/mint/expected_keepers.go @@ -4,11 +4,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// expected stake keeper -type StakeKeeper interface { - TotalPower(ctx sdk.Context) sdk.Dec +// expected staking keeper +type StakingKeeper interface { + TotalPower(ctx sdk.Context) sdk.Int BondedRatio(ctx sdk.Context) sdk.Dec - InflateSupply(ctx sdk.Context, newTokens sdk.Dec) + InflateSupply(ctx sdk.Context, newTokens sdk.Int) } // expected fee collection keeper interface diff --git a/x/mint/keeper.go b/x/mint/keeper.go index eba4c3fc1875..1a508dc2933b 100644 --- a/x/mint/keeper.go +++ b/x/mint/keeper.go @@ -6,17 +6,17 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -// keeper of the stake store +// keeper of the staking store type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec paramSpace params.Subspace - sk StakeKeeper + sk StakingKeeper fck FeeCollectionKeeper } func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, - paramSpace params.Subspace, sk StakeKeeper, fck FeeCollectionKeeper) Keeper { + paramSpace params.Subspace, sk StakingKeeper, fck FeeCollectionKeeper) Keeper { keeper := Keeper{ storeKey: key, @@ -38,7 +38,7 @@ var ( ParamStoreKeyParams = []byte("params") ) -// ParamTable for stake module +// ParamTable for staking module func ParamTypeTable() params.TypeTable { return params.NewTypeTable( ParamStoreKeyParams, Params{}, @@ -48,6 +48,9 @@ func ParamTypeTable() params.TypeTable { const ( // default paramspace for params keeper DefaultParamspace = "mint" + + // StoreKey is the default store key for mint + StoreKey = "mint" ) //______________________________________________________________________ diff --git a/x/mint/minter.go b/x/mint/minter.go index 8bf49f99ab78..761d9d1e6368 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -74,10 +74,10 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( } // calculate the annual provisions based on current total supply and inflation rate -func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) ( +func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Int) ( provisions sdk.Dec) { - return m.Inflation.Mul(totalSupply) + return m.Inflation.MulInt(totalSupply) } // get the provisions for a block based on the annual provisions rate diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index 18540862899d..0245c71972fb 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -121,7 +121,7 @@ func BenchmarkNextInflation(b *testing.B) { func BenchmarkNextAnnualProvisions(b *testing.B) { minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) params := DefaultParams() - totalSupply := sdk.NewDec(100000000000000) + totalSupply := sdk.NewInt(100000000000000) // run the NextAnnualProvisions function b.N times for n := 0; n < b.N; n++ { diff --git a/x/mint/params.go b/x/mint/params.go index e1acd3a63014..45a66f7fc1de 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -3,7 +3,7 @@ package mint import ( "fmt" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -34,7 +34,7 @@ func NewParams(mintDenom string, inflationRateChange, inflationMax, // default minting module parameters func DefaultParams() Params { return Params{ - MintDenom: stakeTypes.DefaultBondDenom, + MintDenom: stakingTypes.DefaultBondDenom, InflationRateChange: sdk.NewDecWithPrec(13, 2), InflationMax: sdk.NewDecWithPrec(20, 2), InflationMin: sdk.NewDecWithPrec(7, 2), diff --git a/x/mock/app.go b/x/mock/app.go index 066ac93dcaa5..541d923dcd5c 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -18,6 +18,7 @@ import ( "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/params" ) const chainID = "" @@ -27,13 +28,17 @@ const chainID = "" // capabilities aren't needed for testing. type App struct { *bam.BaseApp - Cdc *codec.Codec // Cdc is public since the codec is passed into the module anyways - KeyMain *sdk.KVStoreKey - KeyAccount *sdk.KVStoreKey + Cdc *codec.Codec // Cdc is public since the codec is passed into the module anyways + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + KeyFeeCollection *sdk.KVStoreKey + KeyParams *sdk.KVStoreKey + TKeyParams *sdk.TransientStoreKey // TODO: Abstract this out from not needing to be auth specifically AccountKeeper auth.AccountKeeper FeeCollectionKeeper auth.FeeCollectionKeeper + ParamsKeeper params.Keeper GenesisAccounts []auth.Account TotalCoinsSupply sdk.Coins @@ -55,17 +60,27 @@ func NewApp() *App { app := &App{ BaseApp: bam.NewBaseApp("mock", logger, db, auth.DefaultTxDecoder(cdc)), Cdc: cdc, - KeyMain: sdk.NewKVStoreKey("main"), - KeyAccount: sdk.NewKVStoreKey("acc"), + KeyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + KeyAccount: sdk.NewKVStoreKey(auth.StoreKey), TotalCoinsSupply: sdk.Coins{}, + KeyFeeCollection: sdk.NewKVStoreKey("fee"), + KeyParams: sdk.NewKVStoreKey("params"), + TKeyParams: sdk.NewTransientStoreKey("transient_params"), } + app.ParamsKeeper = params.NewKeeper(app.Cdc, app.KeyParams, app.TKeyParams) + // Define the accountKeeper app.AccountKeeper = auth.NewAccountKeeper( app.Cdc, app.KeyAccount, + app.ParamsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, ) + app.FeeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.Cdc, + app.KeyFeeCollection, + ) // Initialize the app. The chainers and blockers can be overwritten before // calling complete setup. @@ -80,8 +95,10 @@ func NewApp() *App { // CompleteSetup completes the application setup after the routes have been // registered. func (app *App) CompleteSetup(newKeys ...sdk.StoreKey) error { - newKeys = append(newKeys, app.KeyMain) - newKeys = append(newKeys, app.KeyAccount) + newKeys = append( + newKeys, + app.KeyMain, app.KeyAccount, app.KeyParams, app.TKeyParams, app.KeyFeeCollection, + ) for _, key := range newKeys { switch key.(type) { @@ -109,6 +126,8 @@ func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.Respo app.AccountKeeper.SetAccount(ctx, acc) } + auth.InitGenesis(ctx, app.AccountKeeper, app.FeeCollectionKeeper, auth.DefaultGenesisState()) + return abci.ResponseInitChain{} } diff --git a/x/mock/simulation/account.go b/x/mock/simulation/account.go index 37dfbb2cdb1a..fb3fdb65d817 100644 --- a/x/mock/simulation/account.go +++ b/x/mock/simulation/account.go @@ -44,8 +44,10 @@ func RandomAccounts(r *rand.Rand, n int) []Account { } else { accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) } + accs[i].PubKey = accs[i].PrivKey.PubKey() accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) } + return accs } diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index d2836bc7bd3b..81e360a1a827 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -19,7 +19,7 @@ import ( ) // AppStateFn returns the app state json bytes -type AppStateFn func(r *rand.Rand, accs []Account) json.RawMessage +type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) json.RawMessage // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, @@ -32,12 +32,13 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, } // initialize the chain for the simulation -func initChain(r *rand.Rand, params Params, accounts []Account, - app *baseapp.BaseApp, - appStateFn AppStateFn) mockValidators { +func initChain( + r *rand.Rand, params Params, accounts []Account, + app *baseapp.BaseApp, appStateFn AppStateFn, genesisTimestamp time.Time, +) mockValidators { req := abci.RequestInitChain{ - AppStateBytes: appStateFn(r, accounts), + AppStateBytes: appStateFn(r, accounts, genesisTimestamp), } res := app.InitChain(req) validators := newMockValidators(r, res.Validators, params) @@ -62,9 +63,9 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, params := RandomParams(r) // := DefaultParams() fmt.Printf("Randomized simulation params: %+v\n", params) - timestamp := RandTimestamp(r) + genesisTimestamp := RandTimestamp(r) fmt.Printf("Starting the simulation from time %v, unixtime %v\n", - timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) + genesisTimestamp.UTC().Format(time.UnixDate), genesisTimestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock accs := RandomAccounts(r, params.NumKeys) @@ -72,12 +73,12 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Second variable to keep pending validator set (delayed one block since // TM 0.24) Initially this is the same as the initial validator set - validators := initChain(r, params, accs, app, appStateFn) + validators := initChain(r, params, accs, app, appStateFn, genesisTimestamp) nextValidators := validators header := abci.Header{ Height: 1, - Time: timestamp, + Time: genesisTimestamp, ProposerAddress: validators.randomProposer(r), } opCount := 0 diff --git a/x/params/keeper.go b/x/params/keeper.go index cf78b60ff4f3..a316fed0b5ed 100644 --- a/x/params/keeper.go +++ b/x/params/keeper.go @@ -7,6 +7,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/params/subspace" ) +const ( + // StoreKey is the string key for the params store + StoreKey = subspace.StoreKey + + // TStoreKey is the string key for the params transient store + TStoreKey = subspace.TStoreKey +) + // Keeper of the global paramstore type Keeper struct { cdc *codec.Codec diff --git a/x/params/subspace/subspace.go b/x/params/subspace/subspace.go index fe5889a908ca..2ace1923276d 100644 --- a/x/params/subspace/subspace.go +++ b/x/params/subspace/subspace.go @@ -7,6 +7,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + // StoreKey is the string store key for the param store + StoreKey = "params" + + // TStoreKey is the string store key for the param transient store + TStoreKey = "transient_params" +) + // Individual parameter store for each keeper // Transient store persists for a block, so we use it for // recording whether the parameter has been changed or not diff --git a/x/params/subspace/test_common.go b/x/params/subspace/test_common.go index e3d980a72a4d..e39b9c1b2ddf 100644 --- a/x/params/subspace/test_common.go +++ b/x/params/subspace/test_common.go @@ -23,8 +23,8 @@ const ( // Returns components for testing func DefaultTestComponents(t *testing.T, table TypeTable) (sdk.Context, Subspace, func() sdk.CommitID) { cdc := codec.New() - key := sdk.NewKVStoreKey("params") - tkey := sdk.NewTransientStoreKey("tparams") + key := sdk.NewKVStoreKey(StoreKey) + tkey := sdk.NewTransientStoreKey(TStoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.WithTracer(os.Stdout) diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 4f312acd85a4..3662d7faafa0 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -11,9 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -23,36 +22,32 @@ var ( ) // initialize the mock application for this module -func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { +func getMockApp(t *testing.T) (*mock.App, staking.Keeper, Keeper) { mapp := mock.NewApp() RegisterCodec(mapp.Cdc) - keyStake := sdk.NewKVStoreKey("stake") - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keySlashing := sdk.NewKVStoreKey("slashing") + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySlashing := sdk.NewKVStoreKey(StoreKey) - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) + stakingKeeper := staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, bankKeeper, mapp.ParamsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + keeper := NewKeeper(mapp.Cdc, keySlashing, stakingKeeper, mapp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) + mapp.Router().AddRoute(staking.RouterKey, staking.NewHandler(stakingKeeper)) + mapp.Router().AddRoute(RouterKey, NewHandler(keeper)) - paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams, tkeyParams) - stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Subspace(DefaultParamspace), DefaultCodespace) - mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) - mapp.Router().AddRoute("slashing", NewHandler(keeper)) + mapp.SetEndBlocker(getEndBlocker(stakingKeeper)) + mapp.SetInitChainer(getInitChainer(mapp, stakingKeeper)) - mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) - mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) + require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keySlashing)) - require.NoError(t, mapp.CompleteSetup(keyStake, tkeyStake, keySlashing, keyParams, tkeyParams)) - - return mapp, stakeKeeper, keeper + return mapp, stakingKeeper, keeper } -// stake endblocker -func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { +// staking endblocker +func getEndBlocker(keeper staking.Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - validatorUpdates, tags := stake.EndBlocker(ctx, keeper) + validatorUpdates, tags := staking.EndBlocker(ctx, keeper) return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Tags: tags, @@ -61,12 +56,12 @@ func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { } // overwrite the mock init chainer -func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { +func getInitChainer(mapp *mock.App, keeper staking.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stakeGenesis := stake.DefaultGenesisState() - stakeGenesis.Pool.LooseTokens = sdk.NewDec(100000) - validators, err := stake.InitGenesis(ctx, keeper, stakeGenesis) + stakingGenesis := staking.DefaultGenesisState() + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt(100000) + validators, err := staking.InitGenesis(ctx, keeper, stakingGenesis) if err != nil { panic(err) } @@ -77,8 +72,8 @@ func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { } } -func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper, - addr sdk.AccAddress, expFound bool) stake.Validator { +func checkValidator(t *testing.T, mapp *mock.App, keeper staking.Keeper, + addr sdk.AccAddress, expFound bool) staking.Validator { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) validator, found := keeper.GetValidator(ctxCheck, sdk.ValAddress(addr1)) require.Equal(t, expFound, found) @@ -94,10 +89,10 @@ func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, } func TestSlashingMsgs(t *testing.T) { - mapp, stakeKeeper, keeper := getMockApp(t) + mapp, stakingKeeper, keeper := getMockApp(t) - genCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42) - bondCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10) + genCoin := sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 42) + bondCoin := sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -106,20 +101,20 @@ func TestSlashingMsgs(t *testing.T) { accs := []auth.Account{acc1} mock.SetGenesis(mapp, accs) - description := stake.NewDescription("foo_moniker", "", "", "") - commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + description := staking.NewDescription("foo_moniker", "", "", "") + commission := staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) - createValidatorMsg := stake.NewMsgCreateValidator( + createValidatorMsg := staking.NewMsgCreateValidator( sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, ) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []uint64{0}, []uint64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mapp.BeginBlock(abci.RequestBeginBlock{}) - validator := checkValidator(t, mapp, stakeKeeper, addr1, true) + validator := checkValidator(t, mapp, stakingKeeper, addr1, true) require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) require.Equal(t, sdk.Bonded, validator.Status) - require.True(sdk.DecEq(t, sdk.NewDec(10), validator.BondedTokens())) + require.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens())) unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.ConsPubKey.Address())} // no signing info yet diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index e174567ddf08..fa5f882cc69e 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" // XXX fix @@ -15,67 +13,49 @@ import ( // GetCmdQuerySigningInfo implements the command to query signing info. func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "signing-info [validator-pubkey]", Short: "Query a validator's signing information", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + pk, err := sdk.GetConsPubKeyBech32(args[0]) if err != nil { return err } key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address())) - cliCtx := context.NewCLIContext().WithCodec(cdc) res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } - signingInfo := new(slashing.ValidatorSigningInfo) + var signingInfo slashing.ValidatorSigningInfo cdc.MustUnmarshalBinaryLengthPrefixed(res, signingInfo) - - switch viper.Get(cli.OutputFlag) { - - case "text": - human := signingInfo.HumanReadableString() - fmt.Println(human) - - case "json": - // parse out the signing info - output, err := codec.MarshalJSONIndent(cdc, signingInfo) - if err != nil { - return err - } - fmt.Println(string(output)) - } - - return nil + return cliCtx.PrintOutput(signingInfo) }, } - - return cmd } // GetCmdQueryParams implements a command to fetch slashing parameters. func GetCmdQueryParams(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "params", Short: "Query the current slashing parameters", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - route := fmt.Sprintf("custom/%s/parameters", slashing.QuerierRoute) + route := fmt.Sprintf("custom/%s/parameters", slashing.QuerierRoute) res, err := cliCtx.QueryWithData(route, nil) if err != nil { return err } - fmt.Println(string(res)) - return nil + var params slashing.Params + cdc.MustUnmarshalJSON(res, ¶ms) + return cliCtx.PrintOutput(params) }, } - - return cmd } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index fd047cae0fdf..59c6493c2352 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -16,7 +16,7 @@ import ( func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc( "/slashing/validators/{validatorPubKey}/signing_info", - signingInfoHandlerFn(cliCtx, "slashing", cdc), + signingInfoHandlerFn(cliCtx, slashing.StoreKey, cdc), ).Methods("GET") r.HandleFunc( diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index ec28f48ed690..add8c6665bee 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -38,15 +38,12 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -64,6 +61,6 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL } msg := slashing.NewMsgUnjail(valAddr) - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 05ac917817f6..c3672c3d698b 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -1,16 +1,18 @@ package slashing import ( + "fmt" + "time" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // GenesisState - all slashing state that must be provided at genesis type GenesisState struct { - Params Params `json:"params"` - SigningInfos map[string]ValidatorSigningInfo `json:"signing_infos"` - MissedBlocks map[string][]MissedBlock `json:"missed_blocks"` - SlashingPeriods []ValidatorSlashingPeriod `json:"slashing_periods"` + Params Params `json:"params"` + SigningInfos map[string]ValidatorSigningInfo `json:"signing_infos"` + MissedBlocks map[string][]MissedBlock `json:"missed_blocks"` } // MissedBlock @@ -22,15 +24,44 @@ type MissedBlock struct { // HubDefaultGenesisState - default GenesisState used by Cosmos Hub func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), - SigningInfos: make(map[string]ValidatorSigningInfo), - MissedBlocks: make(map[string][]MissedBlock), - SlashingPeriods: []ValidatorSlashingPeriod{}, + Params: DefaultParams(), + SigningInfos: make(map[string]ValidatorSigningInfo), + MissedBlocks: make(map[string][]MissedBlock), } } -// ValidateGenesis TODO https://github.com/cosmos/cosmos-sdk/issues/3008 +// ValidateGenesis validates the slashing genesis parameters func ValidateGenesis(data GenesisState) error { + downtime := data.Params.SlashFractionDowntime + if downtime.IsNegative() || downtime.GT(sdk.OneDec()) { + return fmt.Errorf("Slashing fraction downtime should be less than or equal to one and greater than zero, is %s", downtime.String()) + } + + dblSign := data.Params.SlashFractionDoubleSign + if dblSign.IsNegative() || dblSign.GT(sdk.OneDec()) { + return fmt.Errorf("Slashing fraction double sign should be less than or equal to one and greater than zero, is %s", dblSign.String()) + } + + minSign := data.Params.MinSignedPerWindow + if minSign.IsNegative() || minSign.GT(sdk.OneDec()) { + return fmt.Errorf("Min signed per window should be less than or equal to one and greater than zero, is %s", minSign.String()) + } + + maxEvidence := data.Params.MaxEvidenceAge + if maxEvidence < 1*time.Minute { + return fmt.Errorf("Max evidence age must be at least 1 minute, is %s", maxEvidence.String()) + } + + downtimeJail := data.Params.DowntimeJailDuration + if downtimeJail < 1*time.Minute { + return fmt.Errorf("Downtime unblond duration must be at least 1 minute, is %s", downtimeJail.String()) + } + + signedWindow := data.Params.SignedBlocksWindow + if signedWindow < 10 { + return fmt.Errorf("Signed blocks window must be at least 10, is %d", signedWindow) + } + return nil } @@ -59,10 +90,6 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState, sdata types. } } - for _, slashingPeriod := range data.SlashingPeriods { - keeper.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) - } - keeper.paramspace.SetParamSet(ctx, &data.Params) } @@ -89,16 +116,9 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { return false }) - slashingPeriods := []ValidatorSlashingPeriod{} - keeper.IterateValidatorSlashingPeriods(ctx, func(slashingPeriod ValidatorSlashingPeriod) (stop bool) { - slashingPeriods = append(slashingPeriods, slashingPeriod) - return false - }) - return GenesisState{ - Params: params, - SigningInfos: signingInfos, - MissedBlocks: missedBlocks, - SlashingPeriods: slashingPeriods, + Params: params, + SigningInfos: signingInfos, + MissedBlocks: missedBlocks, } } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index a4a1aeb3099f..2bed8db9423d 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -2,6 +2,7 @@ package slashing import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/tags" ) func NewHandler(k Keeper) sdk.Handler { @@ -41,6 +42,10 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrNoValidatorForAddress(k.codespace).Result() } + if info.Tombstoned { + return ErrValidatorJailed(k.codespace).Result() + } + // cannot be unjailed until out of jail if ctx.BlockHeader().Time.Before(info.JailedUntil) { return ErrValidatorJailed(k.codespace).Result() @@ -49,7 +54,10 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { // unjail the validator k.validatorSet.Unjail(ctx, consAddr) - tags := sdk.NewTags("validator", []byte(msg.ValidatorAddr.String())) + tags := sdk.NewTags( + tags.Action, tags.ActionValidatorUnjailed, + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) return sdk.Result{ Tags: tags, diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index bd19643ae3dc..a363a497be35 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) func TestCannotUnjailUnlessJailed(t *testing.T) { @@ -17,15 +17,15 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) msg := NewTestMsgCreateValidator(addr, val, amt) - got := stake.NewHandler(sk)(ctx, msg) + got := staking.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + require.True(sdk.IntEq(t, amt, sk.Validator(ctx, addr).GetPower())) // assert non-jailed validator can't be unjailed got = slh(ctx, NewMsgUnjail(addr)) @@ -35,11 +35,11 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { } func TestJailedValidatorDelegations(t *testing.T) { - ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) + ctx, _, stakingKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) - stakeParams := stakeKeeper.GetParams(ctx) - stakeParams.UnbondingTime = 0 - stakeKeeper.SetParams(ctx, stakeParams) + stakingParams := stakingKeeper.GetParams(ctx) + stakingParams.UnbondingTime = 0 + stakingKeeper.SetParams(ctx, stakingParams) // create a validator amount := int64(10) @@ -47,11 +47,11 @@ func TestJailedValidatorDelegations(t *testing.T) { valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) - got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) + got := staking.NewHandler(stakingKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) // end block - stake.EndBlocker(ctx, stakeKeeper) + staking.EndBlocker(ctx, stakingKeeper) // set dummy signing info newInfo := ValidatorSigningInfo{ @@ -65,21 +65,21 @@ func TestJailedValidatorDelegations(t *testing.T) { // delegate tokens to the validator delAddr := sdk.AccAddress(addrs[2]) msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) - got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) + got = staking.NewHandler(stakingKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) unbondShares := sdk.NewDec(10) // unbond validator total self-delegations (which should jail the validator) - msgBeginUnbonding := stake.NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) - got = stake.NewHandler(stakeKeeper)(ctx, msgBeginUnbonding) + msgUndelegate := staking.NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares) + got = staking.NewHandler(stakingKeeper)(ctx, msgUndelegate) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) - err := stakeKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) + err := stakingKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) require.Nil(t, err, "expected complete unbonding validator to be ok, got: %v", err) // verify validator still exists and is jailed - validator, found := stakeKeeper.GetValidator(ctx, valAddr) + validator, found := stakingKeeper.GetValidator(ctx, valAddr) require.True(t, found) require.True(t, validator.GetJailed()) @@ -89,7 +89,7 @@ func TestJailedValidatorDelegations(t *testing.T) { // self-delegate to validator msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) - got = stake.NewHandler(stakeKeeper)(ctx, msgSelfDelegate) + got = staking.NewHandler(stakingKeeper)(ctx, msgSelfDelegate) require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) // verify the validator can now unjail itself diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index bb7e413242b3..3a4a38f2a721 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -1,3 +1,4 @@ +// nolint package slashing import ( @@ -8,7 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) { +func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) { // Update the signing info start height or create a new signing info _, found := k.getValidatorSigningInfo(ctx, address) if !found { @@ -16,36 +17,21 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sd StartHeight: ctx.BlockHeight(), IndexOffset: 0, JailedUntil: time.Unix(0, 0), + Tombstoned: false, MissedBlocksCounter: 0, } k.SetValidatorSigningInfo(ctx, address, signingInfo) } - - // Create a new slashing period when a validator is bonded - slashingPeriod := ValidatorSlashingPeriod{ - ValidatorAddr: address, - StartHeight: ctx.BlockHeight(), - EndHeight: 0, - SlashedSoFar: sdk.ZeroDec(), - } - k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) -} - -// Mark the slashing period as having ended when a validator begins unbonding -func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) { - slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, ctx.BlockHeight()) - slashingPeriod.EndHeight = ctx.BlockHeight() - k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) } // When a validator is created, add the address-pubkey relation. -func (k Keeper) onValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { +func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { validator := k.validatorSet.Validator(ctx, valAddr) k.addPubkey(ctx, validator.GetConsPubKey()) } // When a validator is removed, delete the address-pubkey relation. -func (k Keeper) onValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) { +func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) { k.deleteAddrPubkeyRelation(ctx, crypto.Address(address)) } @@ -64,29 +50,26 @@ func (k Keeper) Hooks() Hooks { } // Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorBonded(ctx, consAddr, valAddr) +func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.AfterValidatorBonded(ctx, consAddr, valAddr) } // Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - h.k.onValidatorBeginUnbonding(ctx, consAddr, valAddr) +func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, _ sdk.ValAddress) { + h.k.AfterValidatorRemoved(ctx, consAddr) } // Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, _ sdk.ValAddress) { - h.k.onValidatorRemoved(ctx, consAddr) -} - -// Implements sdk.ValidatorHooks -func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - h.k.onValidatorCreated(ctx, valAddr) +func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.k.AfterValidatorCreated(ctx, valAddr) } // nolint - unused hooks -func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { -} -func (h Hooks) OnValidatorModified(_ sdk.Context, _ sdk.ValAddress) {} -func (h Hooks) OnDelegationCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} -func (h Hooks) OnDelegationSharesModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} -func (h Hooks) OnDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} +func (h Hooks) AfterValidatorPowerDidChange(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeValidatorModified(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationSharesModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) AfterDelegationModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeValidatorSlashed(_ sdk.Context, _ sdk.ValAddress, _ sdk.Dec) {} diff --git a/x/slashing/hooks_test.go b/x/slashing/hooks_test.go deleted file mode 100644 index eb7406764744..000000000000 --- a/x/slashing/hooks_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package slashing - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestHookOnValidatorBonded(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - addr := sdk.ConsAddress(addrs[0]) - keeper.onValidatorBonded(ctx, addr, nil) - period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) - require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period) -} - -func TestHookOnValidatorBeginUnbonding(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - addr := sdk.ConsAddress(addrs[0]) - keeper.onValidatorBonded(ctx, addr, nil) - keeper.onValidatorBeginUnbonding(ctx, addr, addrs[0]) - period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) - require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), ctx.BlockHeight(), sdk.ZeroDec()}, period) -} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 5137b11981c5..f8494a8877b1 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" - stake "github.com/cosmos/cosmos-sdk/x/stake/types" + staking "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Keeper of the slashing store @@ -47,7 +47,13 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } - // Get validator. + // Reject evidence if the double is too old + if age > k.MaxEvidenceAge(ctx) { + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, k.MaxEvidenceAge(ctx))) + return + } + + // Get validator and signing info validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator == nil || validator.GetStatus() == sdk.Unbonded { // Defensive. @@ -55,48 +61,49 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Tendermint might break this assumption at some point. return } + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) + if !found { + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) + } - // Double sign too old - maxEvidenceAge := k.MaxEvidenceAge(ctx) - if age > maxEvidenceAge { - logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) + // Validator is already tombstoned + if signInfo.Tombstoned { + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, validator already tombstoned", pubkey.Address(), infractionHeight)) return } // Double sign confirmed - logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) + logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d", pubkey.Address(), infractionHeight, age)) // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height. // Note that this *can* result in a negative "distributionHeight", up to -ValidatorUpdateDelay, // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. // That's fine since this is just used to filter unbonding delegations & redelegations. - distributionHeight := infractionHeight - stake.ValidatorUpdateDelay + distributionHeight := infractionHeight - staking.ValidatorUpdateDelay - // Cap the amount slashed to the penalty for the worst infraction - // within the slashing period when this infraction was committed + // get the percentage slash penalty fraction fraction := k.SlashFractionDoubleSign(ctx) - revisedFraction := k.capBySlashingPeriod(ctx, consAddr, fraction, distributionHeight) - logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction)) // Slash validator // `power` is the int64 power of the validator as provided to/by // Tendermint. This value is validator.Tokens as sent to Tendermint via // ABCI, and now received as evidence. - // The revisedFraction (which is the new fraction to be slashed) is passed - // in separately to separately slash unbonding and rebonding delegations. - k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction) + // The fraction is passed in to separately to slash unbonding and rebonding delegations. + k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, fraction) // Jail validator if not already jailed + // begin unbonding validator if not already unbonding (tombstone) if !validator.GetJailed() { k.validatorSet.Jail(ctx, consAddr) } - // Set or updated validator jail duration - signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) - if !found { - panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) - } - signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx)) + // Set slashed so far to total slash + signInfo.Tombstoned = true + + // Set jailed until to be forever (max time) + signInfo.JailedUntil = DoubleSignJailEndTime + + // Set validator signing info k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } @@ -153,10 +160,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1, // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. // That's fine since this is just used to filter unbonding delegations & redelegations. - distributionHeight := height - stake.ValidatorUpdateDelay - 1 + distributionHeight := height - staking.ValidatorUpdateDelay - 1 k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) k.validatorSet.Jail(ctx, consAddr) - signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx)) + signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeJailDuration(ctx)) // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. signInfo.MissedBlocksCounter = 0 signInfo.IndexOffset = 0 diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index fdf409c2389a..9c4ead9d4727 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -8,7 +8,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) // Have to change these parameters for tests @@ -16,8 +16,7 @@ import ( func keeperTestParams() Params { params := DefaultParams() params.SignedBlocksWindow = 1000 - params.DowntimeUnbondDuration = 60 * 60 - params.DoubleSignUnbondDuration = 60 * 60 + params.DowntimeJailDuration = 60 * 60 return params } @@ -33,105 +32,87 @@ func TestHandleDoubleSign(t *testing.T) { ctx = ctx.WithBlockHeight(-1) amtInt := int64(100) operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) + require.True(sdk.IntEq(t, amt, sk.Validator(ctx, operatorAddr).GetPower())) // handle a signature to set signing info keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + oldTokens := sk.Validator(ctx, operatorAddr).GetTokens() + // double sign less than max age keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) - // unjail to measure power - sk.Unjail(ctx, sdk.ConsAddress(val.Address())) - // power should be reduced - require.Equal( - t, sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), - sk.Validator(ctx, operatorAddr).GetPower(), - ) - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))}) - // double sign past max age + // tokens should be decreased + newTokens := sk.Validator(ctx, operatorAddr).GetTokens() + require.True(t, newTokens.LT(oldTokens)) + + // New evidence keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) - require.Equal( - t, sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), - sk.Validator(ctx, operatorAddr).GetPower(), - ) + + // tokens should be the same (capped slash) + require.True(t, sk.Validator(ctx, operatorAddr).GetTokens().Equal(newTokens)) + + // Jump to past the unbonding period + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(sk.GetParams(ctx).UnbondingTime)}) + + // Still shouldn't be able to unjail + msgUnjail := NewMsgUnjail(operatorAddr) + res := handleMsgUnjail(ctx, msgUnjail, keeper) + require.False(t, res.IsOK()) + + // Should be able to unbond now + del, _ := sk.GetDelegation(ctx, sdk.AccAddress(operatorAddr), operatorAddr) + msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(operatorAddr), operatorAddr, del.GetShares()) + res = staking.NewHandler(sk)(ctx, msgUnbond) + require.True(t, res.IsOK()) } -// Test that the amount a validator is slashed for multiple double signs -// is correctly capped by the slashing period in which they were committed -func TestSlashingPeriodCap(t *testing.T) { +// ______________________________________________________________ + +// Test that a validator is slashed correctly +// when we discover evidence of infraction +func TestPastMaxEvidenceAge(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + // validator added pre-genesis + ctx = ctx.WithBlockHeight(-1) amtInt := int64(100) - operatorAddr, amt := addrs[0], sdk.NewInt(amtInt) - valConsPubKey, valConsAddr := pks[0], pks[0].Address() - got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) + operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, operatorAddr).GetPower())) + require.True(sdk.IntEq(t, amt, sk.Validator(ctx, operatorAddr).GetPower())) // handle a signature to set signing info - keeper.handleValidatorSignature(ctx, valConsAddr, amtInt, true) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) - // double sign less than max age - keeper.handleDoubleSign(ctx, valConsAddr, 1, time.Unix(0, 0), amtInt) - // should be jailed - require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) - // end block - stake.EndBlocker(ctx, sk) - // update block height - ctx = ctx.WithBlockHeight(int64(2)) - // unjail to measure power - sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) - // end block - stake.EndBlocker(ctx, sk) - // power should be reduced - expectedPower := sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) - require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))}) - // double sign again, same slashing period - keeper.handleDoubleSign(ctx, valConsAddr, 1, time.Unix(0, 0), amtInt) - // should be jailed - require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) - // end block - stake.EndBlocker(ctx, sk) - // update block height - ctx = ctx.WithBlockHeight(int64(3)) - // unjail to measure power - sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) - // end block - stake.EndBlocker(ctx, sk) - // power should be equal, no more should have been slashed - expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) - require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) + oldPower := sk.Validator(ctx, operatorAddr).GetPower() - // double sign again, new slashing period - keeper.handleDoubleSign(ctx, valConsAddr, 3, time.Unix(0, 0), amtInt) - // should be jailed - require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) - // unjail to measure power - sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) - // end block - stake.EndBlocker(ctx, sk) - // power should be reduced - expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20))) - require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) + // double sign past max age + keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) + + // should still be bonded + require.True(t, sk.Validator(ctx, operatorAddr).GetStatus() == sdk.Bonded) + + // should still have same power + require.True(t, sk.Validator(ctx, operatorAddr).GetPower().Equal(oldPower)) } // Test a validator through uptime, downtime, revocation, @@ -140,19 +121,19 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) - amtInt := int64(100) - addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - sh := stake.NewHandler(sk) + amtInt64 := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt64) + sh := staking.NewHandler(sk) slh := NewHandler(keeper) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + require.True(sdk.IntEq(t, amt, sk.Validator(ctx, addr).GetPower())) // will exist since the validator has been bonded info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) @@ -166,7 +147,7 @@ func TestHandleAbsentValidator(t *testing.T) { // 1000 first blocks OK for ; height < keeper.SignedBlocksWindow(ctx); height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, true) } info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) @@ -176,7 +157,7 @@ func TestHandleAbsentValidator(t *testing.T) { // 500 blocks missed for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) } info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) @@ -187,11 +168,11 @@ func TestHandleAbsentValidator(t *testing.T) { validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, amtInt, pool.BondedTokens.RoundInt64()) + require.True(sdk.IntEq(t, amt, pool.BondedTokens)) // 501st block missed ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) @@ -199,52 +180,44 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.MissedBlocksCounter) // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should have been jailed validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) - slashAmt := sdk.NewDec(amtInt).Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() + slashAmt := sdk.NewDec(amtInt64).Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() // validator should have been slashed - require.Equal(t, amtInt-slashAmt, validator.GetTokens().RoundInt64()) + require.Equal(t, amtInt64-slashAmt, validator.GetTokens().Int64()) // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) height++ ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(1), info.MissedBlocksCounter) // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should not have been slashed any more, since it was already jailed validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, amtInt-slashAmt, validator.GetTokens().RoundInt64()) - - // 502nd block *double signed* (oh no!) - keeper.handleDoubleSign(ctx, val.Address(), height, ctx.BlockHeader().Time, amtInt) - - // validator should have been slashed - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - secondSlashAmt := sdk.NewDec(amtInt).Mul(keeper.SlashFractionDoubleSign(ctx)).RoundInt64() - require.Equal(t, amtInt-slashAmt-secondSlashAmt, validator.GetTokens().RoundInt64()) + require.Equal(t, amtInt64-slashAmt, validator.GetTokens().Int64()) // unrevocation should fail prior to jail expiration got = slh(ctx, NewMsgUnjail(addr)) require.False(t, got.IsOK()) // unrevocation should succeed after jail expiration - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeUnbondDuration(ctx))}) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeJailDuration(ctx))}) got = slh(ctx, NewMsgUnjail(addr)) require.True(t, got.IsOK()) // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should be rebonded now validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) @@ -252,9 +225,9 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should have been slashed pool = sk.GetPool(ctx) - require.Equal(t, amtInt-slashAmt-secondSlashAmt, pool.BondedTokens.RoundInt64()) + require.Equal(t, amtInt64-slashAmt, pool.BondedTokens.Int64()) - // validator start height should not have been changed + // Validator start height should not have been changed info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) @@ -264,7 +237,7 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should not be immediately jailed again height++ ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) @@ -272,21 +245,21 @@ func TestHandleAbsentValidator(t *testing.T) { nextHeight := height + keeper.MinSignedPerWindow(ctx) + 1 for ; height < nextHeight; height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) } // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should be jailed again after 500 unsigned blocks nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 for ; height <= nextHeight; height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt64, false) } // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) @@ -299,7 +272,7 @@ func TestHandleNewValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) addr, val, amt := addrs[0], pks[0], int64(100) - sh := stake.NewHandler(sk) + sh := staking.NewHandler(sk) // 1000 first blocks not a validator ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) @@ -307,13 +280,13 @@ func TestHandleNewValidator(t *testing.T) { // Validator created got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt))}, ) - require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, addr).GetPower()) + require.Equal(t, amt, sk.Validator(ctx, addr).GetPower().Int64()) // Now a validator, for two blocks keeper.handleValidatorSignature(ctx, val.Address(), 100, true) @@ -331,7 +304,7 @@ func TestHandleNewValidator(t *testing.T) { validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(100), pool.BondedTokens.Int64()) } // Test a jailed validator being "down" twice @@ -342,10 +315,10 @@ func TestHandleAlreadyJailed(t *testing.T) { ctx, _, sk, _, keeper := createTestInput(t, DefaultParams()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - sh := stake.NewHandler(sk) + sh := staking.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // 1000 first blocks OK height := int64(0) @@ -361,14 +334,14 @@ func TestHandleAlreadyJailed(t *testing.T) { } // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should have been jailed and slashed validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) // validator should have been slashed - require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) + require.Equal(t, amtInt-1, validator.GetTokens().Int64()) // another block missed ctx = ctx.WithBlockHeight(height) @@ -376,7 +349,7 @@ func TestHandleAlreadyJailed(t *testing.T) { // validator should not have been slashed twice validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) + require.Equal(t, amtInt-1, validator.GetTokens().Int64()) } @@ -394,10 +367,10 @@ func TestValidatorDippingInAndOut(t *testing.T) { amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) consAddr := sdk.ConsAddress(addr) - sh := stake.NewHandler(sk) + sh := staking.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // 100 first blocks OK height := int64(0) @@ -410,7 +383,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { newAmt := int64(101) got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) require.True(t, got.IsOK()) - validatorUpdates, _ := stake.EndBlocker(ctx, sk) + validatorUpdates, _ := staking.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) validator, _ := sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) @@ -422,7 +395,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { // validator added back in got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], sdk.NewInt(3))) require.True(t, got.IsOK()) - validatorUpdates, _ = stake.EndBlocker(ctx, sk) + validatorUpdates, _ = staking.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) @@ -444,7 +417,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { } // should now be jailed & kicked - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) @@ -469,7 +442,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { height++ // validator should not be kicked since we reset counter/array when it was jailed - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) @@ -481,7 +454,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { } // validator should now be jailed & kicked - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) diff --git a/x/slashing/keys.go b/x/slashing/keys.go index 750e8825f12b..5e5b08a7f4d7 100644 --- a/x/slashing/keys.go +++ b/x/slashing/keys.go @@ -4,7 +4,18 @@ import ( "encoding/binary" sdk "github.com/cosmos/cosmos-sdk/types" - stake "github.com/cosmos/cosmos-sdk/x/stake/types" + staking "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +const ( + // StoreKey is the store key string for slashing + StoreKey = "slashing" + + // RouterKey is the message route for slashing + RouterKey = "slashing" + + // QuerierRoute is the querier route for slashing + QuerierRoute = "slashing" ) // key prefix bytes @@ -50,7 +61,7 @@ func GetValidatorSlashingPeriodPrefix(v sdk.ConsAddress) []byte { func GetValidatorSlashingPeriodKey(v sdk.ConsAddress, startHeight int64) []byte { b := make([]byte, 8) // this needs to be height + ValidatorUpdateDelay because the slashing period for genesis validators starts at height -ValidatorUpdateDelay - binary.BigEndian.PutUint64(b, uint64(startHeight+stake.ValidatorUpdateDelay)) + binary.BigEndian.PutUint64(b, uint64(startHeight+staking.ValidatorUpdateDelay)) return append(GetValidatorSlashingPeriodPrefix(v), b...) } diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 51864d4e2656..551258627773 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -7,9 +7,6 @@ import ( var cdc = codec.New() -// name to identify transaction types -const MsgRoute = "slashing" - // verify interface at compile time var _ sdk.Msg = &MsgUnjail{} @@ -25,7 +22,7 @@ func NewMsgUnjail(validatorAddr sdk.ValAddress) MsgUnjail { } //nolint -func (msg MsgUnjail) Route() string { return MsgRoute } +func (msg MsgUnjail) Route() string { return RouterKey } func (msg MsgUnjail) Type() string { return "unjail" } func (msg MsgUnjail) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} @@ -33,11 +30,8 @@ func (msg MsgUnjail) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgUnjail) GetSignBytes() []byte { - b, err := cdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := cdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check diff --git a/x/slashing/params.go b/x/slashing/params.go index 6f95ca73e9fa..12afad7e9180 100644 --- a/x/slashing/params.go +++ b/x/slashing/params.go @@ -1,6 +1,7 @@ package slashing import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,15 +13,19 @@ const ( DefaultParamspace = "slashing" ) +// The Double Sign Jail period ends at Max Time supported by Amino (Dec 31, 9999 - 23:59:59 GMT) +var ( + DoubleSignJailEndTime = time.Unix(253402300799, 0) +) + // Parameter store key var ( - KeyMaxEvidenceAge = []byte("MaxEvidenceAge") - KeySignedBlocksWindow = []byte("SignedBlocksWindow") - KeyMinSignedPerWindow = []byte("MinSignedPerWindow") - KeyDoubleSignUnbondDuration = []byte("DoubleSignUnbondDuration") - KeyDowntimeUnbondDuration = []byte("DowntimeUnbondDuration") - KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") - KeySlashFractionDowntime = []byte("SlashFractionDowntime") + KeyMaxEvidenceAge = []byte("MaxEvidenceAge") + KeySignedBlocksWindow = []byte("SignedBlocksWindow") + KeyMinSignedPerWindow = []byte("MinSignedPerWindow") + KeyDowntimeJailDuration = []byte("DowntimeJailDuration") + KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") + KeySlashFractionDowntime = []byte("SlashFractionDowntime") ) // ParamTypeTable for slashing module @@ -30,13 +35,25 @@ func ParamTypeTable() params.TypeTable { // Params - used for initializing default parameter for slashing at genesis type Params struct { - MaxEvidenceAge time.Duration `json:"max-evidence-age"` - SignedBlocksWindow int64 `json:"signed-blocks-window"` - MinSignedPerWindow sdk.Dec `json:"min-signed-per-window"` - DoubleSignUnbondDuration time.Duration `json:"double-sign-unbond-duration"` - DowntimeUnbondDuration time.Duration `json:"downtime-unbond-duration"` - SlashFractionDoubleSign sdk.Dec `json:"slash-fraction-double-sign"` - SlashFractionDowntime sdk.Dec `json:"slash-fraction-downtime"` + MaxEvidenceAge time.Duration `json:"max-evidence-age"` + SignedBlocksWindow int64 `json:"signed-blocks-window"` + MinSignedPerWindow sdk.Dec `json:"min-signed-per-window"` + DowntimeJailDuration time.Duration `json:"downtime-jail-duration"` + SlashFractionDoubleSign sdk.Dec `json:"slash-fraction-double-sign"` + SlashFractionDowntime sdk.Dec `json:"slash-fraction-downtime"` +} + +func (p Params) String() string { + return fmt.Sprintf(`Slashing Params: + MaxEvidenceAge: %s + SignedBlocksWindow: %d + MinSignedPerWindow: %s + DowntimeJailDuration: %s + SlashFractionDoubleSign: %d + SlashFractionDowntime: %d`, p.MaxEvidenceAge, + p.SignedBlocksWindow, p.MinSignedPerWindow, + p.DowntimeJailDuration, p.SlashFractionDoubleSign, + p.SlashFractionDowntime) } // Implements params.ParamStruct @@ -45,8 +62,7 @@ func (p *Params) KeyValuePairs() params.KeyValuePairs { {KeyMaxEvidenceAge, &p.MaxEvidenceAge}, {KeySignedBlocksWindow, &p.SignedBlocksWindow}, {KeyMinSignedPerWindow, &p.MinSignedPerWindow}, - {KeyDoubleSignUnbondDuration, &p.DoubleSignUnbondDuration}, - {KeyDowntimeUnbondDuration, &p.DowntimeUnbondDuration}, + {KeyDowntimeJailDuration, &p.DowntimeJailDuration}, {KeySlashFractionDoubleSign, &p.SlashFractionDoubleSign}, {KeySlashFractionDowntime, &p.SlashFractionDowntime}, } @@ -59,14 +75,11 @@ func DefaultParams() Params { // TODO Temporarily set to 2 minutes for testnets. MaxEvidenceAge: 60 * 2 * time.Second, - // TODO Temporarily set to five minutes for testnets - DoubleSignUnbondDuration: 60 * 5 * time.Second, - // TODO Temporarily set to 100 blocks for testnets SignedBlocksWindow: 100, // TODO Temporarily set to 10 minutes for testnets - DowntimeUnbondDuration: 60 * 10 * time.Second, + DowntimeJailDuration: 60 * 10 * time.Second, MinSignedPerWindow: sdk.NewDecWithPrec(5, 1), @@ -97,15 +110,9 @@ func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { return sdk.NewDec(signedBlocksWindow).Mul(minSignedPerWindow).RoundInt64() } -// Double-sign unbond duration -func (k Keeper) DoubleSignUnbondDuration(ctx sdk.Context) (res time.Duration) { - k.paramspace.Get(ctx, KeyDoubleSignUnbondDuration, &res) - return -} - // Downtime unbond duration -func (k Keeper) DowntimeUnbondDuration(ctx sdk.Context) (res time.Duration) { - k.paramspace.Get(ctx, KeyDowntimeUnbondDuration, &res) +func (k Keeper) DowntimeJailDuration(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, KeyDowntimeJailDuration, &res) return } diff --git a/x/slashing/querier.go b/x/slashing/querier.go index 50adb486d7e3..a0faaeb509f7 100644 --- a/x/slashing/querier.go +++ b/x/slashing/querier.go @@ -9,8 +9,6 @@ import ( // Query endpoints supported by the slashing querier const ( - QuerierRoute = "slashing" - QueryParameters = "parameters" ) @@ -21,7 +19,7 @@ func NewQuerier(k Keeper, cdc *codec.Codec) sdk.Querier { case QueryParameters: return queryParams(ctx, cdc, k) default: - return nil, sdk.ErrUnknownRequest("unknown stake query endpoint") + return nil, sdk.ErrUnknownRequest("unknown staking query endpoint") } } } diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index f4f2d2fde8cd..26c9501f549f 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -91,11 +91,12 @@ func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.C } // Construct a new `ValidatorSigningInfo` struct -func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, missedBlocksCounter int64) ValidatorSigningInfo { +func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, tombstoned bool, missedBlocksCounter int64) ValidatorSigningInfo { return ValidatorSigningInfo{ StartHeight: startHeight, IndexOffset: indexOffset, JailedUntil: jailedUntil, + Tombstoned: tombstoned, MissedBlocksCounter: missedBlocksCounter, } } @@ -105,11 +106,17 @@ type ValidatorSigningInfo struct { StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until + Tombstoned bool `json:"tombstoned"` // whether or not a validator has been tombstoned (killed out of validator set) MissedBlocksCounter int64 `json:"missed_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) } // Return human readable signing info -func (i ValidatorSigningInfo) HumanReadableString() string { - return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, missed blocks counter: %d", - i.StartHeight, i.IndexOffset, i.JailedUntil, i.MissedBlocksCounter) +func (i ValidatorSigningInfo) String() string { + return fmt.Sprintf(`Start Height: %d +Index Offset: %d +Jailed Until: %v +Tombstoned: %t +Missed Blocks Counter: %d`, + i.StartHeight, i.IndexOffset, i.JailedUntil, + i.Tombstoned, i.MissedBlocksCounter) } diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go deleted file mode 100644 index e726453dc213..000000000000 --- a/x/slashing/slashing_period.go +++ /dev/null @@ -1,134 +0,0 @@ -package slashing - -import ( - "encoding/binary" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - stake "github.com/cosmos/cosmos-sdk/x/stake/types" -) - -// Cap an infraction's slash amount by the slashing period in which it was committed -func (k Keeper) capBySlashingPeriod(ctx sdk.Context, address sdk.ConsAddress, fraction sdk.Dec, infractionHeight int64) (revisedFraction sdk.Dec) { - - // Fetch the newest slashing period starting before this infraction was committed - slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, infractionHeight) - - // Sanity check - if slashingPeriod.EndHeight > 0 && slashingPeriod.EndHeight < infractionHeight { - panic(fmt.Sprintf("slashing period ended before infraction: validator %s, infraction height %d, slashing period ended at %d", address, infractionHeight, slashingPeriod.EndHeight)) - } - - // Calculate the updated total slash amount - // This is capped at the slashing fraction for the worst infraction within this slashing period - totalToSlash := sdk.MaxDec(slashingPeriod.SlashedSoFar, fraction) - - // Calculate the remainder which we now must slash - revisedFraction = totalToSlash.Sub(slashingPeriod.SlashedSoFar) - - // Update the slashing period struct - slashingPeriod.SlashedSoFar = totalToSlash - k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) - - return -} - -// Stored by validator Tendermint address (not operator address) -// This function retrieves the most recent slashing period starting -// before a particular height - so the slashing period that was "in effect" -// at the time of an infraction committed at that height. -// Slashing periods are created upon validator bonding. -func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk.ConsAddress, height int64) (slashingPeriod ValidatorSlashingPeriod) { - store := ctx.KVStore(k.storeKey) - // Get the most recent slashing period at or before the infraction height - start := GetValidatorSlashingPeriodPrefix(address) - end := sdk.PrefixEndBytes(GetValidatorSlashingPeriodKey(address, height)) - iterator := store.ReverseIterator(start, end) - if !iterator.Valid() { - panic(fmt.Sprintf("expected to find slashing period for validator %s before height %d, but none was found", address, height)) - } - slashingPeriod = k.unmarshalSlashingPeriodKeyValue(iterator.Key(), iterator.Value()) - return -} - -// Iterate over all slashing periods in the store, calling on each -// decode slashing period a provided handler function -// Stop if the provided handler function returns true -func (k Keeper) IterateValidatorSlashingPeriods(ctx sdk.Context, handler func(slashingPeriod ValidatorSlashingPeriod) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - slashingPeriod := k.unmarshalSlashingPeriodKeyValue(iter.Key(), iter.Value()) - if handler(slashingPeriod) { - break - } - } -} - -// Delete all slashing periods in the store. -func (k Keeper) DeleteValidatorSlashingPeriods(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) - } - iter.Close() -} - -// Stored by validator Tendermint address (not operator address) -// This function sets a validator slashing period for a particular validator, -// start height, end height, and current slashed-so-far total, or updates -// an existing slashing period for the same validator and start height. -func (k Keeper) addOrUpdateValidatorSlashingPeriod(ctx sdk.Context, slashingPeriod ValidatorSlashingPeriod) { - slashingPeriodValue := ValidatorSlashingPeriodValue{ - EndHeight: slashingPeriod.EndHeight, - SlashedSoFar: slashingPeriod.SlashedSoFar, - } - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(slashingPeriodValue) - store.Set(GetValidatorSlashingPeriodKey(slashingPeriod.ValidatorAddr, slashingPeriod.StartHeight), bz) -} - -// Unmarshal key/value into a ValidatorSlashingPeriod -func (k Keeper) unmarshalSlashingPeriodKeyValue(key []byte, value []byte) ValidatorSlashingPeriod { - var slashingPeriodValue ValidatorSlashingPeriodValue - k.cdc.MustUnmarshalBinaryLengthPrefixed(value, &slashingPeriodValue) - address := sdk.ConsAddress(key[1 : 1+sdk.AddrLen]) - startHeight := int64(binary.BigEndian.Uint64(key[1+sdk.AddrLen:1+sdk.AddrLen+8]) - uint64(stake.ValidatorUpdateDelay)) - return ValidatorSlashingPeriod{ - ValidatorAddr: address, - StartHeight: startHeight, - EndHeight: slashingPeriodValue.EndHeight, - SlashedSoFar: slashingPeriodValue.SlashedSoFar, - } -} - -// Construct a new `ValidatorSlashingPeriod` struct -func NewValidatorSlashingPeriod(startHeight int64, endHeight int64, slashedSoFar sdk.Dec) ValidatorSlashingPeriod { - return ValidatorSlashingPeriod{ - StartHeight: startHeight, - EndHeight: endHeight, - SlashedSoFar: slashedSoFar, - } -} - -// Slashing period for a validator -type ValidatorSlashingPeriod struct { - ValidatorAddr sdk.ConsAddress `json:"validator_addr"` // validator which this slashing period is for - StartHeight int64 `json:"start_height"` // starting height of the slashing period - EndHeight int64 `json:"end_height"` // ending height of the slashing period, or sentinel value of 0 for in-progress - SlashedSoFar sdk.Dec `json:"slashed_so_far"` // fraction of validator stake slashed so far in this slashing period -} - -// Value part of slashing period (validator address & start height are stored in the key) -type ValidatorSlashingPeriodValue struct { - EndHeight int64 `json:"end_height"` - SlashedSoFar sdk.Dec `json:"slashed_so_far"` -} - -// Return human readable slashing period -func (p ValidatorSlashingPeriod) HumanReadableString() string { - return fmt.Sprintf("Start height: %d, end height: %d, slashed so far: %v", - p.StartHeight, p.EndHeight, p.SlashedSoFar) -} diff --git a/x/slashing/slashing_period_test.go b/x/slashing/slashing_period_test.go deleted file mode 100644 index bd12ef127396..000000000000 --- a/x/slashing/slashing_period_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package slashing - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestGetSetValidatorSlashingPeriod(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - addr := sdk.ConsAddress(addrs[0]) - height := int64(5) - require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) }) - newPeriod := ValidatorSlashingPeriod{ - ValidatorAddr: addr, - StartHeight: height, - EndHeight: height + 10, - SlashedSoFar: sdk.ZeroDec(), - } - keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) - - // Get at start height - retrieved := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) - require.Equal(t, newPeriod, retrieved) - - // Get after start height (works) - retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(6)) - require.Equal(t, newPeriod, retrieved) - - // Get before start height (panic) - require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(0)) }) - - // Get after end height (panic) - newPeriod.EndHeight = int64(4) - keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) - require.Panics(t, func() { keeper.capBySlashingPeriod(ctx, addr, sdk.ZeroDec(), height) }) - - // Back to old end height - newPeriod.EndHeight = height + 10 - keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) - - // Set a new, later period - anotherPeriod := ValidatorSlashingPeriod{ - ValidatorAddr: addr, - StartHeight: height + 1, - EndHeight: height + 11, - SlashedSoFar: sdk.ZeroDec(), - } - keeper.addOrUpdateValidatorSlashingPeriod(ctx, anotherPeriod) - - // Old period retrieved for prior height - retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) - require.Equal(t, newPeriod, retrieved) - - // New period retrieved at new height - retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height+1) - require.Equal(t, anotherPeriod, retrieved) -} - -func TestValidatorSlashingPeriodCap(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - addr := sdk.ConsAddress(addrs[0]) - height := int64(5) - newPeriod := ValidatorSlashingPeriod{ - ValidatorAddr: addr, - StartHeight: height, - EndHeight: height + 10, - SlashedSoFar: sdk.ZeroDec(), - } - keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) - half := sdk.NewDec(1).Quo(sdk.NewDec(2)) - - // First slash should be full - fractionA := keeper.capBySlashingPeriod(ctx, addr, half, height) - require.True(t, fractionA.Equal(half)) - - // Second slash should be capped - fractionB := keeper.capBySlashingPeriod(ctx, addr, half, height) - require.True(t, fractionB.Equal(sdk.ZeroDec())) - - // Third slash should be capped to difference - fractionC := keeper.capBySlashingPeriod(ctx, addr, sdk.OneDec(), height) - require.True(t, fractionC.Equal(half)) -} diff --git a/x/slashing/tags/tags.go b/x/slashing/tags/tags.go new file mode 100644 index 000000000000..9ab5a48bd260 --- /dev/null +++ b/x/slashing/tags/tags.go @@ -0,0 +1,13 @@ +package tags + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Slashing tags +var ( + ActionValidatorUnjailed = []byte("validator-unjailed") + + Action = sdk.TagAction + Validator = "validator" +) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 72ee58e1b26b..c2e7c2e87f1a 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -20,8 +20,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // TODO remove dependencies on staking (should only refer to validator set type from sdk) @@ -45,23 +45,23 @@ func createTestCodec() *codec.Codec { sdk.RegisterCodec(cdc) auth.RegisterCodec(cdc) bank.RegisterCodec(cdc) - stake.RegisterCodec(cdc) + staking.RegisterCodec(cdc) codec.RegisterCrypto(cdc) return cdc } -func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, stake.Keeper, params.Subspace, Keeper) { - keyAcc := sdk.NewKVStoreKey("acc") - keyStake := sdk.NewKVStoreKey("stake") - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keySlashing := sdk.NewKVStoreKey("slashing") - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") +func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, staking.Keeper, params.Subspace, Keeper) { + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySlashing := sdk.NewKVStoreKey(StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) - ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) @@ -69,16 +69,16 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout)) cdc := createTestCodec() - accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, auth.ProtoBaseAccount) + paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) ck := bank.NewBaseKeeper(accountKeeper) - paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) - sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - genesis := stake.DefaultGenesisState() + sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, ck, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + genesis := staking.DefaultGenesisState() - genesis.Pool.LooseTokens = sdk.NewDec(initCoins.MulRaw(int64(len(addrs))).Int64()) + genesis.Pool.NotBondedTokens = sdk.NewInt(initCoins.MulRaw(int64(len(addrs))).Int64()) - _, err = stake.InitGenesis(ctx, sk, genesis) + _, err = staking.InitGenesis(ctx, sk, genesis) require.Nil(t, err) for _, addr := range addrs { @@ -92,7 +92,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s sk.SetHooks(keeper.Hooks()) require.NotPanics(t, func() { - InitGenesis(ctx, keeper, GenesisState{defaults, nil, nil, nil}, genesis) + InitGenesis(ctx, keeper, GenesisState{defaults, nil, nil}, genesis) }) return ctx, ck, sk, paramstore, keeper @@ -113,22 +113,15 @@ func testAddr(addr string) sdk.AccAddress { return res } -func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { - commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) - return stake.MsgCreateValidator{ - Description: stake.Description{}, - Commission: commission, - DelegatorAddr: sdk.AccAddress(address), - ValidatorAddr: address, - PubKey: pubKey, - Delegation: sdk.NewCoin(stakeTypes.DefaultBondDenom, amt), - } +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) staking.MsgCreateValidator { + commission := staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + return staking.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin(stakingTypes.DefaultBondDenom, amt), + staking.Description{}, commission, + ) } -func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) stake.MsgDelegate { - return stake.MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: sdk.NewCoin(stakeTypes.DefaultBondDenom, delAmount), - } +func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) staking.MsgDelegate { + amount := sdk.NewCoin(staking.DefaultBondDenom, delAmount) + return staking.NewMsgDelegate(delAddr, valAddr, amount) } diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 41af4b9aec22..15fb9cd18f57 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -1,7 +1,6 @@ package slashing import ( - "encoding/binary" "fmt" abci "github.com/tendermint/tendermint/abci/types" @@ -11,13 +10,7 @@ import ( ) // slashing begin block functionality -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags sdk.Tags) { - - // Tag the height - heightBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height)) - tags = sdk.NewTags("height", heightBytes) - +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) sdk.Tags { // Iterate over all the validators which *should* have signed this block // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) @@ -37,5 +30,5 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags } } - return + return sdk.EmptyTags() } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 932ac51a93ee..9ee6d03c0403 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -9,7 +9,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" ) func TestBeginBlocker(t *testing.T) { @@ -17,14 +17,14 @@ func TestBeginBlocker(t *testing.T) { addr, pk, amt := addrs[2], pks[2], sdk.NewInt(100) // bond the validator - got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) + require.True(sdk.IntEq(t, amt, sk.Validator(ctx, addr).GetPower())) val := abci.Validator{ Address: pk.Address(), @@ -80,7 +80,7 @@ func TestBeginBlocker(t *testing.T) { } // end block - stake.EndBlocker(ctx, sk) + staking.EndBlocker(ctx, sk) // validator should be jailed validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go deleted file mode 100644 index 40598fcf8000..000000000000 --- a/x/stake/client/cli/query.go +++ /dev/null @@ -1,605 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/cli" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/types" -) - -// GetCmdQueryValidator implements the validator query command. -func GetCmdQueryValidator(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "validator [operator-addr]", - Short: "Query a validator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.ValAddressFromBech32(args[0]) - if err != nil { - return err - } - - key := stake.GetValidatorKey(addr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } else if len(res) == 0 { - return fmt.Errorf("No validator found with address %s", args[0]) - } - - validator := types.MustUnmarshalValidator(cdc, addr, res) - - switch viper.Get(cli.OutputFlag) { - case "text": - human, err := validator.HumanReadableString() - if err != nil { - return err - } - fmt.Println(human) - - case "json": - // parse out the validator - output, err := codec.MarshalJSONIndent(cdc, validator) - if err != nil { - return err - } - - fmt.Println(string(output)) - } - - // TODO: output with proofs / machine parseable etc. - return nil - }, - } - - return cmd -} - -// GetCmdQueryValidators implements the query all validators command. -func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "validators", - Short: "Query for all validators", - RunE: func(cmd *cobra.Command, args []string) error { - key := stake.ValidatorsKey - cliCtx := context.NewCLIContext().WithCodec(cdc) - - resKVs, err := cliCtx.QuerySubspace(key, storeName) - if err != nil { - return err - } - - // parse out the validators - var validators []stake.Validator - for _, kv := range resKVs { - addr := kv.Key[1:] - validator := types.MustUnmarshalValidator(cdc, addr, kv.Value) - validators = append(validators, validator) - } - - switch viper.Get(cli.OutputFlag) { - case "text": - for _, validator := range validators { - resp, err := validator.HumanReadableString() - if err != nil { - return err - } - - fmt.Println(resp) - } - case "json": - output, err := codec.MarshalJSONIndent(cdc, validators) - if err != nil { - return err - } - - fmt.Println(string(output)) - return nil - } - - // TODO: output with proofs / machine parseable etc. - return nil - }, - } - - return cmd -} - -// GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. -func GetCmdQueryValidatorUnbondingDelegations(storeKey string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "unbonding-delegations-from [operator-addr]", - Short: "Query all unbonding delegatations from a validator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.ValAddressFromBech32(args[0]) - if err != nil { - return err - } - - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.NewQueryValidatorParams(valAddr) - - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - res, err := cliCtx.QueryWithData( - fmt.Sprintf("custom/%s/validatorUnbondingDelegations", storeKey), - bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - return cmd -} - -// GetCmdQueryValidatorRedelegations implements the query all redelegatations from a validator command. -func GetCmdQueryValidatorRedelegations(storeKey string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "redelegations-from [operator-addr]", - Short: "Query all outgoing redelegatations from a validator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.ValAddressFromBech32(args[0]) - if err != nil { - return err - } - - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.NewQueryValidatorParams(valAddr) - - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - res, err := cliCtx.QueryWithData( - fmt.Sprintf("custom/%s/validatorRedelegations", storeKey), - bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - return cmd -} - -// GetCmdQueryDelegation the query delegation command. -func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "delegation", - Short: "Query a delegation based on address and validator address", - RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) - if err != nil { - return err - } - - delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) - if err != nil { - return err - } - - key := stake.GetDelegationKey(delAddr, valAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } - - // parse out the delegation - - delegation, err := types.UnmarshalDelegation(cdc, key, res) - if err != nil { - return err - } - - switch viper.Get(cli.OutputFlag) { - case "text": - resp, err := delegation.HumanReadableString() - if err != nil { - return err - } - - fmt.Println(resp) - case "json": - output, err := codec.MarshalJSONIndent(cdc, delegation) - if err != nil { - return err - } - - fmt.Println(string(output)) - return nil - } - - return nil - }, - } - - cmd.Flags().AddFlagSet(fsValidator) - cmd.Flags().AddFlagSet(fsDelegator) - - return cmd -} - -// GetCmdQueryDelegations implements the command to query all the delegations -// made from one delegator. -func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "delegations [delegator-addr]", - Short: "Query all delegations made from one delegator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - key := stake.GetDelegationsKey(delegatorAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - resKVs, err := cliCtx.QuerySubspace(key, storeName) - if err != nil { - return err - } - - // parse out the validators - var delegations []stake.Delegation - for _, kv := range resKVs { - delegation := types.MustUnmarshalDelegation(cdc, kv.Key, kv.Value) - delegations = append(delegations, delegation) - } - - output, err := codec.MarshalJSONIndent(cdc, delegations) - if err != nil { - return err - } - - fmt.Println(string(output)) - - // TODO: output with proofs / machine parseable etc. - return nil - }, - } - - return cmd -} - -// GetCmdQueryValidatorDelegations implements the command to query all the -// delegations to a specific validator. -func GetCmdQueryValidatorDelegations(storeKey string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "delegations-to [validator-addr]", - Short: "Query all delegations made to one validator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - validatorAddr, err := sdk.ValAddressFromBech32(args[0]) - if err != nil { - return err - } - - params := stake.NewQueryValidatorParams(validatorAddr) - - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validatorDelegations", storeKey), bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - return cmd -} - -// GetCmdQueryUnbondingDelegation implements the command to query a single -// unbonding-delegation record. -func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "unbonding-delegation", - Short: "Query an unbonding-delegation record based on delegator and validator address", - RunE: func(cmd *cobra.Command, args []string) error { - valAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) - if err != nil { - return err - } - - delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) - if err != nil { - return err - } - - key := stake.GetUBDKey(delAddr, valAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } - - // parse out the unbonding delegation - ubd := types.MustUnmarshalUBD(cdc, key, res) - - switch viper.Get(cli.OutputFlag) { - case "text": - resp, err := ubd.HumanReadableString() - if err != nil { - return err - } - - fmt.Println(resp) - case "json": - output, err := codec.MarshalJSONIndent(cdc, ubd) - if err != nil { - return err - } - - fmt.Println(string(output)) - return nil - } - - return nil - }, - } - - cmd.Flags().AddFlagSet(fsValidator) - cmd.Flags().AddFlagSet(fsDelegator) - - return cmd -} - -// GetCmdQueryUnbondingDelegations implements the command to query all the -// unbonding-delegation records for a delegator. -func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "unbonding-delegations [delegator-addr]", - Short: "Query all unbonding-delegations records for one delegator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - key := stake.GetUBDsKey(delegatorAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - resKVs, err := cliCtx.QuerySubspace(key, storeName) - if err != nil { - return err - } - - // parse out the unbonding delegations - var ubds []stake.UnbondingDelegation - for _, kv := range resKVs { - ubd := types.MustUnmarshalUBD(cdc, kv.Key, kv.Value) - ubds = append(ubds, ubd) - } - - output, err := codec.MarshalJSONIndent(cdc, ubds) - if err != nil { - return err - } - - fmt.Println(string(output)) - - // TODO: output with proofs / machine parseable etc. - return nil - }, - } - - return cmd -} - -// GetCmdQueryRedelegation implements the command to query a single -// redelegation record. -func GetCmdQueryRedelegation(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "redelegation", - Short: "Query a redelegation record based on delegator and a source and destination validator address", - RunE: func(cmd *cobra.Command, args []string) error { - valSrcAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) - if err != nil { - return err - } - - valDstAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) - if err != nil { - return err - } - - delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) - if err != nil { - return err - } - - key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } - - // parse out the unbonding delegation - red := types.MustUnmarshalRED(cdc, key, res) - - switch viper.Get(cli.OutputFlag) { - case "text": - resp, err := red.HumanReadableString() - if err != nil { - return err - } - - fmt.Println(resp) - case "json": - output, err := codec.MarshalJSONIndent(cdc, red) - if err != nil { - return err - } - - fmt.Println(string(output)) - return nil - } - - return nil - }, - } - - cmd.Flags().AddFlagSet(fsRedelegation) - cmd.Flags().AddFlagSet(fsDelegator) - - return cmd -} - -// GetCmdQueryRedelegations implements the command to query all the -// redelegation records for a delegator. -func GetCmdQueryRedelegations(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "redelegations [delegator-addr]", - Short: "Query all redelegations records for one delegator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - key := stake.GetREDsKey(delegatorAddr) - cliCtx := context.NewCLIContext().WithCodec(cdc) - - resKVs, err := cliCtx.QuerySubspace(key, storeName) - if err != nil { - return err - } - - // parse out the validators - var reds []stake.Redelegation - for _, kv := range resKVs { - red := types.MustUnmarshalRED(cdc, kv.Key, kv.Value) - reds = append(reds, red) - } - - output, err := codec.MarshalJSONIndent(cdc, reds) - if err != nil { - return err - } - - fmt.Println(string(output)) - - // TODO: output with proofs / machine parseable etc. - return nil - }, - } - - return cmd -} - -// GetCmdQueryPool implements the pool query command. -func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "pool", - Short: "Query the current staking pool values", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - key := stake.PoolKey - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } - - pool := types.MustUnmarshalPool(cdc, res) - - switch viper.Get(cli.OutputFlag) { - case "text": - human := pool.HumanReadableString() - - fmt.Println(human) - - case "json": - // parse out the pool - output, err := codec.MarshalJSONIndent(cdc, pool) - if err != nil { - return err - } - - fmt.Println(string(output)) - } - return nil - }, - } - - return cmd -} - -// GetCmdQueryPool implements the params query command. -func GetCmdQueryParams(storeName string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "parameters", - Short: "Query the current staking parameters information", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - bz, err := cliCtx.QueryWithData("custom/stake/"+stake.QueryParameters, nil) - if err != nil { - return err - } - - var params stake.Params - err = cdc.UnmarshalJSON(bz, ¶ms) - if err != nil { - return err - } - - switch viper.Get(cli.OutputFlag) { - case "text": - human := params.HumanReadableString() - - fmt.Println(human) - - case "json": - // parse out the params - output, err := codec.MarshalJSONIndent(cdc, params) - if err != nil { - return err - } - - fmt.Println(string(output)) - } - return nil - }, - } - - return cmd -} diff --git a/x/stake/keeper/hooks.go b/x/stake/keeper/hooks.go deleted file mode 100644 index 4ae158786eec..000000000000 --- a/x/stake/keeper/hooks.go +++ /dev/null @@ -1,61 +0,0 @@ -//nolint -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Expose the hooks if present -func (k Keeper) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorCreated(ctx, valAddr) - } -} - -func (k Keeper) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorModified(ctx, valAddr) - } -} - -func (k Keeper) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorRemoved(ctx, consAddr, valAddr) - } -} - -func (k Keeper) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorBonded(ctx, consAddr, valAddr) - } -} - -func (k Keeper) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorPowerDidChange(ctx, consAddr, valAddr) - } -} - -func (k Keeper) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) - } -} - -func (k Keeper) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnDelegationCreated(ctx, delAddr, valAddr) - } -} - -func (k Keeper) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnDelegationSharesModified(ctx, delAddr, valAddr) - } -} - -func (k Keeper) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - if k.hooks != nil { - k.hooks.OnDelegationRemoved(ctx, delAddr, valAddr) - } -} diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go deleted file mode 100644 index 0d49b1db58ad..000000000000 --- a/x/stake/types/delegation.go +++ /dev/null @@ -1,292 +0,0 @@ -package types - -import ( - "bytes" - "fmt" - "time" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// DVPair is struct that just has a delegator-validator pair with no other data. -// 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 -} - -// 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 -} - -// Delegation represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -type Delegation struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Shares sdk.Dec `json:"shares"` -} - -type delegationValue struct { - Shares sdk.Dec -} - -// return the delegation without fields contained within the key for the store -func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { - val := delegationValue{ - delegation.Shares, - } - return cdc.MustMarshalBinaryLengthPrefixed(val) -} - -// return the delegation without fields contained within the key for the store -func MustUnmarshalDelegation(cdc *codec.Codec, key, value []byte) Delegation { - delegation, err := UnmarshalDelegation(cdc, key, value) - if err != nil { - panic(err) - } - return delegation -} - -// return the delegation without fields contained within the key for the store -func UnmarshalDelegation(cdc *codec.Codec, key, value []byte) (delegation Delegation, err error) { - var storeValue delegationValue - err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) - if err != nil { - err = fmt.Errorf("%v: %v", ErrNoDelegation(DefaultCodespace).Data(), err) - return - } - - addrs := key[1:] // remove prefix bytes - if len(addrs) != 2*sdk.AddrLen { - err = fmt.Errorf("%v", ErrBadDelegationAddr(DefaultCodespace).Data()) - return - } - - delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) - valAddr := sdk.ValAddress(addrs[sdk.AddrLen:]) - - return Delegation{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Shares: storeValue.Shares, - }, nil -} - -// nolint -func (d Delegation) Equal(d2 Delegation) bool { - return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && - bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && - d.Shares.Equal(d2.Shares) -} - -// ensure fulfills the sdk validator types -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) GetShares() sdk.Dec { return d.Shares } - -// HumanReadableString returns a human readable string representation of a -// Delegation. An error is returned if the Delegation's delegator or validator -// addresses cannot be Bech32 encoded. -func (d Delegation) HumanReadableString() (string, error) { - resp := "Delegation \n" - resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) - resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) - resp += fmt.Sprintf("Shares: %s\n", d.Shares.String()) - - return resp, nil -} - -// UnbondingDelegation reflects a delegation's passive unbonding queue. -type UnbondingDelegation struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator - ValidatorAddr sdk.ValAddress `json:"validator_addr"` // validator unbonding from operator addr - CreationHeight int64 `json:"creation_height"` // height which the unbonding took place - MinTime time.Time `json:"min_time"` // unix time for unbonding completion - InitialBalance sdk.Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion - Balance sdk.Coin `json:"balance"` // atoms to receive at completion -} - -type ubdValue struct { - CreationHeight int64 - MinTime time.Time - InitialBalance sdk.Coin - Balance sdk.Coin -} - -// return the unbonding delegation without fields contained within the key for the store -func MustMarshalUBD(cdc *codec.Codec, ubd UnbondingDelegation) []byte { - val := ubdValue{ - ubd.CreationHeight, - ubd.MinTime, - ubd.InitialBalance, - ubd.Balance, - } - return cdc.MustMarshalBinaryLengthPrefixed(val) -} - -// unmarshal a unbonding delegation from a store key and value -func MustUnmarshalUBD(cdc *codec.Codec, key, value []byte) UnbondingDelegation { - ubd, err := UnmarshalUBD(cdc, key, value) - if err != nil { - panic(err) - } - return ubd -} - -// unmarshal a unbonding delegation from a store key and value -func UnmarshalUBD(cdc *codec.Codec, key, value []byte) (ubd UnbondingDelegation, err error) { - var storeValue ubdValue - err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) - if err != nil { - return - } - - addrs := key[1:] // remove prefix bytes - if len(addrs) != 2*sdk.AddrLen { - err = fmt.Errorf("%v", ErrBadDelegationAddr(DefaultCodespace).Data()) - return - } - delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) - valAddr := sdk.ValAddress(addrs[sdk.AddrLen:]) - - return UnbondingDelegation{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - CreationHeight: storeValue.CreationHeight, - MinTime: storeValue.MinTime, - InitialBalance: storeValue.InitialBalance, - Balance: storeValue.Balance, - }, nil -} - -// nolint -func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { - bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) - bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) - return bytes.Equal(bz1, bz2) -} - -// HumanReadableString returns a human readable string representation of an -// UnbondingDelegation. An error is returned if the UnbondingDelegation's -// delegator or validator addresses cannot be Bech32 encoded. -func (d UnbondingDelegation) HumanReadableString() (string, error) { - resp := "Unbonding Delegation \n" - resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) - resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) - resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) - resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) - resp += fmt.Sprintf("Expected balance: %s", d.Balance.String()) - - return resp, nil - -} - -// Redelegation reflects a delegation's passive re-delegation queue. -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 - CreationHeight int64 `json:"creation_height"` // height which the redelegation took place - MinTime time.Time `json:"min_time"` // unix time for redelegation completion - InitialBalance sdk.Coin `json:"initial_balance"` // initial balance when redelegation started - Balance sdk.Coin `json:"balance"` // current balance - SharesSrc sdk.Dec `json:"shares_src"` // amount of source shares redelegating - SharesDst sdk.Dec `json:"shares_dst"` // amount of destination shares redelegating -} - -type redValue struct { - CreationHeight int64 - MinTime time.Time - InitialBalance sdk.Coin - Balance sdk.Coin - SharesSrc sdk.Dec - SharesDst sdk.Dec -} - -// return the redelegation without fields contained within the key for the store -func MustMarshalRED(cdc *codec.Codec, red Redelegation) []byte { - val := redValue{ - red.CreationHeight, - red.MinTime, - red.InitialBalance, - red.Balance, - red.SharesSrc, - red.SharesDst, - } - return cdc.MustMarshalBinaryLengthPrefixed(val) -} - -// unmarshal a redelegation from a store key and value -func MustUnmarshalRED(cdc *codec.Codec, key, value []byte) Redelegation { - red, err := UnmarshalRED(cdc, key, value) - if err != nil { - panic(err) - } - return red -} - -// unmarshal a redelegation from a store key and value -func UnmarshalRED(cdc *codec.Codec, key, value []byte) (red Redelegation, err error) { - var storeValue redValue - err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) - if err != nil { - return - } - - addrs := key[1:] // remove prefix bytes - if len(addrs) != 3*sdk.AddrLen { - err = fmt.Errorf("%v", ErrBadRedelegationAddr(DefaultCodespace).Data()) - return - } - delAddr := sdk.AccAddress(addrs[:sdk.AddrLen]) - valSrcAddr := sdk.ValAddress(addrs[sdk.AddrLen : 2*sdk.AddrLen]) - valDstAddr := sdk.ValAddress(addrs[2*sdk.AddrLen:]) - - return Redelegation{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - CreationHeight: storeValue.CreationHeight, - MinTime: storeValue.MinTime, - InitialBalance: storeValue.InitialBalance, - Balance: storeValue.Balance, - SharesSrc: storeValue.SharesSrc, - SharesDst: storeValue.SharesDst, - }, nil -} - -// nolint -func (d Redelegation) Equal(d2 Redelegation) bool { - bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) - bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) - return bytes.Equal(bz1, bz2) -} - -// HumanReadableString returns a human readable string representation of a -// Redelegation. An error is returned if the UnbondingDelegation's delegator or -// validator addresses cannot be Bech32 encoded. -func (d Redelegation) HumanReadableString() (string, error) { - resp := "Redelegation \n" - resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) - resp += fmt.Sprintf("Source Validator: %s\n", d.ValidatorSrcAddr) - resp += fmt.Sprintf("Destination Validator: %s\n", d.ValidatorDstAddr) - resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) - resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) - resp += fmt.Sprintf("Source shares: %s\n", d.SharesSrc.String()) - resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String()) - - return resp, nil - -} diff --git a/x/stake/types/delegation_test.go b/x/stake/types/delegation_test.go deleted file mode 100644 index ee7d81bf5b5f..000000000000 --- a/x/stake/types/delegation_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package types - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestDelegationEqual(t *testing.T) { - d1 := Delegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - Shares: sdk.NewDec(100), - } - d2 := Delegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - Shares: sdk.NewDec(100), - } - - ok := d1.Equal(d2) - require.True(t, ok) - - d2.ValidatorAddr = addr3 - d2.Shares = sdk.NewDec(200) - - ok = d1.Equal(d2) - require.False(t, ok) -} - -func TestDelegationHumanReadableString(t *testing.T) { - d := Delegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - Shares: sdk.NewDec(100), - } - - // NOTE: Being that the validator's keypair is random, we cannot test the - // actual contents of the string. - valStr, err := d.HumanReadableString() - require.Nil(t, err) - require.NotEmpty(t, valStr) -} - -func TestUnbondingDelegationEqual(t *testing.T) { - ud1 := UnbondingDelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - } - ud2 := UnbondingDelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - } - - ok := ud1.Equal(ud2) - require.True(t, ok) - - ud2.ValidatorAddr = addr3 - - ud2.MinTime = time.Unix(20*20*2, 0) - ok = ud1.Equal(ud2) - require.False(t, ok) -} - -func TestUnbondingDelegationHumanReadableString(t *testing.T) { - ud := UnbondingDelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorAddr: addr2, - } - - // NOTE: Being that the validator's keypair is random, we cannot test the - // actual contents of the string. - valStr, err := ud.HumanReadableString() - require.Nil(t, err) - require.NotEmpty(t, valStr) -} - -func TestRedelegationEqual(t *testing.T) { - r1 := Redelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorSrcAddr: addr2, - ValidatorDstAddr: addr3, - } - r2 := Redelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorSrcAddr: addr2, - ValidatorDstAddr: addr3, - } - - ok := r1.Equal(r2) - require.True(t, ok) - - r2.SharesDst = sdk.NewDec(10) - r2.SharesSrc = sdk.NewDec(20) - r2.MinTime = time.Unix(20*20*2, 0) - - ok = r1.Equal(r2) - require.False(t, ok) -} - -func TestRedelegationHumanReadableString(t *testing.T) { - r := Redelegation{ - DelegatorAddr: sdk.AccAddress(addr1), - ValidatorSrcAddr: addr2, - ValidatorDstAddr: addr3, - SharesDst: sdk.NewDec(10), - SharesSrc: sdk.NewDec(20), - } - - // NOTE: Being that the validator's keypair is random, we cannot test the - // actual contents of the string. - valStr, err := r.HumanReadableString() - require.Nil(t, err) - require.NotEmpty(t, valStr) -} diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go deleted file mode 100644 index f877df0b1b3c..000000000000 --- a/x/stake/types/pool_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestPoolEqual(t *testing.T) { - p1 := InitialPool() - p2 := InitialPool() - require.True(t, p1.Equal(p2)) - p2.BondedTokens = sdk.NewDec(3) - require.False(t, p1.Equal(p2)) -} - -func TestAddBondedTokens(t *testing.T) { - pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) - pool.BondedTokens = sdk.NewDec(10) - - pool = pool.looseTokensToBonded(sdk.NewDec(10)) - - require.True(sdk.DecEq(t, sdk.NewDec(20), pool.BondedTokens)) - require.True(sdk.DecEq(t, sdk.NewDec(0), pool.LooseTokens)) -} - -func TestRemoveBondedTokens(t *testing.T) { - pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) - pool.BondedTokens = sdk.NewDec(10) - - pool = pool.bondedTokensToLoose(sdk.NewDec(5)) - - require.True(sdk.DecEq(t, sdk.NewDec(5), pool.BondedTokens)) - require.True(sdk.DecEq(t, sdk.NewDec(15), pool.LooseTokens)) -} diff --git a/x/stake/app_test.go b/x/staking/app_test.go similarity index 81% rename from x/stake/app_test.go rename to x/staking/app_test.go index de16afde3720..f95bce1326bf 100644 --- a/x/stake/app_test.go +++ b/x/staking/app_test.go @@ -1,4 +1,4 @@ -package stake +package staking import ( "testing" @@ -10,8 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/params" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // getMockApp returns an initialized mock application for this module. @@ -20,26 +19,21 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { RegisterCodec(mApp.Cdc) - keyStake := sdk.NewKVStoreKey("stake") - - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") + keyStaking := sdk.NewKVStoreKey(StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(TStoreKey) bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) - pk := params.NewKeeper(mApp.Cdc, keyParams, tkeyParams) - - keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, pk.Subspace(DefaultParamspace), DefaultCodespace) + keeper := NewKeeper(mApp.Cdc, keyStaking, tkeyStaking, bankKeeper, mApp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) - mApp.Router().AddRoute("stake", NewHandler(keeper)) + mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) mApp.SetEndBlocker(getEndBlocker(keeper)) mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mApp.CompleteSetup(keyStake, tkeyStake, keyParams, tkeyParams)) + require.NoError(t, mApp.CompleteSetup(keyStaking, tkeyStaking)) return mApp, keeper } -// getEndBlocker returns a stake endblocker. +// getEndBlocker returns a staking endblocker. func getEndBlocker(keeper Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates, tags := EndBlocker(ctx, keeper) @@ -57,10 +51,10 @@ func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stakeGenesis := DefaultGenesisState() - stakeGenesis.Pool.LooseTokens = sdk.NewDec(100000) + stakingGenesis := DefaultGenesisState() + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt(100000) - validators, err := InitGenesis(ctx, keeper, stakeGenesis) + validators, err := InitGenesis(ctx, keeper, stakingGenesis) if err != nil { panic(err) } @@ -100,11 +94,11 @@ func checkDelegation( require.False(t, found) } -func TestStakeMsgs(t *testing.T) { +func TestStakingMsgs(t *testing.T) { mApp, keeper := getMockApp(t) - genCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 42) - bondCoin := sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10) + genCoin := sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 42) + bondCoin := sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -133,7 +127,7 @@ func TestStakeMsgs(t *testing.T) { validator := checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) require.Equal(t, sdk.Bonded, validator.Status) - require.True(sdk.DecEq(t, sdk.NewDec(10), validator.BondedTokens())) + require.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens())) // addr1 create validator on behalf of addr2 createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf( @@ -147,7 +141,7 @@ func TestStakeMsgs(t *testing.T) { 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.DecEq(t, sdk.NewDec(10), validator.Tokens)) + require.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) // check the bond that should have been created as well checkDelegation(t, mApp, keeper, addr1, sdk.ValAddress(addr1), true, sdk.NewDec(10)) @@ -169,7 +163,7 @@ func TestStakeMsgs(t *testing.T) { checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDec(10)) // begin unbonding - beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDec(10)) + beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), sdk.NewDec(10)) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []uint64{0}, []uint64{2}, true, true, priv2) // delegation should exist anymore diff --git a/x/stake/client/cli/flags.go b/x/staking/client/cli/flags.go similarity index 66% rename from x/stake/client/cli/flags.go rename to x/staking/client/cli/flags.go index 97a62ebd99fc..6e1c4e51d73e 100644 --- a/x/stake/client/cli/flags.go +++ b/x/staking/client/cli/flags.go @@ -3,7 +3,7 @@ package cli import ( flag "github.com/spf13/pflag" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // nolint @@ -46,24 +46,24 @@ var ( ) func init() { - FsPk.String(FlagPubKey, "", "Bech32-encoded PubKey of the validator. ") + FsPk.String(FlagPubKey, "", "The Bech32 encoded PubKey of the validator") FsAmount.String(FlagAmount, "", "Amount of coins to bond") fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") fsShares.String(FlagSharesFraction, "", "Fraction of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") - fsDescriptionCreate.String(FlagMoniker, "", "validator name") - fsDescriptionCreate.String(FlagIdentity, "", "optional identity signature (ex. UPort or Keybase)") - fsDescriptionCreate.String(FlagWebsite, "", "optional website") - fsDescriptionCreate.String(FlagDetails, "", "optional details") + fsDescriptionCreate.String(FlagMoniker, "", "The validator's name") + fsDescriptionCreate.String(FlagIdentity, "", "The optional identity signature (ex. UPort or Keybase)") + fsDescriptionCreate.String(FlagWebsite, "", "The validator's (optional) website") + fsDescriptionCreate.String(FlagDetails, "", "The validator's (optional) details") fsCommissionUpdate.String(FlagCommissionRate, "", "The new commission rate percentage") FsCommissionCreate.String(FlagCommissionRate, "", "The initial commission rate percentage") FsCommissionCreate.String(FlagCommissionMaxRate, "", "The maximum commission rate percentage") FsCommissionCreate.String(FlagCommissionMaxChangeRate, "", "The maximum commission change rate percentage (per day)") - fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "validator name") - fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "optional identity signature (ex. UPort or Keybase)") - fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "optional website") - fsDescriptionEdit.String(FlagDetails, types.DoNotModifyDesc, "optional details") - fsValidator.String(FlagAddressValidator, "", "bech address of the validator") - fsDelegator.String(FlagAddressDelegator, "", "bech address of the delegator") - fsRedelegation.String(FlagAddressValidatorSrc, "", "bech address of the source validator") - fsRedelegation.String(FlagAddressValidatorDst, "", "bech address of the destination validator") + fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "The validator's name") + fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "The (optional) identity signature (ex. UPort or Keybase)") + 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/query.go b/x/staking/client/cli/query.go new file mode 100644 index 000000000000..5d2b20df1a44 --- /dev/null +++ b/x/staking/client/cli/query.go @@ -0,0 +1,389 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// GetCmdQueryValidator implements the validator query command. +func GetCmdQueryValidator(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validator [operator-addr]", + Short: "Query a validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + addr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(staking.GetValidatorKey(addr), storeName) + if err != nil { + return err + } + + return cliCtx.PrintOutput(types.MustUnmarshalValidator(cdc, res)) + }, + } +} + +// GetCmdQueryValidators implements the query all validators command. +func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validators", + Short: "Query for all validators", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, err := cliCtx.QuerySubspace(staking.ValidatorsKey, storeName) + if err != nil { + return err + } + + var validators staking.Validators + for _, kv := range resKVs { + validators = append(validators, types.MustUnmarshalValidator(cdc, kv.Value)) + } + + return cliCtx.PrintOutput(validators) + }, + } +} + +// GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. +func GetCmdQueryValidatorUnbondingDelegations(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "unbonding-delegations-from [operator-addr]", + Short: "Query all unbonding delegatations from a validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(staking.NewQueryValidatorParams(valAddr)) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s", storeKey, staking.QueryValidatorUnbondingDelegations) + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var ubds staking.UnbondingDelegations + cdc.MustUnmarshalJSON(res, &ubds) + return cliCtx.PrintOutput(ubds) + }, + } +} + +// GetCmdQueryValidatorRedelegations implements the query all redelegatations from a validator command. +func GetCmdQueryValidatorRedelegations(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "redelegations-from [operator-addr]", + Short: "Query all outgoing redelegatations from a validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(staking.NewQueryValidatorParams(valAddr)) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s", storeKey, staking.QueryValidatorRedelegations) + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var reds staking.Redelegations + cdc.MustUnmarshalJSON(res, &reds) + return cliCtx.PrintOutput(reds) + }, + } +} + +// GetCmdQueryDelegation the query delegation command. +func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegation [delegator-addr] [validator-addr]", + Short: "Query a delegation based on address and validator address", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valAddr, err := sdk.ValAddressFromBech32(args[1]) + if err != nil { + return err + } + + delAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(staking.GetDelegationKey(delAddr, valAddr), storeName) + if err != nil { + return err + } + + delegation, err := types.UnmarshalDelegation(cdc, res) + if err != nil { + return err + } + + return cliCtx.PrintOutput(delegation) + }, + } +} + +// GetCmdQueryDelegations implements the command to query all the delegations +// made from one delegator. +func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegations [delegator-addr]", + Short: "Query all delegations made from one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + resKVs, err := cliCtx.QuerySubspace(staking.GetDelegationsKey(delegatorAddr), storeName) + if err != nil { + return err + } + + var delegations staking.Delegations + for _, kv := range resKVs { + delegations = append(delegations, types.MustUnmarshalDelegation(cdc, kv.Value)) + } + + return cliCtx.PrintOutput(delegations) + }, + } +} + +// GetCmdQueryValidatorDelegations implements the command to query all the +// delegations to a specific validator. +func GetCmdQueryValidatorDelegations(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegations-to [validator-addr]", + Short: "Query all delegations made to one validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + validatorAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(staking.NewQueryValidatorParams(validatorAddr)) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s", storeKey, staking.QueryValidatorDelegations) + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var dels staking.Delegations + cdc.MustUnmarshalJSON(res, &dels) + return cliCtx.PrintOutput(dels) + }, + } +} + +// GetCmdQueryUnbondingDelegation implements the command to query a single +// unbonding-delegation record. +func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "unbonding-delegation [delegator-addr] [validator-addr]", + Short: "Query an unbonding-delegation record based on delegator and validator address", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valAddr, err := sdk.ValAddressFromBech32(args[1]) + if err != nil { + return err + } + + delAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(staking.GetUBDKey(delAddr, valAddr), storeName) + if err != nil { + return err + } + + return cliCtx.PrintOutput(types.MustUnmarshalUBD(cdc, res)) + + }, + } +} + +// GetCmdQueryUnbondingDelegations implements the command to query all the +// unbonding-delegation records for a delegator. +func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + resKVs, err := cliCtx.QuerySubspace(staking.GetUBDsKey(delegatorAddr), storeName) + if err != nil { + return err + } + + var ubds staking.UnbondingDelegations + for _, kv := range resKVs { + ubds = append(ubds, types.MustUnmarshalUBD(cdc, kv.Value)) + } + + return cliCtx.PrintOutput(ubds) + }, + } +} + +// GetCmdQueryRedelegation implements the command to query a single +// redelegation record. +func GetCmdQueryRedelegation(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "redelegation [delegator-addr] [validator-src-addr] [validator-dst-addr]", + Short: "Query a redelegation record based on delegator and a source and destination validator address", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valSrcAddr, err := sdk.ValAddressFromBech32(args[1]) + if err != nil { + return err + } + + valDstAddr, err := sdk.ValAddressFromBech32(args[2]) + if err != nil { + return err + } + + delAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(staking.GetREDKey(delAddr, valSrcAddr, valDstAddr), storeName) + if err != nil { + return err + } + + return cliCtx.PrintOutput(types.MustUnmarshalRED(cdc, res)) + }, + } +} + +// GetCmdQueryRedelegations implements the command to query all the +// redelegation records for a delegator. +func GetCmdQueryRedelegations(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "redelegations [delegator-addr]", + Short: "Query all redelegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + resKVs, err := cliCtx.QuerySubspace(staking.GetREDsKey(delegatorAddr), storeName) + if err != nil { + return err + } + + var reds staking.Redelegations + for _, kv := range resKVs { + reds = append(reds, types.MustUnmarshalRED(cdc, kv.Value)) + } + + return cliCtx.PrintOutput(reds) + }, + } +} + +// GetCmdQueryPool implements the pool query command. +func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "pool", + Short: "Query the current staking pool values", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryStore(staking.PoolKey, storeName) + if err != nil { + return err + } + + return cliCtx.PrintOutput(types.MustUnmarshalPool(cdc, res)) + }, + } +} + +// GetCmdQueryPool implements the params query command. +func GetCmdQueryParams(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "params", + Short: "Query the current staking parameters information", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", storeName, staking.QueryParameters) + bz, err := cliCtx.QueryWithData(route, nil) + if err != nil { + return err + } + + var params staking.Params + cdc.MustUnmarshalJSON(bz, ¶ms) + return cliCtx.PrintOutput(params) + }, + } +} diff --git a/x/stake/client/cli/tx.go b/x/staking/client/cli/tx.go similarity index 86% rename from x/stake/client/cli/tx.go rename to x/staking/client/cli/tx.go index c5f2f587c564..4a2ed6eb4def 100644 --- a/x/stake/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -2,16 +2,17 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/x/auth" "os" + "github.com/cosmos/cosmos-sdk/x/auth" + "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" sdk "github.com/cosmos/cosmos-sdk/types" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -28,7 +29,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - cliCtx, txBldr, msg, err := BuildCreateValidatorMsg(cliCtx, txBldr) + txBldr, msg, err := BuildCreateValidatorMsg(cliCtx, txBldr) if err != nil { return err } @@ -48,8 +49,9 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { cmd.Flags().AddFlagSet(FsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) cmd.Flags().Bool(FlagGenesisFormat, false, "Export the transaction in gen-tx format; it implies --generate-only") - cmd.Flags().String(FlagIP, "", fmt.Sprintf("Node's public IP. It takes effect only when used in combination with --%s", FlagGenesisFormat)) - cmd.Flags().String(FlagNodeID, "", "Node's ID") + cmd.Flags().String(FlagIP, "", fmt.Sprintf("The node's public IP. It takes effect only when used in combination with --%s", FlagGenesisFormat)) + cmd.Flags().String(FlagNodeID, "", "The node's ID") + cmd.MarkFlagRequired(client.FlagFrom) cmd.MarkFlagRequired(FlagAmount) cmd.MarkFlagRequired(FlagPubKey) @@ -62,7 +64,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "edit-validator", - Short: "edit and existing validator account", + Short: "edit an existing validator account", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(auth.DefaultTxEncoder(cdc)) cliCtx := context.NewCLIContext(). @@ -74,7 +76,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { return err } - description := stake.Description{ + description := staking.Description{ Moniker: viper.GetString(FlagMoniker), Identity: viper.GetString(FlagIdentity), Website: viper.GetString(FlagWebsite), @@ -93,7 +95,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { newRate = &rate } - msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate) + msg := staking.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) @@ -114,7 +116,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", - Short: "delegate liquid tokens to an validator", + Short: "delegate liquid tokens to a validator", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(auth.DefaultTxEncoder(cdc)) cliCtx := context.NewCLIContext(). @@ -136,7 +138,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { return err } - msg := stake.NewMsgDelegate(delAddr, valAddr, amount) + msg := staking.NewMsgDelegate(delAddr, valAddr, amount) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) @@ -191,7 +193,7 @@ func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return err } - msg := stake.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, sharesAmount) + msg := staking.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, sharesAmount) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) @@ -239,7 +241,7 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { return err } - msg := stake.NewMsgBeginUnbonding(delAddr, valAddr, sharesAmount) + msg := staking.NewMsgUndelegate(delAddr, valAddr, sharesAmount) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) @@ -256,25 +258,25 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { } // BuildCreateValidatorMsg makes a new MsgCreateValidator. -func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder) (context.CLIContext, authtxb.TxBuilder, sdk.Msg, error) { +func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder) (authtxb.TxBuilder, sdk.Msg, error) { amounstStr := viper.GetString(FlagAmount) amount, err := sdk.ParseCoin(amounstStr) if err != nil { - return cliCtx, txBldr, nil, err + return txBldr, nil, err } valAddr, err := cliCtx.GetFromAddress() if err != nil { - return cliCtx, txBldr, nil, err + return txBldr, nil, err } pkStr := viper.GetString(FlagPubKey) pk, err := sdk.GetConsPubKeyBech32(pkStr) if err != nil { - return cliCtx, txBldr, nil, err + return txBldr, nil, err } - description := stake.NewDescription( + description := staking.NewDescription( viper.GetString(FlagMoniker), viper.GetString(FlagIdentity), viper.GetString(FlagWebsite), @@ -287,21 +289,23 @@ func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder maxChangeRateStr := viper.GetString(FlagCommissionMaxChangeRate) commissionMsg, err := buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr) if err != nil { - return cliCtx, txBldr, nil, err + return txBldr, nil, err } + delAddr := viper.GetString(FlagAddressDelegator) + var msg sdk.Msg - if viper.GetString(FlagAddressDelegator) != "" { - delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) + if delAddr != "" { + delAddr, err := sdk.AccAddressFromBech32(delAddr) if err != nil { - return cliCtx, txBldr, nil, err + return txBldr, nil, err } - msg = stake.NewMsgCreateValidatorOnBehalfOf( + msg = staking.NewMsgCreateValidatorOnBehalfOf( delAddr, sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, ) } else { - msg = stake.NewMsgCreateValidator( + msg = staking.NewMsgCreateValidator( sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, ) } @@ -313,5 +317,5 @@ func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder txBldr = txBldr.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip)) } } - return cliCtx, txBldr, msg, nil + return txBldr, msg, nil } diff --git a/x/stake/client/cli/utils.go b/x/staking/client/cli/utils.go similarity index 92% rename from x/stake/client/cli/utils.go rename to x/staking/client/cli/utils.go index 848e1725d05e..43eb10e4b0ab 100644 --- a/x/stake/client/cli/utils.go +++ b/x/staking/client/cli/utils.go @@ -6,8 +6,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) func getShares( @@ -42,7 +42,7 @@ func getShares( } // make a query to get the existing delegation shares - key := stake.GetDelegationKey(delAddr, valAddr) + key := staking.GetDelegationKey(delAddr, valAddr) cliCtx := context.NewCLIContext(). WithCodec(cdc). WithAccountDecoder(cdc) @@ -52,7 +52,7 @@ func getShares( return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) } - delegation, err := types.UnmarshalDelegation(cdc, key, resQuery) + delegation, err := types.UnmarshalDelegation(cdc, resQuery) if err != nil { return sdk.ZeroDec(), err } diff --git a/x/stake/client/module_client.go b/x/staking/client/module_client.go similarity index 84% rename from x/stake/client/module_client.go rename to x/staking/client/module_client.go index 03a7c25e2a65..ae2c8e7e80e3 100644 --- a/x/stake/client/module_client.go +++ b/x/staking/client/module_client.go @@ -5,7 +5,7 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/x/stake/client/cli" + "github.com/cosmos/cosmos-sdk/x/staking/client/cli" ) // ModuleClient exports all client functionality from this module @@ -20,11 +20,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for this module func (mc ModuleClient) GetQueryCmd() *cobra.Command { - stakeQueryCmd := &cobra.Command{ - Use: "stake", + stakingQueryCmd := &cobra.Command{ + Use: "staking", Short: "Querying commands for the staking module", } - stakeQueryCmd.AddCommand(client.GetCommands( + stakingQueryCmd.AddCommand(client.GetCommands( cli.GetCmdQueryDelegation(mc.storeKey, mc.cdc), cli.GetCmdQueryDelegations(mc.storeKey, mc.cdc), cli.GetCmdQueryUnbondingDelegation(mc.storeKey, mc.cdc), @@ -39,18 +39,18 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { cli.GetCmdQueryParams(mc.storeKey, mc.cdc), cli.GetCmdQueryPool(mc.storeKey, mc.cdc))...) - return stakeQueryCmd + return stakingQueryCmd } // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { - stakeTxCmd := &cobra.Command{ - Use: "stake", + stakingTxCmd := &cobra.Command{ + Use: "staking", Short: "Staking transaction subcommands", } - stakeTxCmd.AddCommand(client.PostCommands( + stakingTxCmd.AddCommand(client.PostCommands( cli.GetCmdCreateValidator(mc.cdc), cli.GetCmdEditValidator(mc.cdc), cli.GetCmdDelegate(mc.cdc), @@ -58,5 +58,5 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command { cli.GetCmdUnbond(mc.storeKey, mc.cdc), )...) - return stakeTxCmd + return stakingTxCmd } diff --git a/x/stake/client/rest/query.go b/x/staking/client/rest/query.go similarity index 64% rename from x/stake/client/rest/query.go rename to x/staking/client/rest/query.go index 085d7e4e2f02..c51f9e7ad0ac 100644 --- a/x/stake/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -4,109 +4,100 @@ import ( "net/http" "strings" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/tags" "github.com/gorilla/mux" ) -const storeName = "stake" - func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { // Get all delegations from a delegator r.HandleFunc( - "/stake/delegators/{delegatorAddr}/delegations", + "/staking/delegators/{delegatorAddr}/delegations", delegatorDelegationsHandlerFn(cliCtx, cdc), ).Methods("GET") // Get all unbonding delegations from a delegator r.HandleFunc( - "/stake/delegators/{delegatorAddr}/unbonding_delegations", + "/staking/delegators/{delegatorAddr}/unbonding_delegations", delegatorUnbondingDelegationsHandlerFn(cliCtx, cdc), ).Methods("GET") - // Get all redelegations from a delegator - r.HandleFunc( - "/stake/delegators/{delegatorAddr}/redelegations", - delegatorRedelegationsHandlerFn(cliCtx, cdc), - ).Methods("GET") - // Get all staking txs (i.e msgs) from a delegator r.HandleFunc( - "/stake/delegators/{delegatorAddr}/txs", + "/staking/delegators/{delegatorAddr}/txs", delegatorTxsHandlerFn(cliCtx, cdc), ).Methods("GET") // Query all validators that a delegator is bonded to r.HandleFunc( - "/stake/delegators/{delegatorAddr}/validators", + "/staking/delegators/{delegatorAddr}/validators", delegatorValidatorsHandlerFn(cliCtx, cdc), ).Methods("GET") // Query a validator that a delegator is bonded to r.HandleFunc( - "/stake/delegators/{delegatorAddr}/validators/{validatorAddr}", + "/staking/delegators/{delegatorAddr}/validators/{validatorAddr}", delegatorValidatorHandlerFn(cliCtx, cdc), ).Methods("GET") // Query a delegation between a delegator and a validator r.HandleFunc( - "/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}", + "/staking/delegators/{delegatorAddr}/delegations/{validatorAddr}", delegationHandlerFn(cliCtx, cdc), ).Methods("GET") // Query all unbonding delegations between a delegator and a validator r.HandleFunc( - "/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}", + "/staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}", unbondingDelegationHandlerFn(cliCtx, cdc), ).Methods("GET") + // Query redelegations (filters in query params) + r.HandleFunc( + "/staking/redelegations", + redelegationsHandlerFn(cliCtx, cdc), + ).Methods("GET") + // Get all validators r.HandleFunc( - "/stake/validators", + "/staking/validators", validatorsHandlerFn(cliCtx, cdc), ).Methods("GET") // Get a single validator info r.HandleFunc( - "/stake/validators/{validatorAddr}", + "/staking/validators/{validatorAddr}", validatorHandlerFn(cliCtx, cdc), ).Methods("GET") // Get all delegations to a validator r.HandleFunc( - "/stake/validators/{validatorAddr}/delegations", + "/staking/validators/{validatorAddr}/delegations", validatorDelegationsHandlerFn(cliCtx, cdc), ).Methods("GET") // Get all unbonding delegations from a validator r.HandleFunc( - "/stake/validators/{validatorAddr}/unbonding_delegations", + "/staking/validators/{validatorAddr}/unbonding_delegations", validatorUnbondingDelegationsHandlerFn(cliCtx, cdc), ).Methods("GET") - // Get all outgoing redelegations from a validator - r.HandleFunc( - "/stake/validators/{validatorAddr}/redelegations", - validatorRedelegationsHandlerFn(cliCtx, cdc), - ).Methods("GET") - // Get the current state of the staking pool r.HandleFunc( - "/stake/pool", + "/staking/pool", poolHandlerFn(cliCtx, cdc), ).Methods("GET") // Get the current staking parameter values r.HandleFunc( - "/stake/parameters", + "/staking/parameters", paramsHandlerFn(cliCtx, cdc), ).Methods("GET") @@ -114,17 +105,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co // HTTP request handler to query a delegator delegations func delegatorDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryDelegator(cliCtx, cdc, "custom/stake/delegatorDelegations") + return queryDelegator(cliCtx, cdc, "custom/staking/delegatorDelegations") } // HTTP request handler to query a delegator unbonding delegations func delegatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryDelegator(cliCtx, cdc, "custom/stake/delegatorUnbondingDelegations") -} - -// HTTP request handler to query a delegator redelegations -func delegatorRedelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryDelegator(cliCtx, cdc, "custom/stake/delegatorRedelegations") + return queryDelegator(cliCtx, cdc, "custom/staking/delegatorUnbondingDelegations") } // HTTP request handler to query all staking txs (msgs) from a delegator @@ -163,18 +149,18 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han switch { case isBondTx: - actions = append(actions, stake.MsgDelegate{}.Type()) + actions = append(actions, staking.MsgDelegate{}.Type()) case isUnbondTx: - actions = append(actions, stake.MsgBeginUnbonding{}.Type()) + actions = append(actions, staking.MsgUndelegate{}.Type()) actions = append(actions, string(tags.ActionCompleteUnbonding)) case isRedTx: - actions = append(actions, stake.MsgBeginRedelegate{}.Type()) + actions = append(actions, staking.MsgBeginRedelegate{}.Type()) actions = append(actions, string(tags.ActionCompleteRedelegation)) case noQuery: - actions = append(actions, stake.MsgDelegate{}.Type()) - actions = append(actions, stake.MsgBeginUnbonding{}.Type()) + actions = append(actions, staking.MsgDelegate{}.Type()) + actions = append(actions, staking.MsgUndelegate{}.Type()) actions = append(actions, string(tags.ActionCompleteUnbonding)) - actions = append(actions, stake.MsgBeginRedelegate{}.Type()) + actions = append(actions, staking.MsgBeginRedelegate{}.Type()) actions = append(actions, string(tags.ActionCompleteRedelegation)) default: w.WriteHeader(http.StatusNoContent) @@ -200,28 +186,79 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han // HTTP request handler to query an unbonding-delegation func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryBonds(cliCtx, cdc, "custom/stake/unbondingDelegation") + return queryBonds(cliCtx, cdc, "custom/staking/unbondingDelegation") +} + +// HTTP request handler to query redelegations +func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var params staking.QueryRedelegationParams + + bechDelegatorAddr := r.URL.Query().Get("delegator") + bechSrcValidatorAddr := r.URL.Query().Get("validator_from") + bechDstValidatorAddr := r.URL.Query().Get("validator_to") + + if len(bechDelegatorAddr) != 0 { + delegatorAddr, err := sdk.AccAddressFromBech32(bechDelegatorAddr) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.DelegatorAddr = delegatorAddr + } + + if len(bechSrcValidatorAddr) != 0 { + srcValidatorAddr, err := sdk.ValAddressFromBech32(bechSrcValidatorAddr) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.SrcValidatorAddr = srcValidatorAddr + } + + if len(bechDstValidatorAddr) != 0 { + dstValidatorAddr, err := sdk.ValAddressFromBech32(bechDstValidatorAddr) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.DstValidatorAddr = dstValidatorAddr + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData("custom/staking/redelegations", bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } } // HTTP request handler to query a delegation func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryBonds(cliCtx, cdc, "custom/stake/delegation") + return queryBonds(cliCtx, cdc, "custom/staking/delegation") } // HTTP request handler to query all delegator bonded validators func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryDelegator(cliCtx, cdc, "custom/stake/delegatorValidators") + return queryDelegator(cliCtx, cdc, "custom/staking/delegatorValidators") } // HTTP request handler to get information from a currently bonded validator func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryBonds(cliCtx, cdc, "custom/stake/delegatorValidator") + return queryBonds(cliCtx, cdc, "custom/staking/delegatorValidator") } // HTTP request handler to query list of validators func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - res, err := cliCtx.QueryWithData("custom/stake/validators", nil) + res, err := cliCtx.QueryWithData("custom/staking/validators", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -232,28 +269,23 @@ func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handl // HTTP request handler to query the validator information from a given validator address func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryValidator(cliCtx, cdc, "custom/stake/validator") + return queryValidator(cliCtx, cdc, "custom/staking/validator") } // HTTP request handler to query all unbonding delegations from a validator func validatorDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryValidator(cliCtx, cdc, "custom/stake/validatorDelegations") + return queryValidator(cliCtx, cdc, "custom/staking/validatorDelegations") } // HTTP request handler to query all unbonding delegations from a validator func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryValidator(cliCtx, cdc, "custom/stake/validatorUnbondingDelegations") -} - -// HTTP request handler to query all redelegations from a source validator -func validatorRedelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return queryValidator(cliCtx, cdc, "custom/stake/validatorRedelegations") + return queryValidator(cliCtx, cdc, "custom/staking/validatorUnbondingDelegations") } // HTTP request handler to query the pool information func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - res, err := cliCtx.QueryWithData("custom/stake/pool", nil) + res, err := cliCtx.QueryWithData("custom/staking/pool", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -265,7 +297,7 @@ func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc // HTTP request handler to query the staking params values func paramsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - res, err := cliCtx.QueryWithData("custom/stake/parameters", nil) + res, err := cliCtx.QueryWithData("custom/staking/parameters", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/stake/client/rest/rest.go b/x/staking/client/rest/rest.go similarity index 100% rename from x/stake/client/rest/rest.go rename to x/staking/client/rest/rest.go diff --git a/x/stake/client/rest/tx.go b/x/staking/client/rest/tx.go similarity index 71% rename from x/stake/client/rest/tx.go rename to x/staking/client/rest/tx.go index bb2bc0e7fa78..73d86ce423fa 100644 --- a/x/stake/client/rest/tx.go +++ b/x/staking/client/rest/tx.go @@ -9,22 +9,22 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/staking" "github.com/gorilla/mux" ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc( - "/stake/delegators/{delegatorAddr}/delegations", + "/staking/delegators/{delegatorAddr}/delegations", postDelegationsHandlerFn(cdc, kb, cliCtx), ).Methods("POST") r.HandleFunc( - "/stake/delegators/{delegatorAddr}/unbonding_delegations", + "/staking/delegators/{delegatorAddr}/unbonding_delegations", postUnbondingDelegationsHandlerFn(cdc, kb, cliCtx), ).Methods("POST") r.HandleFunc( - "/stake/delegators/{delegatorAddr}/redelegations", + "/staking/delegators/{delegatorAddr}/redelegations", postRedelegationsHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } @@ -45,7 +45,7 @@ type ( SharesAmount sdk.Dec `json:"shares"` } - msgBeginUnbondingInput struct { + msgUndelegateInput struct { BaseReq utils.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 @@ -63,15 +63,12 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -82,14 +79,14 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - msg := stake.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) + msg := staking.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -103,15 +100,12 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -122,20 +116,20 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return } - msg := stake.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) + msg := staking.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req msgBeginUnbondingInput + var req msgUndelegateInput err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { @@ -143,15 +137,12 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return } - cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) - cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w, cliCtx) { + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { return } - info, err := kb.Get(baseReq.Name) + info, err := kb.Get(req.BaseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return @@ -162,13 +153,13 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return } - msg := stake.NewMsgBeginUnbonding(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) + msg := staking.NewMsgUndelegate(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/stake/client/rest/utils.go b/x/staking/client/rest/utils.go similarity index 68% rename from x/stake/client/rest/utils.go rename to x/staking/client/rest/utils.go index d9ae457cfe79..2a95776e3dc0 100644 --- a/x/stake/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -11,8 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/tags" rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -50,6 +50,50 @@ func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec return tx.FormatTxResults(cdc, res.Txs) } +func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bech32delegator := vars["delegatorAddr"] + bech32srcValidator := vars["srcValidatorAddr"] + bech32dstValidator := vars["dstValidatorAddr"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + srcValidatorAddr, err := sdk.ValAddressFromBech32(bech32srcValidator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + dstValidatorAddr, err := sdk.ValAddressFromBech32(bech32dstValidator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := staking.QueryRedelegationParams{ + DelegatorAddr: delegatorAddr, + SrcValidatorAddr: srcValidatorAddr, + DstValidatorAddr: dstValidatorAddr, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData(endpoint, bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -63,7 +107,7 @@ func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) ht return } - params := stake.NewQueryBondsParams(delegatorAddr, validatorAddr) + params := staking.NewQueryBondsParams(delegatorAddr, validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -91,7 +135,7 @@ func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.NewQueryDelegatorParams(delegatorAddr) + params := staking.NewQueryDelegatorParams(delegatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -119,7 +163,7 @@ func queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.NewQueryValidatorParams(validatorAddr) + params := staking.NewQueryValidatorParams(validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { diff --git a/x/stake/genesis.go b/x/staking/genesis.go similarity index 85% rename from x/stake/genesis.go rename to x/staking/genesis.go index d4039003e9e4..8fa52c07ac5c 100644 --- a/x/stake/genesis.go +++ b/x/staking/genesis.go @@ -1,14 +1,13 @@ -package stake +package staking import ( "fmt" - "sort" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // InitGenesis sets the pool and parameters for the provided keeper. For each @@ -18,9 +17,11 @@ import ( // Returns final validator set after applying all declaration and delegations func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res []abci.ValidatorUpdate, err error) { - // We need to pretend to be "n blocks before genesis", where "n" is the validator update delay, - // so that e.g. slashing periods are correctly initialized for the validator set - // e.g. with a one-block offset - the first TM block is at height 1, so state updates applied from genesis.json are in block 0. + // We need to pretend to be "n blocks before genesis", where "n" is the + // validator update delay, so that e.g. slashing periods are correctly + // initialized for the validator set e.g. with a one-block offset - the + // first TM block is at height 1, so state updates applied from + // genesis.json are in block 0. ctx = ctx.WithBlockHeight(1 - types.ValidatorUpdateDelay) keeper.SetPool(ctx, data.Pool) @@ -33,7 +34,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ // Manually set indices for the first time keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator) - keeper.OnValidatorCreated(ctx, validator.OperatorAddr) + if !data.Exported { + keeper.AfterValidatorCreated(ctx, validator.OperatorAddr) + } // Set timeslice if necessary if validator.Status == sdk.Unbonding { @@ -42,24 +45,27 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ } for _, delegation := range data.Bonds { + if !data.Exported { + keeper.BeforeDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + } keeper.SetDelegation(ctx, delegation) - keeper.OnDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + if !data.Exported { + keeper.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + } } - sort.SliceStable(data.UnbondingDelegations[:], func(i, j int) bool { - return data.UnbondingDelegations[i].CreationHeight < data.UnbondingDelegations[j].CreationHeight - }) for _, ubd := range data.UnbondingDelegations { keeper.SetUnbondingDelegation(ctx, ubd) - keeper.InsertUnbondingQueue(ctx, ubd) + for _, entry := range ubd.Entries { + keeper.InsertUBDQueue(ctx, ubd, entry.CompletionTime) + } } - sort.SliceStable(data.Redelegations[:], func(i, j int) bool { - return data.Redelegations[i].CreationHeight < data.Redelegations[j].CreationHeight - }) for _, red := range data.Redelegations { keeper.SetRedelegation(ctx, red) - keeper.InsertRedelegationQueue(ctx, red) + for _, entry := range red.Entries { + keeper.InsertRedelegationQueue(ctx, red, entry.CompletionTime) + } } // don't need to run Tendermint updates if we exported @@ -124,7 +130,7 @@ func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisVali keeper.IterateLastValidators(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetConsPubKey(), - Power: validator.GetPower().RoundInt64(), + Power: validator.GetPower().Int64(), Name: validator.GetMoniker(), }) diff --git a/x/stake/genesis_test.go b/x/staking/genesis_test.go similarity index 91% rename from x/stake/genesis_test.go rename to x/staking/genesis_test.go index f747ca7a7abb..ac9174651729 100644 --- a/x/stake/genesis_test.go +++ b/x/staking/genesis_test.go @@ -1,4 +1,4 @@ -package stake +package staking import ( "fmt" @@ -12,15 +12,15 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/types" + keep "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) func TestInitGenesis(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) pool := keeper.GetPool(ctx) - pool.BondedTokens = sdk.NewDec(2) + pool.BondedTokens = sdk.NewInt(2) params := keeper.GetParams(ctx) validators := make([]Validator, 2) @@ -31,13 +31,13 @@ func TestInitGenesis(t *testing.T) { validators[0].ConsPubKey = keep.PKs[0] validators[0].Description = Description{Moniker: "hoop"} validators[0].Status = sdk.Bonded - validators[0].Tokens = sdk.OneDec() + validators[0].Tokens = sdk.OneInt() validators[0].DelegatorShares = sdk.OneDec() validators[1].OperatorAddr = sdk.ValAddress(keep.Addrs[1]) validators[1].ConsPubKey = keep.PKs[1] validators[1].Description = Description{Moniker: "bloop"} validators[1].Status = sdk.Bonded - validators[1].Tokens = sdk.OneDec() + validators[1].Tokens = sdk.OneInt() validators[1].DelegatorShares = sdk.OneDec() genesisState := types.NewGenesisState(pool, params, validators, delegations) @@ -75,7 +75,7 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { // Assigning 2 to the first 100 vals, 1 to the rest pool := keeper.GetPool(ctx) - pool.BondedTokens = sdk.NewDec(int64(200 + (size - 100))) + pool.BondedTokens = sdk.NewInt(int64(200 + (size - 100))) params := keeper.GetParams(ctx) delegations := []Delegation{} @@ -86,10 +86,10 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { validators[i].Status = sdk.Bonded if i < 100 { - validators[i].Tokens = sdk.NewDec(2) + validators[i].Tokens = sdk.NewInt(2) validators[i].DelegatorShares = sdk.NewDec(2) } else { - validators[i].Tokens = sdk.OneDec() + validators[i].Tokens = sdk.OneInt() validators[i].DelegatorShares = sdk.OneDec() } } @@ -110,7 +110,7 @@ func TestValidateGenesis(t *testing.T) { genValidators1 := make([]types.Validator, 1, 5) pk := ed25519.GenPrivKey().PubKey() genValidators1[0] = types.NewValidator(sdk.ValAddress(pk.Address()), pk, types.NewDescription("", "", "", "")) - genValidators1[0].Tokens = sdk.OneDec() + genValidators1[0].Tokens = sdk.OneInt() genValidators1[0].DelegatorShares = sdk.OneDec() tests := []struct { diff --git a/x/stake/handler.go b/x/staking/handler.go similarity index 76% rename from x/stake/handler.go rename to x/staking/handler.go index f8792da786b5..1b8813a191f1 100644 --- a/x/stake/handler.go +++ b/x/staking/handler.go @@ -1,16 +1,17 @@ -package stake +package staking import ( "bytes" + "time" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/common" tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/tags" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/tags" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) func NewHandler(k keeper.Keeper) sdk.Handler { @@ -25,8 +26,8 @@ func NewHandler(k keeper.Keeper) sdk.Handler { return handleMsgDelegate(ctx, msg, k) case types.MsgBeginRedelegate: return handleMsgBeginRedelegate(ctx, msg, k) - case types.MsgBeginUnbonding: - return handleMsgBeginUnbonding(ctx, msg, k) + case types.MsgUndelegate: + return handleMsgUndelegate(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -34,7 +35,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler { } // Called every block, update validator set -func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.ValidatorUpdate, endBlockerTags sdk.Tags) { +func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, sdk.Tags) { + resTags := sdk.NewTags() + // Calculate validator set changes. // // NOTE: ApplyAndReturnValidatorSetUpdates has to come before @@ -44,19 +47,20 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.Valid // unbonded after the Endblocker (go from Bonded -> Unbonding during // ApplyAndReturnValidatorSetUpdates and then Unbonding -> Unbonded during // UnbondAllMatureValidatorQueue). - validatorUpdates = k.ApplyAndReturnValidatorSetUpdates(ctx) + validatorUpdates := k.ApplyAndReturnValidatorSetUpdates(ctx) // Unbond all mature validators from the unbonding queue. k.UnbondAllMatureValidatorQueue(ctx) // Remove all mature unbonding delegations from the ubd queue. - matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time) + matureUnbonds := k.DequeueAllMatureUBDQueue(ctx, ctx.BlockHeader().Time) for _, dvPair := range matureUnbonds { err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) if err != nil { continue } - endBlockerTags.AppendTags(sdk.NewTags( + + resTags.AppendTags(sdk.NewTags( tags.Action, ActionCompleteUnbonding, tags.Delegator, []byte(dvPair.DelegatorAddr.String()), tags.SrcValidator, []byte(dvPair.ValidatorAddr.String()), @@ -66,18 +70,21 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.Valid // 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.DelegatorAddr, + dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr) if err != nil { continue } - endBlockerTags.AppendTags(sdk.NewTags( + + resTags.AppendTags(sdk.NewTags( tags.Action, tags.ActionCompleteRedelegation, tags.Delegator, []byte(dvvTriplet.DelegatorAddr.String()), tags.SrcValidator, []byte(dvvTriplet.ValidatorSrcAddr.String()), tags.DstValidator, []byte(dvvTriplet.ValidatorDstAddr.String()), )) } - return + + return validatorUpdates, resTags } //_____________________________________________________________________ @@ -97,14 +104,20 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k return ErrValidatorPubKeyExists(k.Codespace()).Result() } - if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { + if msg.Value.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } + if _, err := msg.Description.EnsureLength(); err != nil { + return err.Result() + } + if ctx.ConsensusParams() != nil { tmPubKey := tmtypes.TM2PB.PubKey(msg.PubKey) if !common.StringInSlice(tmPubKey.Type, ctx.ConsensusParams().Validator.PubKeyTypes) { - return ErrValidatorPubKeyTypeUnsupported(k.Codespace(), tmPubKey.Type, ctx.ConsensusParams().Validator.PubKeyTypes).Result() + return ErrValidatorPubKeyTypeUnsupported(k.Codespace(), + tmPubKey.Type, + ctx.ConsensusParams().Validator.PubKeyTypes).Result() } } @@ -122,11 +135,11 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k k.SetValidatorByConsAddr(ctx, validator) k.SetNewValidatorByPowerIndex(ctx, validator) - k.OnValidatorCreated(ctx, validator.OperatorAddr) + k.AfterValidatorCreated(ctx, validator.OperatorAddr) // 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.Delegation, validator, true) + _, err = k.Delegate(ctx, msg.DelegatorAddr, msg.Value, validator, true) if err != nil { return err.Result() } @@ -162,8 +175,9 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe if err != nil { return err.Result() } + + k.BeforeValidatorModified(ctx, msg.ValidatorAddr) validator.Commission = commission - k.OnValidatorModified(ctx, msg.ValidatorAddr) } k.SetValidator(ctx, validator) @@ -185,7 +199,7 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return ErrNoValidatorFound(k.Codespace()).Result() } - if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { + if msg.Value.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } @@ -193,7 +207,7 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return ErrValidatorJailed(k.Codespace()).Result() } - _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true) + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Value, validator, true) if err != nil { return err.Result() } @@ -208,36 +222,36 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) } } -func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { - ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) +func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keeper) sdk.Result { + completionTime, err := k.Undelegate(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) if err != nil { return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(ubd.MinTime) - + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime) tags := sdk.NewTags( tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.SrcValidator, []byte(msg.ValidatorAddr.String()), - tags.EndTime, finishTime, + tags.EndTime, []byte(completionTime.Format(time.RFC3339)), ) + return sdk.Result{Data: finishTime, Tags: tags} } func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { - red, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, + completionTime, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr, msg.SharesAmount) if err != nil { return err.Result() } - finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(red.MinTime) - - tags := sdk.NewTags( + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime) + resTags := sdk.NewTags( tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), - tags.EndTime, finishTime, + tags.EndTime, []byte(completionTime.Format(time.RFC3339)), ) - return sdk.Result{Data: finishTime, Tags: tags} + + return sdk.Result{Data: finishTime, Tags: resTags} } diff --git a/x/stake/handler_test.go b/x/staking/handler_test.go similarity index 77% rename from x/stake/handler_test.go rename to x/staking/handler_test.go index 04e1e372f221..3caecca44078 100644 --- a/x/stake/handler_test.go +++ b/x/staking/handler_test.go @@ -1,4 +1,4 @@ -package stake +package staking import ( "testing" @@ -12,8 +12,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" - keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/types" + keep "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) //______________________________________________________________________ @@ -74,8 +74,8 @@ func TestValidatorByPowerIndex(t *testing.T) { keeper.ApplyAndReturnValidatorSetUpdates(ctx) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding - require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure tokens slashed + require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding + require.Equal(t, int64(500000), validator.Tokens.Int64()) // ensure tokens slashed keeper.Unjail(ctx, consAddr0) // the old power record should have been deleted as the power changed @@ -92,8 +92,8 @@ func TestValidatorByPowerIndex(t *testing.T) { require.Equal(t, power2, power3) // unbond self-delegation - msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(1000000)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(1000000)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) @@ -125,7 +125,7 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, sdk.Bonded, validator.Status) assert.Equal(t, addr1, validator.OperatorAddr) assert.Equal(t, pk1, validator.ConsPubKey) - assert.Equal(t, sdk.NewDec(10), validator.BondedTokens()) + assert.Equal(t, int64(10), validator.BondedTokens().Int64()) assert.Equal(t, sdk.NewDec(10), validator.DelegatorShares) assert.Equal(t, Description{}, validator.Description) @@ -154,7 +154,7 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, sdk.Bonded, validator.Status) assert.Equal(t, addr2, validator.OperatorAddr) assert.Equal(t, pk2, validator.ConsPubKey) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) assert.Equal(t, Description{}, validator.Description) } @@ -198,7 +198,7 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { assert.Equal(t, sdk.Bonded, validator.Status) assert.Equal(t, validatorAddr, validator.OperatorAddr) assert.Equal(t, pk, validator.ConsPubKey) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) assert.Equal(t, Description{}, validator.Description) @@ -232,7 +232,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status) require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) - require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64()) + require.Equal(t, bondAmount, validator.BondedTokens().Int64()) // delegate tokens to the validator msgDelegate := NewTestMsgDelegate(delAddr, valAddr, bondAmount) @@ -243,13 +243,13 @@ func TestLegacyValidatorDelegations(t *testing.T) { validator, found = keeper.GetValidator(ctx, valAddr) require.True(t, found) require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64()) - require.Equal(t, bondAmount*2, validator.BondedTokens().RoundInt64()) + require.Equal(t, bondAmount*2, validator.BondedTokens().Int64()) // unbond validator total self-delegations (which should jail the validator) unbondShares := sdk.NewDec(10) - msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) var finishTime time.Time @@ -261,7 +261,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { validator, found = keeper.GetValidator(ctx, valAddr) require.True(t, found) require.True(t, validator.Jailed) - require.Equal(t, sdk.NewDec(10), validator.Tokens) + require.Equal(t, int64(10), validator.Tokens.Int64()) // verify delegation still exists bond, found := keeper.GetDelegation(ctx, delAddr, valAddr) @@ -283,7 +283,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { validator, found = keeper.GetValidator(ctx, valAddr) require.True(t, found) require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64()) - require.Equal(t, bondAmount*2, validator.Tokens.RoundInt64()) + require.Equal(t, bondAmount*2, validator.Tokens.Int64()) // unjail the validator now that is has non-zero self-delegated shares keeper.Unjail(ctx, valConsAddr) @@ -297,7 +297,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { validator, found = keeper.GetValidator(ctx, valAddr) require.True(t, found) require.Equal(t, bondAmount*3, validator.DelegatorShares.RoundInt64()) - require.Equal(t, bondAmount*3, validator.Tokens.RoundInt64()) + require.Equal(t, bondAmount*3, validator.Tokens.Int64()) // verify new delegation bond, found = keeper.GetDelegation(ctx, delAddr, valAddr) @@ -326,7 +326,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status) require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) - require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64(), "validator: %v", validator) + require.Equal(t, bondAmount, validator.BondedTokens().Int64(), "validator: %v", validator) _, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) @@ -338,7 +338,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { 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.RoundInt64()) + require.Equal(t, bondAmount, pool.BondedTokens.Int64()) // just send the same msgbond multiple times msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) @@ -408,22 +408,23 @@ func TestIncrementsMsgUnbond(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, initBond*2, validator.DelegatorShares.RoundInt64()) - require.Equal(t, initBond*2, validator.BondedTokens().RoundInt64()) + require.Equal(t, initBond*2, validator.BondedTokens().Int64()) // just send the same msgUnbond multiple times // TODO use decimals here unbondShares := sdk.NewDec(10) - msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { - got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + + got := handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) - //Check that the accounts and the bond account have the appropriate values + // check that the accounts and the bond account have the appropriate values validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) @@ -458,8 +459,8 @@ func TestIncrementsMsgUnbond(t *testing.T) { } for _, c := range errorCases { unbondShares := sdk.NewDec(c) - msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail") } @@ -467,17 +468,17 @@ func TestIncrementsMsgUnbond(t *testing.T) { // should be unable to unbond one more than we have unbondShares = sdk.NewDec(leftBonded + 1) - msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + 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, msgBeginUnbonding, unbondShares.String(), leftBonded) + "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.NewDec(leftBonded) - msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUndelegate, unbondShares, leftBonded) } func TestMultipleMsgCreateValidator(t *testing.T) { @@ -509,8 +510,8 @@ func TestMultipleMsgCreateValidator(t *testing.T) { for i, validatorAddr := range validatorAddrs { _, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddrs[i], validatorAddr, sdk.NewDec(10)) // remove delegation - got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate := NewMsgUndelegate(delegatorAddrs[i], validatorAddr, sdk.NewDec(10)) // 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 // Jump to finishTime for unbonding period and remove from unbonding queue @@ -556,8 +557,8 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) - got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + got := handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) @@ -586,8 +587,8 @@ func TestJailValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) // unbond the validators bond portion - msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) @@ -603,8 +604,8 @@ func TestJailValidator(t *testing.T) { require.False(t, got.IsOK(), "expected error, got %v", got) // test that the delegator can still withdraw their bonds - msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegateDelegator, keeper) require.True(t, got.IsOK(), "expected no error") types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) @@ -637,8 +638,8 @@ func TestValidatorQueue(t *testing.T) { EndBlocker(ctx, keeper) // unbond the all self-delegation to put validator in unbonding state - msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) @@ -682,8 +683,8 @@ func TestUnbondingPeriod(t *testing.T) { EndBlocker(ctx, keeper) // begin unbonding - msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error") origHeader := ctx.BlockHeader() @@ -723,8 +724,8 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) // unbond the validators bond portion - msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error") // change the ctx to Block Time one second before the validator would have unbonded @@ -733,8 +734,8 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) // unbond the delegator from the validator - msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + got = handleMsgUndelegate(ctx, msgUndelegateDelegator, keeper) require.True(t, got.IsOK(), "expected no error") // move the Block time forward by one second @@ -849,46 +850,205 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, got.IsOK(), "expected no error") } -func TestConflictingRedelegation(t *testing.T) { +func TestMultipleRedelegationAtSameTime(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr := sdk.ValAddress(keep.Addrs[0]) - validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + valAddr := sdk.ValAddress(keep.Addrs[0]) + valAddr2 := sdk.ValAddress(keep.Addrs[1]) // set the unbonding time params := keeper.GetParams(ctx) - params.UnbondingTime = 1 + params.UnbondingTime = 1 * time.Second keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(valAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // end block to bond them EndBlocker(ctx, keeper) - // begin redelegate - msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(5)) + // begin a redelegate + selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) + msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, + valAddr, valAddr2, sdk.NewDec(5)) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) - // cannot redelegate again while first redelegation still exists + // there should only be one entry in the redelegation object + rd, found := keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.True(t, found) + require.Len(t, rd.Entries, 1) + + // start a second redelegation at this same time as the first got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) - require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginRedelegate) - // progress forward in time - ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second)) + // now there should be two entries + rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.True(t, found) + require.Len(t, rd.Entries, 2) - // complete first redelegation + // move forward in time, should complete both redelegations + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(1 * time.Second)) EndBlocker(ctx, keeper) - // now should be able to redelegate again + rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.False(t, found) +} + +func TestMultipleRedelegationAtUniqueTimes(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + valAddr := sdk.ValAddress(keep.Addrs[0]) + valAddr2 := sdk.ValAddress(keep.Addrs[1]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 10 * time.Second + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(valAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond them + EndBlocker(ctx, keeper) + + // begin a redelegate + selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) + msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, + valAddr, valAddr2, sdk.NewDec(5)) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) - require.True(t, got.IsOK(), "expected no error") + require.True(t, got.IsOK(), "expected no error, %v", got) + + // move forward in time and start a second redelegation + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginRedelegate) + + // now there should be two entries + rd, found := keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.True(t, found) + require.Len(t, rd.Entries, 2) + + // move forward in time, should complete the first redelegation, but not the second + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + EndBlocker(ctx, keeper) + rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.True(t, found) + require.Len(t, rd.Entries, 1) + + // move forward in time, should complete the second redelegation + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + EndBlocker(ctx, keeper) + rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2) + require.False(t, found) +} + +func TestMultipleUnbondingDelegationAtSameTime(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + valAddr := sdk.ValAddress(keep.Addrs[0]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 1 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond + EndBlocker(ctx, keeper) + + // begin an unbonding delegation + selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, sdk.NewDec(5)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // there should only be one entry in the ubd object + ubd, found := keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // start a second ubd at this same time as the first + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error, msg: %v", msgUndelegate) + + // now there should be two entries + ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.True(t, found) + require.Len(t, ubd.Entries, 2) + + // move forwaubd in time, should complete both ubds + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(1 * time.Second)) + EndBlocker(ctx, keeper) + + ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.False(t, found) +} + +func TestMultipleUnbondingDelegationAtUniqueTimes(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + valAddr := sdk.ValAddress(keep.Addrs[0]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 10 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond + EndBlocker(ctx, keeper) + + // begin an unbonding delegation + selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, sdk.NewDec(5)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // there should only be one entry in the ubd object + ubd, found := keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // move forwaubd in time and start a second redelegation + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error, msg: %v", msgUndelegate) + + // now there should be two entries + ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.True(t, found) + require.Len(t, ubd.Entries, 2) + + // move forwaubd in time, should complete the first redelegation, but not the second + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + EndBlocker(ctx, keeper) + ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // move forwaubd in time, should complete the second redelegation + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second)) + EndBlocker(ctx, keeper) + ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr) + require.False(t, found) } func TestUnbondingWhenExcessValidators(t *testing.T) { @@ -926,9 +1086,9 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) // unbond the valdator-2 - msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDec(30)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDec(30)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -969,9 +1129,9 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ctx = ctx.WithBlockHeight(1) // begin unbonding 4 stake - msgBeginUnbonding := NewMsgBeginUnbonding(del, valA, sdk.NewDec(4)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + msgUndelegate := NewMsgUndelegate(del, valA, sdk.NewDec(4)) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") // begin redelegate 6 stake msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, sdk.NewDec(6)) @@ -991,14 +1151,16 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should have been slashed by half - unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA) + ubd, found := keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) - require.Equal(t, int64(2), unbonding.Balance.Amount.Int64()) + require.Len(t, ubd.Entries, 1) + require.Equal(t, int64(2), ubd.Entries[0].Balance.Amount.Int64()) // redelegation should have been slashed by half redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB) require.True(t, found) - require.Equal(t, int64(3), redelegation.Balance.Amount.Int64()) + require.Len(t, redelegation.Entries, 1) + require.Equal(t, int64(3), redelegation.Entries[0].Balance.Amount.Int64()) // destination delegation should have been slashed by half delegation, found = keeper.GetDelegation(ctx, del, valB) @@ -1008,21 +1170,23 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // validator power should have been reduced by half validator, found := keeper.GetValidator(ctx, valA) require.True(t, found) - require.Equal(t, sdk.NewDec(5), validator.GetPower()) + require.Equal(t, int64(5), validator.GetPower().Int64()) // slash the validator for an infraction committed after the unbonding and redelegation begin ctx = ctx.WithBlockHeight(3) keeper.Slash(ctx, consAddr0, 2, 10, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should be unchanged - unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA) + ubd, found = keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) - require.Equal(t, int64(2), unbonding.Balance.Amount.Int64()) + require.Len(t, ubd.Entries, 1) + require.Equal(t, int64(2), ubd.Entries[0].Balance.Amount.Int64()) // redelegation should be unchanged redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB) require.True(t, found) - require.Equal(t, int64(3), redelegation.Balance.Amount.Int64()) + require.Len(t, redelegation.Entries, 1) + require.Equal(t, int64(3), redelegation.Entries[0].Balance.Amount.Int64()) // destination delegation should be unchanged delegation, found = keeper.GetDelegation(ctx, del, valB) diff --git a/x/stake/keeper/_store.md b/x/staking/keeper/_store.md similarity index 99% rename from x/stake/keeper/_store.md rename to x/staking/keeper/_store.md index d569389417c3..f6430c312b90 100644 --- a/x/stake/keeper/_store.md +++ b/x/staking/keeper/_store.md @@ -1,7 +1,7 @@ # Stores This document provided a bit more insight as to the purpose of several related -prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. +prefixed areas of the staking store which are accessed in `x/staking/keeper.go`. # IAVL Store diff --git a/x/stake/keeper/delegation.go b/x/staking/keeper/delegation.go similarity index 67% rename from x/stake/keeper/delegation.go rename to x/staking/keeper/delegation.go index 2567f3dfbbc7..5a15b4d03597 100644 --- a/x/stake/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -5,7 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // return a specific delegation @@ -20,7 +20,7 @@ func (k Keeper) GetDelegation(ctx sdk.Context, return delegation, false } - delegation = types.MustUnmarshalDelegation(k.cdc, key, value) + delegation = types.MustUnmarshalDelegation(k.cdc, value) return delegation, true } @@ -31,7 +31,7 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) delegations = append(delegations, delegation) } return delegations @@ -44,7 +44,7 @@ func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) if delegation.GetValidatorAddr().Equals(valAddr) { delegations = append(delegations, delegation) } @@ -65,7 +65,7 @@ func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddres i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) delegations[i] = delegation i++ } @@ -81,7 +81,7 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { // remove a delegation from store func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { - k.OnDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + k.BeforeDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) store := ctx.KVStore(k.storeKey) store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } @@ -101,7 +101,7 @@ func (k Keeper) GetUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddres i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { - unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Value()) unbondingDelegations[i] = unbondingDelegation i++ } @@ -119,7 +119,7 @@ func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, return ubd, false } - ubd = types.MustUnmarshalUBD(k.cdc, key, value) + ubd = types.MustUnmarshalUBD(k.cdc, value) return ubd, true } @@ -132,7 +132,7 @@ func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sd for ; iterator.Valid(); iterator.Next() { key := GetUBDKeyFromValIndexKey(iterator.Key()) value := store.Get(key) - ubd := types.MustUnmarshalUBD(k.cdc, key, value) + ubd := types.MustUnmarshalUBD(k.cdc, value) ubds = append(ubds, ubd) } return ubds @@ -145,7 +145,7 @@ func (k Keeper) IterateUnbondingDelegations(ctx sdk.Context, fn func(index int64 defer iterator.Close() for i := int64(0); iterator.Valid(); iterator.Next() { - ubd := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + ubd := types.MustUnmarshalUBD(k.cdc, iterator.Value()) if stop := fn(i, ubd); stop { break } @@ -170,9 +170,28 @@ func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDe store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) } -// gets a specific unbonding queue timeslice. A timeslice is a slice of DVPairs corresponding to unbonding delegations -// that expire at a certain time. -func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) { +// SetUnbondingDelegationEntry adds an entry to the unbonding delegation at +// the given addresses. It creates the unbonding delegation if it does not exist +func (k Keeper) SetUnbondingDelegationEntry(ctx sdk.Context, + delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress, + creationHeight int64, minTime time.Time, balance sdk.Coin) types.UnbondingDelegation { + + ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + if found { + ubd.AddEntry(creationHeight, minTime, balance) + } else { + ubd = types.NewUnbondingDelegation(delegatorAddr, validatorAddr, creationHeight, minTime, balance) + } + k.SetUnbondingDelegation(ctx, ubd) + return ubd +} + +//________________________________________________ +// unbonding delegation queue timeslice operations + +// gets a specific unbonding queue timeslice. A timeslice is a slice of DVPairs +// corresponding to unbonding delegations that expire at a certain time. +func (k Keeper) GetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetUnbondingDelegationTimeKey(timestamp)) if bz == nil { @@ -183,38 +202,45 @@ func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) } // Sets a specific unbonding queue timeslice. -func (k Keeper) SetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { +func (k Keeper) SetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys) store.Set(GetUnbondingDelegationTimeKey(timestamp), bz) } // Insert an unbonding delegation to the appropriate timeslice in the unbonding queue -func (k Keeper) InsertUnbondingQueue(ctx sdk.Context, ubd types.UnbondingDelegation) { - timeSlice := k.GetUnbondingQueueTimeSlice(ctx, ubd.MinTime) +func (k Keeper) InsertUBDQueue(ctx sdk.Context, ubd types.UnbondingDelegation, + completionTime time.Time) { + + timeSlice := k.GetUBDQueueTimeSlice(ctx, completionTime) dvPair := types.DVPair{ubd.DelegatorAddr, ubd.ValidatorAddr} if len(timeSlice) == 0 { - k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, []types.DVPair{dvPair}) + k.SetUBDQueueTimeSlice(ctx, completionTime, []types.DVPair{dvPair}) } else { timeSlice = append(timeSlice, dvPair) - k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, timeSlice) + k.SetUBDQueueTimeSlice(ctx, completionTime, timeSlice) } } // Returns all the unbonding queue timeslices from time 0 until endTime -func (k Keeper) UnbondingQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { +func (k Keeper) UBDQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(k.storeKey) - return store.Iterator(UnbondingQueueKey, sdk.InclusiveEndBytes(GetUnbondingDelegationTimeKey(endTime))) + return store.Iterator(UnbondingQueueKey, + sdk.InclusiveEndBytes(GetUnbondingDelegationTimeKey(endTime))) } -// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue -func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []types.DVPair) { +// Returns a concatenated list of all the timeslices inclusively previous to +// currTime, and deletes the timeslices from the queue +func (k Keeper) DequeueAllMatureUBDQueue(ctx sdk.Context, + currTime time.Time) (matureUnbonds []types.DVPair) { + store := ctx.KVStore(k.storeKey) // gets an iterator for all timeslices from time 0 until the current Blockheader time - unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time) + unbondingTimesliceIterator := k.UBDQueueIterator(ctx, ctx.BlockHeader().Time) for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { timeslice := []types.DVPair{} - k.cdc.MustUnmarshalBinaryLengthPrefixed(unbondingTimesliceIterator.Value(), ×lice) + value := unbondingTimesliceIterator.Value() + k.cdc.MustUnmarshalBinaryLengthPrefixed(value, ×lice) matureUnbonds = append(matureUnbonds, timeslice...) store.Delete(unbondingTimesliceIterator.Key()) } @@ -235,7 +261,7 @@ func (k Keeper) GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress, i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { - redelegation := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + redelegation := types.MustUnmarshalRED(k.cdc, iterator.Value()) redelegations[i] = redelegation i++ } @@ -253,7 +279,7 @@ func (k Keeper) GetRedelegation(ctx sdk.Context, return red, false } - red = types.MustUnmarshalRED(k.cdc, key, value) + red = types.MustUnmarshalRED(k.cdc, value) return red, true } @@ -266,7 +292,7 @@ func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAd for ; iterator.Valid(); iterator.Next() { key := GetREDKeyFromValSrcIndexKey(iterator.Key()) value := store.Get(key) - red := types.MustUnmarshalRED(k.cdc, key, value) + red := types.MustUnmarshalRED(k.cdc, value) reds = append(reds, red) } return reds @@ -281,11 +307,10 @@ func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, iterator := sdk.KVStorePrefixIterator(store, prefix) defer iterator.Close() - found := false if iterator.Valid() { - found = true + return true } - return found + return false } // set a redelegation and associated index @@ -298,6 +323,26 @@ func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) } +// SetUnbondingDelegationEntry adds an entry to the unbonding delegation at +// the given addresses. It creates the unbonding delegation if it does not exist +func (k Keeper) SetRedelegationEntry(ctx sdk.Context, + delegatorAddr sdk.AccAddress, validatorSrcAddr, + validatorDstAddr sdk.ValAddress, creationHeight int64, + minTime time.Time, balance sdk.Coin, + sharesSrc, sharesDst sdk.Dec) types.Redelegation { + + red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) + if found { + red.AddEntry(creationHeight, minTime, balance, sharesSrc, sharesDst) + } else { + red = types.NewRedelegation(delegatorAddr, validatorSrcAddr, + validatorDstAddr, creationHeight, minTime, balance, sharesSrc, + sharesDst) + } + k.SetRedelegation(ctx, red) + return red +} + // iterate through all redelegations func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red types.Redelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) @@ -305,7 +350,7 @@ func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red t defer iterator.Close() for i := int64(0); iterator.Valid(); iterator.Next() { - red := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + red := types.MustUnmarshalRED(k.cdc, iterator.Value()) if stop := fn(i, red); stop { break } @@ -322,6 +367,9 @@ func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) } +//________________________________________________ +// redelegation queue timeslice operations + // Gets a specific redelegation queue timeslice. A timeslice is a slice of DVVTriplets corresponding to redelegations // that expire at a certain time. func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvvTriplets []types.DVVTriplet) { @@ -342,14 +390,16 @@ func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Ti } // Insert an redelegation delegation to the appropriate timeslice in the redelegation queue -func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation) { - timeSlice := k.GetRedelegationQueueTimeSlice(ctx, red.MinTime) +func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation, + completionTime time.Time) { + + timeSlice := k.GetRedelegationQueueTimeSlice(ctx, completionTime) dvvTriplet := types.DVVTriplet{red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr} if len(timeSlice) == 0 { - k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, []types.DVVTriplet{dvvTriplet}) + k.SetRedelegationQueueTimeSlice(ctx, completionTime, []types.DVVTriplet{dvvTriplet}) } else { timeSlice = append(timeSlice, dvvTriplet) - k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, timeSlice) + k.SetRedelegationQueueTimeSlice(ctx, completionTime, timeSlice) } } @@ -359,14 +409,16 @@ func (k Keeper) RedelegationQueueIterator(ctx sdk.Context, endTime time.Time) sd return store.Iterator(RedelegationQueueKey, sdk.InclusiveEndBytes(GetRedelegationTimeKey(endTime))) } -// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +// Returns a concatenated list of all the timeslices inclusively previous to +// currTime, and deletes the timeslices from the queue func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) // gets an iterator for all timeslices from time 0 until the current Blockheader time redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time) for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { timeslice := []types.DVVTriplet{} - k.cdc.MustUnmarshalBinaryLengthPrefixed(redelegationTimesliceIterator.Value(), ×lice) + value := redelegationTimesliceIterator.Value() + k.cdc.MustUnmarshalBinaryLengthPrefixed(value, ×lice) matureRedelegations = append(matureRedelegations, timeslice...) store.Delete(redelegationTimesliceIterator.Key()) } @@ -398,16 +450,15 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co // call the appropriate hook if present if found { - k.OnDelegationSharesModified(ctx, delAddr, validator.OperatorAddr) + k.BeforeDelegationSharesModified(ctx, delAddr, validator.OperatorAddr) } else { - k.OnDelegationCreated(ctx, delAddr, validator.OperatorAddr) + k.BeforeDelegationCreated(ctx, delAddr, validator.OperatorAddr) } if subtractAccount { - // Account new shares, save - _, _, err = k.bankKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) + _, err := k.bankKeeper.DelegateCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) if err != nil { - return + return sdk.Dec{}, err } } @@ -416,34 +467,32 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co // Update delegation delegation.Shares = delegation.Shares.Add(newShares) k.SetDelegation(ctx, delegation) + k.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) return newShares, nil } // unbond the the delegation return func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, - shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { + shares sdk.Dec) (amount sdk.Int, err sdk.Error) { // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { - err = types.ErrNoDelegatorForAddress(k.Codespace()) - return + return amount, types.ErrNoDelegatorForAddress(k.Codespace()) } - k.OnDelegationSharesModified(ctx, delAddr, valAddr) + k.BeforeDelegationSharesModified(ctx, delAddr, valAddr) // retrieve the amount to remove if delegation.Shares.LT(shares) { - err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) - return + return amount, types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) } // get validator validator, found := k.GetValidator(ctx, valAddr) if !found { - err = types.ErrNoValidatorFound(k.Codespace()) - return + return amount, types.ErrNoValidatorFound(k.Codespace()) } // subtract shares from delegator @@ -463,6 +512,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA } else { // update the delegation k.SetDelegation(ctx, delegation) + k.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) } // remove the coins from the validator @@ -478,9 +528,9 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA //______________________________________________________________________________________________________ -// get info for begin functions: MinTime and CreationHeight +// get info for begin functions: completionTime and CreationHeight func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( - minTime time.Time, height int64, completeNow bool) { + completionTime time.Time, height int64, completeNow bool) { validator, found := k.GetValidator(ctx, valSrcAddr) @@ -488,17 +538,17 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( case !found || validator.Status == sdk.Bonded: // the longest wait - just unbonding period from now - minTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) + completionTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) height = ctx.BlockHeight() - return minTime, height, false + return completionTime, height, false case validator.Status == sdk.Unbonded: - return minTime, height, true + return completionTime, height, true case validator.Status == sdk.Unbonding: - minTime = validator.UnbondingMinTime + completionTime = validator.UnbondingCompletionTime height = validator.UnbondingHeight - return minTime, height, false + return completionTime, height, false default: panic("unknown validator status") @@ -506,143 +556,122 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( } // begin unbonding an unbonding record -func (k Keeper) BeginUnbonding(ctx sdk.Context, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.UnbondingDelegation, sdk.Error) { - - // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 - _, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) - if found { - return types.UnbondingDelegation{}, types.ErrExistingUnbondingDelegation(k.Codespace()) - } +func (k Keeper) Undelegate(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress, sharesAmount sdk.Dec) (completionTime time.Time, sdkErr sdk.Error) { // create the unbonding delegation - minTime, height, completeNow := k.getBeginInfo(ctx, valAddr) + completionTime, height, completeNow := k.getBeginInfo(ctx, valAddr) returnAmount, err := k.unbond(ctx, delAddr, valAddr, sharesAmount) if err != nil { - return types.UnbondingDelegation{}, err + return completionTime, err } - - rounded := returnAmount.TruncateInt() - balance := sdk.NewCoin(k.BondDenom(ctx), rounded) - change := returnAmount.Sub(sdk.NewDecFromInt(rounded)) - - // for now, change is just burned - pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(change) - k.SetPool(ctx, pool) + balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount) // no need to create the ubd object just complete now if completeNow { - _, _, err := k.bankKeeper.AddCoins(ctx, delAddr, sdk.Coins{balance}) - if err != nil { - return types.UnbondingDelegation{}, err + // track undelegation only when remaining or truncated shares are non-zero + if !balance.IsZero() { + if _, err := k.bankKeeper.UndelegateCoins(ctx, delAddr, sdk.Coins{balance}); err != nil { + return completionTime, err + } } - return types.UnbondingDelegation{MinTime: minTime}, nil - } - ubd := types.UnbondingDelegation{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - CreationHeight: height, - MinTime: minTime, - Balance: balance, - InitialBalance: balance, + return completionTime, nil } - k.SetUnbondingDelegation(ctx, ubd) - k.InsertUnbondingQueue(ctx, ubd) - return ubd, nil + ubd := k.SetUnbondingDelegationEntry(ctx, delAddr, + valAddr, height, completionTime, balance) + + k.InsertUBDQueue(ctx, ubd, completionTime) + return completionTime, nil } -// complete unbonding an unbonding record -// CONTRACT: Expects unbonding passed in has finished the unbonding period -func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Error { +// CompleteUnbonding completes the unbonding of all mature entries in the +// retrieved unbonding delegation object. +func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress) sdk.Error { ubd, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) if !found { return types.ErrNoUnbondingDelegation(k.Codespace()) } - _, _, err := k.bankKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance}) - if err != nil { - return err + ctxTime := ctx.BlockHeader().Time + + // loop through all the entries and complete unbonding mature entries + for i := 0; i < len(ubd.Entries); i++ { + entry := ubd.Entries[i] + if entry.IsMature(ctxTime) { + ubd.RemoveEntry(int64(i)) + i-- + + _, _, err := k.bankKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{entry.Balance}) + if err != nil { + return err + } + } + } + + // set the unbonding delegation or remove it if there are no more entries + if len(ubd.Entries) == 0 { + k.RemoveUnbondingDelegation(ctx, ubd) + } else { + k.SetUnbondingDelegation(ctx, ubd) } - k.RemoveUnbondingDelegation(ctx, ubd) + return nil } // begin unbonding / redelegation; create a redelegation record func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, - valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { + valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) ( + completionTime time.Time, errSdk sdk.Error) { if bytes.Equal(valSrcAddr, valDstAddr) { - return types.Redelegation{}, types.ErrSelfRedelegation(k.Codespace()) - } - - // check if there is already a redelgation in progress from src to dst - // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 - _, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr) - if found { - return types.Redelegation{}, types.ErrConflictingRedelegation(k.Codespace()) + return time.Time{}, types.ErrSelfRedelegation(k.Codespace()) } // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { - return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace()) + return time.Time{}, types.ErrTransitiveRedelegation(k.Codespace()) } returnAmount, err := k.unbond(ctx, delAddr, valSrcAddr, sharesAmount) if err != nil { - return types.Redelegation{}, err + return time.Time{}, err } - rounded := returnAmount.TruncateInt() - if rounded.IsZero() { //TODO design consideration - return types.Redelegation{}, types.ErrVerySmallRedelegation(k.Codespace()) + if returnAmount.IsZero() { + return time.Time{}, types.ErrVerySmallRedelegation(k.Codespace()) } - returnCoin := sdk.NewCoin(k.BondDenom(ctx), rounded) - change := returnAmount.Sub(sdk.NewDecFromInt(rounded)) - - // for now, change is just burned - pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(change) - k.SetPool(ctx, pool) + returnCoin := sdk.NewCoin(k.BondDenom(ctx), returnAmount) dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { - return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace()) + return time.Time{}, types.ErrBadRedelegationDst(k.Codespace()) } sharesCreated, err := k.Delegate(ctx, delAddr, returnCoin, dstValidator, false) if err != nil { - return types.Redelegation{}, err + return time.Time{}, err } // create the unbonding delegation - minTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr) + completionTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr) if completeNow { // no need to create the redelegation object - return types.Redelegation{MinTime: minTime}, nil + return completionTime, nil } - red := types.Redelegation{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - CreationHeight: height, - MinTime: minTime, - SharesDst: sharesCreated, - SharesSrc: sharesAmount, - Balance: returnCoin, - InitialBalance: returnCoin, - } - k.SetRedelegation(ctx, red) - k.InsertRedelegationQueue(ctx, red) - return red, nil + red := k.SetRedelegationEntry(ctx, delAddr, valSrcAddr, valDstAddr, + height, completionTime, returnCoin, sharesAmount, sharesCreated) + k.InsertRedelegationQueue(ctx, red, completionTime) + return completionTime, nil } -// complete unbonding an ongoing redelegation +// CompleteRedelegation completes the unbonding of all mature entries in the +// retrieved unbonding delegation object. func (k Keeper) CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) sdk.Error { @@ -651,12 +680,23 @@ func (k Keeper) CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, return types.ErrNoRedelegation(k.Codespace()) } - // ensure that enough time has passed ctxTime := ctx.BlockHeader().Time - if red.MinTime.After(ctxTime) { - return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime) + + // loop through all the entries and complete mature redelegation entries + for i := 0; i < len(red.Entries); i++ { + entry := red.Entries[i] + if entry.IsMature(ctxTime) { + red.RemoveEntry(int64(i)) + i-- + } + } + + // set the redelegation or remove it if there are no more entries + if len(red.Entries) == 0 { + k.RemoveRedelegation(ctx, red) + } else { + k.SetRedelegation(ctx, red) } - k.RemoveRedelegation(ctx, red) return nil } diff --git a/x/stake/keeper/delegation_test.go b/x/staking/keeper/delegation_test.go similarity index 88% rename from x/stake/keeper/delegation_test.go rename to x/staking/keeper/delegation_test.go index 584db1ef6f85..3b4243319ada 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/staking/keeper/delegation_test.go @@ -6,7 +6,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -137,13 +137,8 @@ func TestDelegation(t *testing.T) { func TestUnbondingDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) - ubd := types.UnbondingDelegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - CreationHeight: 0, - MinTime: time.Unix(0, 0), - Balance: sdk.NewInt64Coin(types.DefaultBondDenom, 5), - } + ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0, + time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5)) // set and retrieve a record keeper.SetUnbondingDelegation(ctx, ubd) @@ -152,7 +147,7 @@ func TestUnbondingDelegation(t *testing.T) { require.True(t, ubd.Equal(resUnbond)) // modify a records, save, and retrieve - ubd.Balance = sdk.NewInt64Coin(types.DefaultBondDenom, 21) + ubd.Entries[0].Balance = sdk.NewInt64Coin(types.DefaultBondDenom, 21) keeper.SetUnbondingDelegation(ctx, ubd) resUnbonds := keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) @@ -181,7 +176,7 @@ func TestUnbondingDelegation(t *testing.T) { func TestUnbondDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(10) + pool.NotBondedTokens = sdk.NewInt(10) //create a validator and a delegator to that validator validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -191,8 +186,8 @@ func TestUnbondDelegation(t *testing.T) { validator = TestingUpdateValidator(keeper, ctx, validator, true) pool = keeper.GetPool(ctx) - require.Equal(t, int64(10), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(10), validator.BondedTokens().RoundInt64()) + require.Equal(t, int64(10), pool.BondedTokens.Int64()) + require.Equal(t, int64(10), validator.BondedTokens().Int64()) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -203,7 +198,7 @@ func TestUnbondDelegation(t *testing.T) { amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) - require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation + require.Equal(t, int64(6), amount.Int64()) // shares to be added to an unbonding delegation / redelegation delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) @@ -212,9 +207,9 @@ func TestUnbondDelegation(t *testing.T) { pool = keeper.GetPool(ctx) require.Equal(t, int64(4), delegation.Shares.RoundInt64()) - require.Equal(t, int64(4), validator.BondedTokens().RoundInt64()) - require.Equal(t, int64(6), pool.LooseTokens.RoundInt64(), "%v", pool) - require.Equal(t, int64(4), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(4), validator.BondedTokens().Int64()) + require.Equal(t, int64(6), pool.NotBondedTokens.Int64(), "%v", pool) + require.Equal(t, int64(4), pool.BondedTokens.Int64()) } // test removing all self delegation from a validator which should @@ -223,7 +218,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(20) + pool.NotBondedTokens = sdk.NewInt(20) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -254,7 +249,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { keeper.SetDelegation(ctx, delegation) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -263,14 +258,14 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, int64(10), validator.Tokens.RoundInt64()) + require.Equal(t, int64(10), validator.Tokens.Int64()) require.Equal(t, sdk.Unbonding, validator.Status) } func TestUndelegateFromUnbondingValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(20) + pool.NotBondedTokens = sdk.NewInt(20) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -310,7 +305,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.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -321,7 +316,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingCompletionTime)) //change the context header = ctx.BlockHeader() @@ -332,21 +327,22 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond some of the other delegation's shares - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) // retrieve the unbonding delegation ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) - require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) - assert.Equal(t, blockHeight, ubd.CreationHeight) - assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) + require.Len(t, ubd.Entries, 1) + require.True(t, ubd.Entries[0].Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) + assert.Equal(t, blockHeight, ubd.Entries[0].CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.Entries[0].CompletionTime)) } func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(20) + pool.NotBondedTokens = sdk.NewInt(20) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -382,7 +378,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.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -393,10 +389,10 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.True(t, found) require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingCompletionTime)) // unbond the validator - ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + ctx = ctx.WithBlockTime(validator.UnbondingCompletionTime) keeper.UnbondAllMatureValidatorQueue(ctx) // Make sure validator is still in state because there is still an outstanding delegation @@ -405,7 +401,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.Equal(t, validator.Status, sdk.Unbonded) // unbond some of the other delegation's shares - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) // no ubd should have been found, coins should have been returned direcly to account @@ -413,7 +409,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.False(t, found, "%v", ubd) // unbond rest of the other delegation's shares - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(4)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDec(4)) require.NoError(t, err) // now validator should now be deleted from state @@ -425,7 +421,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { func TestUnbondingAllDelegationFromValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(20) + pool.NotBondedTokens = sdk.NewInt(20) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -461,7 +457,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.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -469,7 +465,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { require.Equal(t, 1, len(updates)) // unbond all the remaining delegation - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(10)) + _, err = keeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // validator should still be in state and still be in unbonding state @@ -478,7 +474,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { require.Equal(t, validator.Status, sdk.Unbonding) // unbond the validator - ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + ctx = ctx.WithBlockTime(validator.UnbondingCompletionTime) keeper.UnbondAllMatureValidatorQueue(ctx) // validator should now be deleted from state @@ -490,15 +486,9 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { func TestGetRedelegationsFromValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) - rd := types.Redelegation{ - DelegatorAddr: addrDels[0], - ValidatorSrcAddr: addrVals[0], - ValidatorDstAddr: addrVals[1], - CreationHeight: 0, - MinTime: time.Unix(0, 0), - SharesSrc: sdk.NewDec(5), - SharesDst: sdk.NewDec(5), - } + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0, + time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5), + sdk.NewDec(5), sdk.NewDec(5)) // set and retrieve a record keeper.SetRedelegation(ctx, rd) @@ -520,15 +510,9 @@ func TestGetRedelegationsFromValidator(t *testing.T) { func TestRedelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) - rd := types.Redelegation{ - DelegatorAddr: addrDels[0], - ValidatorSrcAddr: addrVals[0], - ValidatorDstAddr: addrVals[1], - CreationHeight: 0, - MinTime: time.Unix(0, 0), - SharesSrc: sdk.NewDec(5), - SharesDst: sdk.NewDec(5), - } + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0, + time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5), + sdk.NewDec(5), sdk.NewDec(5)) // test shouldn't have and redelegations has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) @@ -547,7 +531,7 @@ func TestRedelegation(t *testing.T) { require.Equal(t, 1, len(redelegations)) require.True(t, redelegations[0].Equal(resRed)) - redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0], nil, nil) require.Equal(t, 1, len(redelegations)) require.True(t, redelegations[0].Equal(resRed)) @@ -556,8 +540,8 @@ func TestRedelegation(t *testing.T) { require.True(t, has) // modify a records, save, and retrieve - rd.SharesSrc = sdk.NewDec(21) - rd.SharesDst = sdk.NewDec(21) + rd.Entries[0].SharesSrc = sdk.NewDec(21) + rd.Entries[0].SharesDst = sdk.NewDec(21) keeper.SetRedelegation(ctx, rd) resRed, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -580,15 +564,14 @@ func TestRedelegation(t *testing.T) { redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) require.Equal(t, 0, len(redelegations)) - redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0], nil, nil) require.Equal(t, 0, len(redelegations)) } func TestRedelegateToSameValidator(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(30) + pool.NotBondedTokens = sdk.NewInt(30) // create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -611,10 +594,9 @@ func TestRedelegateToSameValidator(t *testing.T) { } func TestRedelegateSelfDelegation(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(30) + pool.NotBondedTokens = sdk.NewInt(30) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -635,7 +617,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) - pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(10)) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewInt(10)) keeper.SetPool(ctx, pool) validator2 = TestingUpdateValidator(keeper, ctx, validator2, true) require.Equal(t, sdk.Bonded, validator2.Status) @@ -662,14 +644,14 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, int64(10), validator.Tokens.RoundInt64()) + require.Equal(t, int64(10), validator.Tokens.Int64()) require.Equal(t, sdk.Unbonding, validator.Status) } func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(30) + pool.NotBondedTokens = sdk.NewInt(30) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -716,7 +698,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -727,7 +709,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingCompletionTime)) //change the context header = ctx.BlockHeader() @@ -744,15 +726,16 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { // retrieve the unbonding delegation ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) - require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) - assert.Equal(t, blockHeight, ubd.CreationHeight) - assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) + require.Len(t, ubd.Entries, 1) + require.True(t, ubd.Entries[0].Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) + assert.Equal(t, blockHeight, ubd.Entries[0].CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.Entries[0].CompletionTime)) } func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.LooseTokens = sdk.NewDec(30) + pool.NotBondedTokens = sdk.NewInt(30) //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) @@ -796,7 +779,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.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -807,7 +790,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { require.True(t, found) require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingCompletionTime)) // unbond the validator keeper.unbondingToUnbonded(ctx, validator) diff --git a/x/staking/keeper/hooks.go b/x/staking/keeper/hooks.go new file mode 100644 index 000000000000..4ed0a724c54d --- /dev/null +++ b/x/staking/keeper/hooks.go @@ -0,0 +1,73 @@ +//nolint +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Expose the hooks if present +func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterValidatorCreated(ctx, valAddr) + } +} + +func (k Keeper) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.BeforeValidatorModified(ctx, valAddr) + } +} + +func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterValidatorRemoved(ctx, consAddr, valAddr) + } +} + +func (k Keeper) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterValidatorBonded(ctx, consAddr, valAddr) + } +} + +func (k Keeper) AfterValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterValidatorPowerDidChange(ctx, consAddr, valAddr) + } +} + +func (k Keeper) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) + } +} + +func (k Keeper) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.BeforeDelegationCreated(ctx, delAddr, valAddr) + } +} + +func (k Keeper) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.BeforeDelegationSharesModified(ctx, delAddr, valAddr) + } +} + +func (k Keeper) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.BeforeDelegationRemoved(ctx, delAddr, valAddr) + } +} + +func (k Keeper) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.AfterDelegationModified(ctx, delAddr, valAddr) + } +} + +func (k Keeper) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { + if k.hooks != nil { + k.hooks.BeforeValidatorSlashed(ctx, valAddr, fraction) + } +} diff --git a/x/stake/keeper/keeper.go b/x/staking/keeper/keeper.go similarity index 98% rename from x/stake/keeper/keeper.go rename to x/staking/keeper/keeper.go index 93b75958d176..ea3527d5d633 100644 --- a/x/stake/keeper/keeper.go +++ b/x/staking/keeper/keeper.go @@ -8,12 +8,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) const aminoCacheSize = 500 -// keeper of the stake store +// keeper of the staking store type Keeper struct { storeKey sdk.StoreKey storeTKey sdk.StoreKey diff --git a/x/stake/keeper/keeper_test.go b/x/staking/keeper/keeper_test.go similarity index 91% rename from x/stake/keeper/keeper_test.go rename to x/staking/keeper/keeper_test.go index 9307ddddf252..89ffe13d19ee 100644 --- a/x/stake/keeper/keeper_test.go +++ b/x/staking/keeper/keeper_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) func TestParams(t *testing.T) { @@ -33,7 +33,7 @@ func TestPool(t *testing.T) { require.True(t, expPool.Equal(resPool)) //modify a params, save, and retrieve - expPool.BondedTokens = sdk.NewDec(777) + expPool.BondedTokens = sdk.NewInt(777) keeper.SetPool(ctx, expPool) resPool = keeper.GetPool(ctx) require.True(t, expPool.Equal(resPool)) diff --git a/x/stake/keeper/key.go b/x/staking/keeper/key.go similarity index 97% rename from x/stake/keeper/key.go rename to x/staking/keeper/key.go index ce1429052317..6dbb053cf688 100644 --- a/x/stake/keeper/key.go +++ b/x/staking/keeper/key.go @@ -5,7 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // TODO remove some of these prefixes once have working multistore @@ -14,7 +14,7 @@ import ( var ( // Keys for store prefixes // TODO DEPRECATED: delete in next release and reorder keys - // ParamKey = []byte{0x00} // key for parameters relating to staking + // ParamKey = []byte{0x00} // key for parameters relating to stake PoolKey = []byte{0x01} // key for the staking pools // Last* values are const during a block. @@ -40,7 +40,7 @@ var ( const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch // gets the key for the validator with address -// VALUE: stake/types.Validator +// VALUE: staking/types.Validator func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { return append(ValidatorsKey, operatorAddr.Bytes()...) } @@ -78,7 +78,7 @@ func getValidatorPowerRank(validator types.Validator) []byte { potentialPower := validator.Tokens // todo: deal with cases above 2**64, ref https://github.com/cosmos/cosmos-sdk/issues/2439#issuecomment-427167556 - tendermintPower := potentialPower.RoundInt64() + tendermintPower := potentialPower.Int64() tendermintPowerBytes := make([]byte, 8) binary.BigEndian.PutUint64(tendermintPowerBytes[:], uint64(tendermintPower)) @@ -120,7 +120,7 @@ func GetValidatorQueueTimeKey(timestamp time.Time) []byte { //______________________________________________________________________________ // gets the key for delegator bond with validator -// VALUE: stake/types.Delegation +// VALUE: staking/types.Delegation func GetDelegationKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { return append(GetDelegationsKey(delAddr), valAddr.Bytes()...) } @@ -133,7 +133,7 @@ func GetDelegationsKey(delAddr sdk.AccAddress) []byte { //______________________________________________________________________________ // gets the key for an unbonding delegation by delegator and validator addr -// VALUE: stake/types.UnbondingDelegation +// VALUE: staking/types.UnbondingDelegation func GetUBDKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { return append( GetUBDsKey(delAddr.Bytes()), @@ -178,7 +178,7 @@ func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { //________________________________________________________________________________ // gets the key for a redelegation -// VALUE: stake/types.RedelegationKey +// VALUE: staking/types.RedelegationKey func GetREDKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte { key := make([]byte, 1+sdk.AddrLen*3) diff --git a/x/stake/keeper/key_test.go b/x/staking/keeper/key_test.go similarity index 94% rename from x/stake/keeper/key_test.go rename to x/staking/keeper/key_test.go index f994c392035a..9f80062ac482 100644 --- a/x/stake/keeper/key_test.go +++ b/x/staking/keeper/key_test.go @@ -9,7 +9,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -25,12 +25,12 @@ func TestGetValidatorPowerRank(t *testing.T) { valAddr1 := sdk.ValAddress(addr1) emptyDesc := types.Description{} val1 := types.NewValidator(valAddr1, pk1, emptyDesc) - val1.Tokens = sdk.NewDec(0) + val1.Tokens = sdk.ZeroInt() val2, val3, val4 := val1, val1, val1 - val2.Tokens = sdk.NewDec(1) - val3.Tokens = sdk.NewDec(10) + val2.Tokens = sdk.NewInt(1) + val3.Tokens = sdk.NewInt(10) x := new(big.Int).Exp(big.NewInt(2), big.NewInt(40), big.NewInt(0)) - val4.Tokens = sdk.NewDecFromBigInt(x) + val4.Tokens = sdk.NewIntFromBigInt(x) tests := []struct { validator types.Validator diff --git a/x/stake/keeper/params.go b/x/staking/keeper/params.go similarity index 90% rename from x/stake/keeper/params.go rename to x/staking/keeper/params.go index b62a7688fbb8..2d3fdf296e10 100644 --- a/x/stake/keeper/params.go +++ b/x/staking/keeper/params.go @@ -5,15 +5,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Default parameter namespace const ( - DefaultParamspace = "stake" + DefaultParamspace = "staking" ) -// ParamTable for stake module +// ParamTable for staking module func ParamTypeTable() params.TypeTable { return params.NewTypeTable().RegisterParamSet(&types.Params{}) } diff --git a/x/stake/keeper/query_utils.go b/x/staking/keeper/query_utils.go similarity index 81% rename from x/stake/keeper/query_utils.go rename to x/staking/keeper/query_utils.go index c1575cd42121..872059f1108a 100644 --- a/x/stake/keeper/query_utils.go +++ b/x/staking/keeper/query_utils.go @@ -2,7 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Return all validators that a delegator is bonded to. If maxRetrieve is supplied, the respective amount will be returned. @@ -17,8 +17,7 @@ func (k Keeper) GetDelegatorValidators(ctx sdk.Context, delegatorAddr sdk.AccAdd i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { - addr := iterator.Key() - delegation := types.MustUnmarshalDelegation(k.cdc, addr, iterator.Value()) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) validator, found := k.GetValidator(ctx, delegation.ValidatorAddr) if !found { @@ -59,7 +58,7 @@ func (k Keeper) GetAllDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAdd i := 0 for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) delegations = append(delegations, delegation) i++ } @@ -77,7 +76,7 @@ func (k Keeper) GetAllUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAdd i := 0 for ; iterator.Valid(); iterator.Next() { - unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Value()) unbondingDelegations = append(unbondingDelegations, unbondingDelegation) i++ } @@ -85,17 +84,24 @@ func (k Keeper) GetAllUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAdd } // return all redelegations for a delegator -func (k Keeper) GetAllRedelegations(ctx sdk.Context, delegator sdk.AccAddress) (redelegations []types.Redelegation) { +func (k Keeper) GetAllRedelegations(ctx sdk.Context, delegator sdk.AccAddress, srcValAddress, dstValAddress sdk.ValAddress) (redelegations []types.Redelegation) { store := ctx.KVStore(k.storeKey) delegatorPrefixKey := GetREDsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest defer iterator.Close() - i := 0 + srcValFilter := !(srcValAddress.Empty() || srcValAddress == nil) + dstValFilter := !(dstValAddress.Empty() || dstValAddress == nil) + for ; iterator.Valid(); iterator.Next() { - redelegation := types.MustUnmarshalRED(k.cdc, iterator.Key(), iterator.Value()) + redelegation := types.MustUnmarshalRED(k.cdc, iterator.Value()) + if srcValFilter && !(srcValAddress.Equals(redelegation.ValidatorSrcAddr)) { + continue + } + if dstValFilter && !(dstValAddress.Equals(redelegation.ValidatorDstAddr)) { + continue + } redelegations = append(redelegations, redelegation) - i++ } return redelegations } diff --git a/x/stake/keeper/sdk_types.go b/x/staking/keeper/sdk_types.go similarity index 90% rename from x/stake/keeper/sdk_types.go rename to x/staking/keeper/sdk_types.go index f777077d7d1f..7e610360a5d9 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/staking/keeper/sdk_types.go @@ -4,7 +4,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Implements ValidatorSet @@ -16,8 +16,7 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) i := int64(0) for ; iterator.Valid(); iterator.Next() { - addr := iterator.Key()[1:] - validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + validator := types.MustUnmarshalValidator(k.cdc, iterator.Value()) stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? if stop { break @@ -89,7 +88,7 @@ func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.V } // total power from the bond (not last, but current) -func (k Keeper) TotalPower(ctx sdk.Context) sdk.Dec { +func (k Keeper) TotalPower(ctx sdk.Context) sdk.Int { pool := k.GetPool(ctx) return pool.BondedTokens } @@ -101,9 +100,9 @@ func (k Keeper) BondedRatio(ctx sdk.Context) sdk.Dec { } // when minting new tokens -func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Dec) { +func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Int) { pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Add(newTokens) + pool.NotBondedTokens = pool.NotBondedTokens.Add(newTokens) k.SetPool(ctx, pool) } @@ -136,7 +135,7 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, delegatorPrefixKey := GetDelegationsKey(delAddr) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest for i := int64(0); iterator.Valid(); iterator.Next() { - del := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + del := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) stop := fn(i, del) if stop { break diff --git a/x/stake/keeper/slash.go b/x/staking/keeper/slash.go similarity index 62% rename from x/stake/keeper/slash.go rename to x/staking/keeper/slash.go index e7bd72764b5d..3ca7ffcad5bc 100644 --- a/x/stake/keeper/slash.go +++ b/x/staking/keeper/slash.go @@ -4,7 +4,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - types "github.com/cosmos/cosmos-sdk/x/stake/types" + types "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Slash a validator for an infraction committed at a known height @@ -22,14 +22,16 @@ import ( // Infraction committed at the current height or at a past height, // not at a height in the future func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { - logger := ctx.Logger().With("module", "x/stake") + logger := ctx.Logger().With("module", "x/staking") if slashFactor.LT(sdk.ZeroDec()) { panic(fmt.Errorf("attempted to slash with a negative slash factor: %v", slashFactor)) } // Amount of slashing = slash slashFactor * power at time of infraction - slashAmount := sdk.NewDec(power).Mul(slashFactor) + slashAmountDec := sdk.NewDec(power).Mul(slashFactor) + slashAmount := slashAmountDec.TruncateInt() + // ref https://github.com/cosmos/cosmos-sdk/issues/1348 // ref https://github.com/cosmos/cosmos-sdk/issues/1471 @@ -51,7 +53,17 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } operatorAddress := validator.GetOperator() - k.OnValidatorModified(ctx, operatorAddress) + 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() + } + k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction) + } // Track remaining slash amount for the validator // This will decrease when we slash unbondings and @@ -97,15 +109,15 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } // cannot decrease balance below zero - tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) - tokensToBurn = sdk.MaxDec(tokensToBurn, sdk.ZeroDec()) // defensive. + tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) + tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive. // Deduct from validator's bonded tokens and update the validator. - // The deducted tokens are returned to pool.LooseTokens. + // The deducted tokens are returned to pool.NotBondedTokens. validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) pool := k.GetPool(ctx) // Burn the slashed tokens, which are now loose. - pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) + pool.NotBondedTokens = pool.NotBondedTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) // Log that a slash occurred! @@ -121,7 +133,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { validator := k.mustGetValidatorByConsAddr(ctx, consAddr) k.jailValidator(ctx, validator) - logger := ctx.Logger().With("module", "x/stake") + logger := ctx.Logger().With("module", "x/staking") logger.Info(fmt.Sprintf("validator %s jailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return @@ -131,7 +143,7 @@ func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { validator := k.mustGetValidatorByConsAddr(ctx, consAddr) k.unjailValidator(ctx, validator) - logger := ctx.Logger().With("module", "x/stake") + logger := ctx.Logger().With("module", "x/staking") logger.Info(fmt.Sprintf("validator %s unjailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return @@ -143,43 +155,51 @@ func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { // (the amount actually slashed may be less if there's // insufficient stake remaining) func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, - infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec) { + infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) { now := ctx.BlockHeader().Time + totalSlashAmount = sdk.ZeroInt() - // If unbonding started before this height, stake didn't contribute to infraction - if unbondingDelegation.CreationHeight < infractionHeight { - return sdk.ZeroDec() - } + // perform slashing on all entries within the unbonding delegation + for i, entry := range unbondingDelegation.Entries { - if unbondingDelegation.MinTime.Before(now) { - // Unbonding delegation no longer eligible for slashing, skip it - // TODO Settle and delete it automatically? - return sdk.ZeroDec() - } + // If unbonding started before this height, stake didn't contribute to infraction + if entry.CreationHeight < infractionHeight { + continue + } + + if entry.IsMature(now) { + // Unbonding delegation no longer eligible for slashing, skip it + continue + } - // Calculate slash amount proportional to stake contributing to infraction - slashAmount = slashFactor.MulInt(unbondingDelegation.InitialBalance.Amount) + // Calculate slash amount proportional to stake contributing to infraction + slashAmountDec := slashFactor.MulInt(entry.InitialBalance.Amount) + slashAmount := slashAmountDec.TruncateInt() + totalSlashAmount = totalSlashAmount.Add(slashAmount) - // Don't slash more tokens than held - // Possible since the unbonding delegation may already - // have been slashed, and slash amounts are calculated - // according to stake held at time of infraction - unbondingSlashAmount := sdk.MinInt(slashAmount.RoundInt(), unbondingDelegation.Balance.Amount) + // Don't slash more tokens than held + // Possible since the unbonding delegation may already + // have been slashed, and slash amounts are calculated + // according to stake held at time of infraction + unbondingSlashAmount := sdk.MinInt(slashAmount, entry.Balance.Amount) - // Update unbonding delegation if necessary - if !unbondingSlashAmount.IsZero() { - unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount) + // Update unbonding delegation if necessary + if unbondingSlashAmount.IsZero() { + continue + } + entry.Balance.Amount = entry.Balance.Amount.Sub(unbondingSlashAmount) + unbondingDelegation.Entries[i] = entry k.SetUnbondingDelegation(ctx, unbondingDelegation) pool := k.GetPool(ctx) - // Burn loose tokens + // Burn not-bonded tokens // Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760 - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(unbondingSlashAmount)) + pool.NotBondedTokens = pool.NotBondedTokens.Sub(unbondingSlashAmount) k.SetPool(ctx, pool) } - return + return totalSlashAmount } // slash a redelegation and update the pool @@ -189,43 +209,51 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty // insufficient stake remaining) // nolint: unparam func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation, - infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec) { + infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) { now := ctx.BlockHeader().Time + totalSlashAmount = sdk.ZeroInt() - // If redelegation started before this height, stake didn't contribute to infraction - if redelegation.CreationHeight < infractionHeight { - return sdk.ZeroDec() - } + // perform slashing on all entries within the redelegation + for i, entry := range redelegation.Entries { - if redelegation.MinTime.Before(now) { - // Redelegation no longer eligible for slashing, skip it - // TODO Delete it automatically? - return sdk.ZeroDec() - } - - // Calculate slash amount proportional to stake contributing to infraction - slashAmount = slashFactor.MulInt(redelegation.InitialBalance.Amount) + // If redelegation started before this height, stake didn't contribute to infraction + if entry.CreationHeight < infractionHeight { + continue + } - // Don't slash more tokens than held - // Possible since the redelegation may already - // have been slashed, and slash amounts are calculated - // according to stake held at time of infraction - redelegationSlashAmount := sdk.MinInt(slashAmount.RoundInt(), redelegation.Balance.Amount) + if entry.IsMature(now) { + // Redelegation no longer eligible for slashing, skip it + continue + } - // Update redelegation if necessary - if !redelegationSlashAmount.IsZero() { - redelegation.Balance.Amount = redelegation.Balance.Amount.Sub(redelegationSlashAmount) - k.SetRedelegation(ctx, redelegation) - } + // Calculate slash amount proportional to stake contributing to infraction + slashAmountDec := slashFactor.MulInt(entry.InitialBalance.Amount) + slashAmount := slashAmountDec.TruncateInt() + totalSlashAmount = totalSlashAmount.Add(slashAmount) + + // Don't slash more tokens than held + // Possible since the redelegation may already + // have been slashed, and slash amounts are calculated + // according to stake held at time of infraction + redelegationSlashAmount := sdk.MinInt(slashAmount, entry.Balance.Amount) + + // Update entry if necessary + if !redelegationSlashAmount.IsZero() { + entry.Balance.Amount = entry.Balance.Amount.Sub(redelegationSlashAmount) + redelegation.Entries[i] = entry + k.SetRedelegation(ctx, redelegation) + } - // Unbond from target validator - sharesToUnbond := slashFactor.Mul(redelegation.SharesDst) - if !sharesToUnbond.IsZero() { + // Unbond from target validator + sharesToUnbond := slashFactor.Mul(entry.SharesDst) + if sharesToUnbond.IsZero() { + continue + } delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr) if !found { // If deleted, delegation has zero shares, and we can't unbond any more - return slashAmount + continue } if sharesToUnbond.GT(delegation.Shares) { sharesToUnbond = delegation.Shares @@ -236,11 +264,11 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re panic(fmt.Errorf("error unbonding delegator: %v", err)) } - // Burn loose tokens + // Burn not-bonded tokens pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) + pool.NotBondedTokens = pool.NotBondedTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) } - return slashAmount + return totalSlashAmount } diff --git a/x/stake/keeper/slash_test.go b/x/staking/keeper/slash_test.go similarity index 75% rename from x/stake/keeper/slash_test.go rename to x/staking/keeper/slash_test.go index 94418a962cc8..3dc5e1364eb3 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/staking/keeper/slash_test.go @@ -9,7 +9,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // TODO integrate with test_common.go helper (CreateTestInput) @@ -21,13 +21,13 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { params := keeper.GetParams(ctx) pool := keeper.GetPool(ctx) numVals := 3 - pool.LooseTokens = sdk.NewDec(amt * int64(numVals)) + pool.NotBondedTokens = sdk.NewInt(amt * int64(numVals)) // add numVals validators for i := 0; i < numVals; i++ { validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(amt)) - pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(amt)) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewInt(amt)) keeper.SetPool(ctx, pool) validator = TestingUpdateValidator(keeper, ctx, validator, true) keeper.SetValidatorByConsAddr(ctx, validator) @@ -70,44 +70,40 @@ func TestSlashUnbondingDelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) fraction := sdk.NewDecWithPrec(5, 1) - // set an unbonding delegation - ubd := types.UnbondingDelegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - CreationHeight: 0, - // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) - MinTime: time.Unix(0, 0), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 10), - Balance: sdk.NewInt64Coin(params.BondDenom, 10), - } + // set an unbonding delegation with expiration timestamp (beyond which the + // unbonding delegation shouldn't be slashed) + ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0, + time.Unix(5, 0), sdk.NewInt64Coin(params.BondDenom, 10)) + keeper.SetUnbondingDelegation(ctx, ubd) - // unbonding started prior to the infraction height, stake didn't contribute + // unbonding started prior to the infraction height, stakw didn't contribute slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction) - require.Equal(t, int64(0), slashAmount.RoundInt64()) + require.Equal(t, int64(0), slashAmount.Int64()) // after the expiration time, no longer eligible for slashing ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) keeper.SetUnbondingDelegation(ctx, ubd) slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) - require.Equal(t, int64(0), slashAmount.RoundInt64()) + require.Equal(t, int64(0), slashAmount.Int64()) // test valid slash, before expiration timestamp and to which stake contributed oldPool := keeper.GetPool(ctx) ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) keeper.SetUnbondingDelegation(ctx, ubd) slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) - require.Equal(t, int64(5), slashAmount.RoundInt64()) + require.Equal(t, int64(5), slashAmount.Int64()) ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + require.Len(t, ubd.Entries, 1) - // initialbalance unchanged - require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance) + // initial balance unchanged + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.Entries[0].InitialBalance) // balance decreased - require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance) + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Entries[0].Balance) newPool := keeper.GetPool(ctx) - require.Equal(t, int64(5), oldPool.LooseTokens.Sub(newPool.LooseTokens).RoundInt64()) + require.Equal(t, int64(5), oldPool.NotBondedTokens.Sub(newPool.NotBondedTokens).Int64()) } // tests slashRedelegation @@ -115,19 +111,12 @@ func TestSlashRedelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) fraction := sdk.NewDecWithPrec(5, 1) - // set a redelegation - rd := types.Redelegation{ - DelegatorAddr: addrDels[0], - ValidatorSrcAddr: addrVals[0], - ValidatorDstAddr: addrVals[1], - CreationHeight: 0, - // expiration timestamp (beyond which the redelegation shouldn't be slashed) - MinTime: time.Unix(0, 0), - SharesSrc: sdk.NewDec(10), - SharesDst: sdk.NewDec(10), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 10), - Balance: sdk.NewInt64Coin(params.BondDenom, 10), - } + // set a redelegation with an expiration timestamp beyond which the + // redelegation shouldn't be slashed + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0, + time.Unix(5, 0), sdk.NewInt64Coin(params.BondDenom, 10), sdk.NewDec(10), + sdk.NewDec(10)) + keeper.SetRedelegation(ctx, rd) // set the associated delegation @@ -142,7 +131,7 @@ func TestSlashRedelegation(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction) - require.Equal(t, int64(0), slashAmount.RoundInt64()) + require.Equal(t, int64(0), slashAmount.Int64()) // after the expiration time, no longer eligible for slashing ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) @@ -150,7 +139,7 @@ func TestSlashRedelegation(t *testing.T) { validator, found = keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) - require.Equal(t, int64(0), slashAmount.RoundInt64()) + require.Equal(t, int64(0), slashAmount.Int64()) // test valid slash, before expiration timestamp and to which stake contributed oldPool := keeper.GetPool(ctx) @@ -159,19 +148,20 @@ func TestSlashRedelegation(t *testing.T) { validator, found = keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) - require.Equal(t, int64(5), slashAmount.RoundInt64()) + require.Equal(t, int64(5), slashAmount.Int64()) rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rd.Entries, 1) // end block updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(updates)) // initialbalance unchanged - require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance) + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.Entries[0].InitialBalance) // balance decreased - require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Balance) + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Entries[0].Balance) // shares decreased del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) @@ -180,7 +170,7 @@ func TestSlashRedelegation(t *testing.T) { // pool bonded tokens decreased newPool := keeper.GetPool(ctx) - require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) } // tests Slash at a future height (must panic) @@ -214,9 +204,9 @@ func TestSlashAtNegativeHeight(t *testing.T) { validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) // power decreased - require.Equal(t, sdk.NewDec(5), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(5), validator.GetPower())) // pool bonded shares decreased - require.Equal(t, sdk.NewDec(5).RoundInt64(), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) } // tests Slash at the current height @@ -241,9 +231,9 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) // power decreased - require.Equal(t, sdk.NewDec(5), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(5), validator.GetPower())) // pool bonded shares decreased - require.Equal(t, sdk.NewDec(5).RoundInt64(), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) } // tests Slash at a previous height with an unbonding delegation @@ -252,16 +242,10 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) - // set an unbonding delegation - ubd := types.UnbondingDelegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - CreationHeight: 11, - // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) - MinTime: time.Unix(0, 0), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 4), - Balance: sdk.NewInt64Coin(params.BondDenom, 4), - } + // set an unbonding delegation with expiration timestamp beyond which the + // unbonding delegation shouldn't be slashed + ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, + time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 4)) keeper.SetUnbondingDelegation(ctx, ubd) // slash validator for the first time @@ -278,12 +262,13 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // read updating unbonding delegation ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + require.Len(t, ubd.Entries, 1) // balance decreased - require.Equal(t, sdk.NewInt(2), ubd.Balance.Amount) + require.Equal(t, sdk.NewInt(2), ubd.Entries[0].Balance.Amount) // read updated pool newPool := keeper.GetPool(ctx) // bonded tokens burned - require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) @@ -291,24 +276,25 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // was still bonded at the time of discovery and was slashed by half, 4 stake // bonded at the time of discovery hadn't been bonded at the time of infraction // and wasn't slashed - require.Equal(t, sdk.NewDec(7), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(7), validator.GetPower())) // slash validator again ctx = ctx.WithBlockHeight(13) keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + require.Len(t, ubd.Entries, 1) // balance decreased again - require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // bonded tokens burned again - require.Equal(t, int64(6), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(6), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again - require.Equal(t, sdk.NewDec(4), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(4), validator.GetPower())) // slash validator again // all originally bonded stake has been slashed, so this will have no effect @@ -318,17 +304,18 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + require.Len(t, ubd.Entries, 1) // balance unchanged - require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // bonded tokens burned again - require.Equal(t, int64(9), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(9), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again - require.Equal(t, sdk.NewDec(1), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(1), validator.GetPower())) // slash validator again // all originally bonded stake has been slashed, so this will have no effect @@ -338,12 +325,13 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + require.Len(t, ubd.Entries, 1) // balance unchanged - require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // just 1 bonded token burned again since that's all the validator now has - require.Equal(t, int64(10), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(10), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator @@ -360,17 +348,9 @@ func TestSlashWithRedelegation(t *testing.T) { fraction := sdk.NewDecWithPrec(5, 1) // set a redelegation - rd := types.Redelegation{ - DelegatorAddr: addrDels[0], - ValidatorSrcAddr: addrVals[0], - ValidatorDstAddr: addrVals[1], - CreationHeight: 11, - MinTime: time.Unix(0, 0), - SharesSrc: sdk.NewDec(6), - SharesDst: sdk.NewDec(6), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 6), - Balance: sdk.NewInt64Coin(params.BondDenom, 6), - } + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, + time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 6), sdk.NewDec(6), + sdk.NewDec(6)) keeper.SetRedelegation(ctx, rd) // set the associated delegation @@ -383,7 +363,7 @@ func TestSlashWithRedelegation(t *testing.T) { // update bonded tokens pool := keeper.GetPool(ctx) - pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(6)) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewInt(6)) keeper.SetPool(ctx, pool) // slash validator @@ -396,12 +376,13 @@ func TestSlashWithRedelegation(t *testing.T) { // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rd.Entries, 1) // balance decreased - require.Equal(t, sdk.NewInt(3), rd.Balance.Amount) + require.Equal(t, sdk.NewInt(3), rd.Entries[0].Balance.Amount) // read updated pool newPool := keeper.GetPool(ctx) // bonded tokens burned - require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) @@ -409,7 +390,7 @@ func TestSlashWithRedelegation(t *testing.T) { // was still bonded at the time of discovery and was slashed by half, 4 stake // bonded at the time of discovery hadn't been bonded at the time of infraction // and wasn't slashed - require.Equal(t, sdk.NewDec(8), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(8), validator.GetPower())) // slash the validator again ctx = ctx.WithBlockHeight(12) @@ -420,17 +401,18 @@ func TestSlashWithRedelegation(t *testing.T) { // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rd.Entries, 1) // balance decreased, now zero - require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // seven bonded tokens burned - require.Equal(t, int64(12), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(12), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 4 - require.Equal(t, sdk.NewDec(4), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(4), validator.GetPower())) // slash the validator again, by 100% ctx = ctx.WithBlockHeight(12) @@ -441,12 +423,13 @@ func TestSlashWithRedelegation(t *testing.T) { // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rd.Entries, 1) // balance still zero - require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // four more bonded tokens burned - require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator @@ -465,12 +448,13 @@ func TestSlashWithRedelegation(t *testing.T) { // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rd.Entries, 1) // balance still zero - require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount) // read updated pool newPool = keeper.GetPool(ctx) // no more bonded tokens burned - require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator // power still zero, still in unbonding period validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) @@ -482,19 +466,11 @@ func TestSlashBoth(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) fraction := sdk.NewDecWithPrec(5, 1) - // set a redelegation - rdA := types.Redelegation{ - DelegatorAddr: addrDels[0], - ValidatorSrcAddr: addrVals[0], - ValidatorDstAddr: addrVals[1], - CreationHeight: 11, - // expiration timestamp (beyond which the redelegation shouldn't be slashed) - MinTime: time.Unix(0, 0), - SharesSrc: sdk.NewDec(6), - SharesDst: sdk.NewDec(6), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 6), - Balance: sdk.NewInt64Coin(params.BondDenom, 6), - } + // set a redelegation with expiration timestamp beyond which the + // redelegation shouldn't be slashed + rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, + time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 6), sdk.NewDec(6), + sdk.NewDec(6)) keeper.SetRedelegation(ctx, rdA) // set the associated delegation @@ -505,16 +481,10 @@ func TestSlashBoth(t *testing.T) { } keeper.SetDelegation(ctx, delA) - // set an unbonding delegation - ubdA := types.UnbondingDelegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - CreationHeight: 11, - // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) - MinTime: time.Unix(0, 0), - InitialBalance: sdk.NewInt64Coin(params.BondDenom, 4), - Balance: sdk.NewInt64Coin(params.BondDenom, 4), - } + // set an unbonding delegation with expiration timestamp (beyond which the + // unbonding delegation shouldn't be slashed) + ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, + time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 4)) keeper.SetUnbondingDelegation(ctx, ubdA) // slash validator @@ -528,17 +498,18 @@ func TestSlashBoth(t *testing.T) { // read updating redelegation rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + require.Len(t, rdA.Entries, 1) // balance decreased - require.Equal(t, sdk.NewInt(3), rdA.Balance.Amount) + require.Equal(t, sdk.NewInt(3), rdA.Entries[0].Balance.Amount) // read updated pool newPool := keeper.GetPool(ctx) - // loose tokens burned - require.Equal(t, int64(2), oldPool.LooseTokens.Sub(newPool.LooseTokens).RoundInt64()) + // not-bonded tokens burned + require.Equal(t, int64(2), oldPool.NotBondedTokens.Sub(newPool.NotBondedTokens).Int64()) // bonded tokens burned - require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).Int64()) // read updated validator validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) require.True(t, found) // power not decreased, all stake was bonded since - require.Equal(t, sdk.NewDec(10), validator.GetPower()) + require.True(sdk.IntEq(t, sdk.NewInt(10), validator.GetPower())) } diff --git a/x/stake/keeper/test_common.go b/x/staking/keeper/test_common.go similarity index 84% rename from x/stake/keeper/test_common.go rename to x/staking/keeper/test_common.go index c3ca811cd6d4..057b82a2a070 100644 --- a/x/stake/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -22,7 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // dummy addresses used for testing @@ -60,15 +60,15 @@ func MakeTestCodec() *codec.Codec { // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) - cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) - cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) - cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) - cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) - cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil) + cdc.RegisterConcrete(bank.MsgSend{}, "test/staking/Send", nil) + cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/staking/CreateValidator", nil) + cdc.RegisterConcrete(types.MsgEditValidator{}, "test/staking/EditValidator", nil) + cdc.RegisterConcrete(types.MsgUndelegate{}, "test/staking/Undelegate", nil) + cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/staking/BeginRedelegate", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) - cdc.RegisterConcrete(&auth.BaseAccount{}, "test/stake/Account", nil) + cdc.RegisterConcrete(&auth.BaseAccount{}, "test/staking/Account", nil) codec.RegisterCrypto(cdc) return cdc @@ -77,16 +77,16 @@ func MakeTestCodec() *codec.Codec { // hogpodge of all sorts of input required for testing func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountKeeper, Keeper) { - keyStake := sdk.NewKVStoreKey("stake") - tkeyStake := sdk.NewTransientStoreKey("transient_stake") - keyAcc := sdk.NewKVStoreKey("acc") - keyParams := sdk.NewKVStoreKey("params") - tkeyParams := sdk.NewTransientStoreKey("transient_params") + keyStaking := sdk.NewKVStoreKey(types.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(types.TStoreKey) + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) - ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) @@ -94,18 +94,27 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) - ctx = ctx.WithConsensusParams(&abci.ConsensusParams{Validator: &abci.ValidatorParams{PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519}}}) + ctx = ctx.WithConsensusParams( + &abci.ConsensusParams{ + Validator: &abci.ValidatorParams{ + PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519}, + }, + }, + ) cdc := MakeTestCodec() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + accountKeeper := auth.NewAccountKeeper( - cdc, // amino codec - keyAcc, // target store + cdc, // amino codec + keyAcc, // target store + pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, // prototype ) ck := bank.NewBaseKeeper(accountKeeper) - pk := params.NewKeeper(cdc, keyParams, tkeyParams) - keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) + keeper := NewKeeper(cdc, keyStaking, tkeyStaking, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) @@ -116,7 +125,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context {keeper.BondDenom(ctx), sdk.NewInt(initCoins)}, }) require.Nil(t, err) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + pool.NotBondedTokens = pool.NotBondedTokens.Add(sdk.NewInt(initCoins)) keeper.SetPool(ctx, pool) } diff --git a/x/stake/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go similarity index 93% rename from x/stake/keeper/val_state_change.go rename to x/staking/keeper/val_state_change.go index e3e13ddceb76..3ddb995fc02a 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -8,14 +8,14 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Apply and return accumulated updates to the bonded validator set. Also, // * Updates the active valset as keyed by LastValidatorPowerKey. // * Updates the total power as keyed by LastTotalPowerKey. // * Updates validator status' according to updated powers. -// * Updates the fee pool bonded vs loose tokens. +// * Updates the fee pool bonded vs not-bonded tokens. // * Updates relevant indices. // It gets called once after genesis, another time maybe after genesis transactions, // then once at every EndBlock. @@ -49,8 +49,7 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // if we get to a zero-power validator (which we don't bond), // there are no more possible bonded validators - // note: we must check the ABCI power, since we round before sending to Tendermint - if validator.Tokens.RoundInt64() == int64(0) { + if validator.Tokens.IsZero() { break } @@ -72,7 +71,7 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab oldPowerBytes, found := last[valAddrBytes] // calculate the new power bytes - newPower := validator.BondedTokens().RoundInt64() + newPower := validator.BondedTokens().Int64() newPowerBytes := k.cdc.MustMarshalBinaryLengthPrefixed(sdk.NewInt(newPower)) // update the validator set if power has changed if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) { @@ -81,7 +80,7 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // Assert that the validator had updated its ValidatorDistInfo.FeePoolWithdrawalHeight. // This hook is extremely useful, otherwise lazy accum bugs will be difficult to solve. if k.hooks != nil { - k.hooks.OnValidatorPowerDidChange(ctx, validator.ConsAddress(), valAddr) + k.hooks.AfterValidatorPowerDidChange(ctx, validator.ConsAddress(), valAddr) } // set validator power on lookup index. @@ -197,7 +196,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. // call the bond hook if present if k.hooks != nil { - k.hooks.OnValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.hooks.AfterValidatorBonded(ctx, validator.ConsAddress(), validator.OperatorAddr) } return validator @@ -220,7 +219,7 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) k.SetPool(ctx, pool) - validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) + validator.UnbondingCompletionTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) validator.UnbondingHeight = ctx.BlockHeader().Height // save the now unbonded validator record and power index @@ -232,7 +231,7 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat // call the unbond hook if present if k.hooks != nil { - k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.hooks.AfterValidatorBeginUnbonding(ctx, validator.ConsAddress(), validator.OperatorAddr) } return validator diff --git a/x/stake/keeper/validator.go b/x/staking/keeper/validator.go similarity index 90% rename from x/stake/keeper/validator.go rename to x/staking/keeper/validator.go index b762e1c4616a..d218ca966c75 100644 --- a/x/stake/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -6,7 +6,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Cache the amino decoding of validators, as it can be the case that repeated slashing calls @@ -36,7 +36,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty } // amino bytes weren't found in cache, so amino unmarshal and add it to the cache - validator = types.MustUnmarshalValidator(k.cdc, addr, value) + validator = types.MustUnmarshalValidator(k.cdc, value) cachedVal := cachedValidator{validator, strValue} k.validatorCache[strValue] = cachedValidator{validator, strValue} k.validatorCacheList.PushBack(cachedVal) @@ -47,7 +47,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty delete(k.validatorCache, valToRemove.marshalled) } - validator = types.MustUnmarshalValidator(k.cdc, addr, value) + validator = types.MustUnmarshalValidator(k.cdc, value) return validator, true } @@ -132,7 +132,7 @@ func (k Keeper) AddValidatorTokensAndShares(ctx sdk.Context, validator types.Val // Update the tokens of an existing validator, update the validators power index key func (k Keeper) RemoveValidatorTokensAndShares(ctx sdk.Context, validator types.Validator, - sharesToRemove sdk.Dec) (valOut types.Validator, removedTokens sdk.Dec) { + sharesToRemove sdk.Dec) (valOut types.Validator, removedTokens sdk.Int) { k.DeleteValidatorByPowerIndex(ctx, validator) pool := k.GetPool(ctx) @@ -144,7 +144,8 @@ func (k Keeper) RemoveValidatorTokensAndShares(ctx sdk.Context, validator types. } // Update the tokens of an existing validator, update the validators power index key -func (k Keeper) RemoveValidatorTokens(ctx sdk.Context, validator types.Validator, tokensToRemove sdk.Dec) types.Validator { +func (k Keeper) RemoveValidatorTokens(ctx sdk.Context, + validator types.Validator, tokensToRemove sdk.Int) types.Validator { k.DeleteValidatorByPowerIndex(ctx, validator) pool := k.GetPool(ctx) @@ -157,7 +158,9 @@ func (k Keeper) RemoveValidatorTokens(ctx sdk.Context, validator types.Validator // UpdateValidatorCommission attempts to update a validator's commission rate. // An error is returned if the new commission rate is invalid. -func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Validator, newRate sdk.Dec) (types.Commission, sdk.Error) { +func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, + validator types.Validator, newRate sdk.Dec) (types.Commission, sdk.Error) { + commission := validator.Commission blockTime := ctx.BlockHeader().Time @@ -188,7 +191,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { // this happens if shares are zero but tokens are not. // TODO: Remove once https://github.com/cosmos/cosmos-sdk/pull/2958 is merged pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(validator.Tokens) + pool.NotBondedTokens = pool.NotBondedTokens.Sub(validator.Tokens) k.SetPool(ctx, pool) // delete the old validator record @@ -199,7 +202,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { // call hook if present if k.hooks != nil { - k.hooks.OnValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddr) + k.hooks.AfterValidatorRemoved(ctx, validator.ConsAddress(), validator.OperatorAddr) } } @@ -214,8 +217,7 @@ func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - addr := iterator.Key()[1:] - validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + validator := types.MustUnmarshalValidator(k.cdc, iterator.Value()) validators = append(validators, validator) } return validators @@ -231,8 +233,7 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators [ i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { - addr := iterator.Key()[1:] - validator := types.MustUnmarshalValidator(k.cdc, addr, iterator.Value()) + validator := types.MustUnmarshalValidator(k.cdc, iterator.Value()) validators[i] = validator i++ } @@ -329,18 +330,19 @@ func (k Keeper) DeleteValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Ti // Insert an validator address to the appropriate timeslice in the validator queue func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { - timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime) + var keys []sdk.ValAddress if len(timeSlice) == 0 { - k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, []sdk.ValAddress{val.OperatorAddr}) + keys = []sdk.ValAddress{val.OperatorAddr} } else { - timeSlice = append(timeSlice, val.OperatorAddr) - k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, timeSlice) + keys = append(timeSlice, val.OperatorAddr) } + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime, keys) } // Delete a validator address from the validator queue func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) { - timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime) newTimeSlice := []sdk.ValAddress{} for _, addr := range timeSlice { if !bytes.Equal(addr, val.OperatorAddr) { @@ -348,16 +350,17 @@ func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) { } } if len(newTimeSlice) == 0 { - k.DeleteValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + k.DeleteValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime) } else { - k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, newTimeSlice) + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingCompletionTime, newTimeSlice) } } // Returns all the validator queue timeslices from time 0 until endTime func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(k.storeKey) - return store.Iterator(ValidatorQueueKey, sdk.InclusiveEndBytes(GetValidatorQueueTimeKey(endTime))) + return store.Iterator(ValidatorQueueKey, + sdk.InclusiveEndBytes(GetValidatorQueueTimeKey(endTime))) } // Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue diff --git a/x/stake/keeper/validator_test.go b/x/staking/keeper/validator_test.go similarity index 93% rename from x/stake/keeper/validator_test.go rename to x/staking/keeper/validator_test.go index e328537a548f..9f605312ae9d 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/staking/keeper/validator_test.go @@ -8,7 +8,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +27,7 @@ func TestSetValidator(t *testing.T) { validator := types.NewValidator(valAddr, valPubKey, types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, sdk.Unbonded, validator.Status) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) keeper.SetPool(ctx, pool) keeper.SetValidator(ctx, validator) @@ -42,7 +42,7 @@ func TestSetValidator(t *testing.T) { // after the save the validator should be bonded require.Equal(t, sdk.Bonded, validator.Status) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) // Check each store for being saved @@ -75,20 +75,20 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { pool := keeper.GetPool(ctx) // create a random pool - pool.LooseTokens = sdk.NewDec(10000) - pool.BondedTokens = sdk.NewDec(1234) + pool.NotBondedTokens = sdk.NewInt(10000) + pool.BondedTokens = sdk.NewInt(1234) keeper.SetPool(ctx, pool) // add a validator validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, sdk.NewInt(100)) require.Equal(t, sdk.Unbonded, validator.Status) - require.Equal(t, int64(100), validator.Tokens.RoundInt64()) + require.Equal(t, int64(100), validator.Tokens.Int64()) keeper.SetPool(ctx, pool) TestingUpdateValidator(keeper, ctx, validator, true) validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) + require.Equal(t, int64(100), validator.Tokens.Int64(), "\nvalidator %v\npool %v", validator, pool) pool = keeper.GetPool(ctx) power := GetValidatorsByPowerIndexKey(validator) @@ -97,7 +97,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { // burn half the delegator shares keeper.DeleteValidatorByPowerIndex(ctx, validator) validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewDec(2))) - require.Equal(t, int64(50), burned.RoundInt64()) + require.Equal(t, int64(50), burned.Int64()) keeper.SetPool(ctx, pool) // update the pool TestingUpdateValidator(keeper, ctx, validator, true) // update the validator, possibly kicking it out require.False(t, validatorByPowerIndexExists(keeper, ctx, power)) @@ -123,8 +123,8 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { keeper.SetParams(ctx, params) // create a random pool - pool.LooseTokens = sdk.NewDec(10000) - pool.BondedTokens = sdk.NewDec(1234) + pool.NotBondedTokens = sdk.NewInt(10000) + pool.BondedTokens = sdk.NewInt(1234) keeper.SetPool(ctx, pool) validators := make([]types.Validator, numVals) @@ -175,11 +175,11 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(100)) require.Equal(t, sdk.Unbonded, validator.Status) - require.Equal(t, int64(100), validator.Tokens.RoundInt64()) + require.Equal(t, int64(100), validator.Tokens.Int64()) keeper.SetPool(ctx, pool) keeper.SetValidatorByConsAddr(ctx, validator) validator = TestingUpdateValidator(keeper, ctx, validator, true) - require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) + require.Equal(t, int64(100), validator.Tokens.Int64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% consAddr0 := sdk.ConsAddress(PKs[0].Address()) @@ -202,13 +202,13 @@ func TestValidatorBasics(t *testing.T) { for i, amt := range amts { validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) validators[i].Status = sdk.Unbonded - validators[i].Tokens = sdk.ZeroDec() + validators[i].Tokens = sdk.ZeroInt() validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - assert.True(sdk.DecEq(t, sdk.NewDec(9), validators[0].Tokens)) - assert.True(sdk.DecEq(t, sdk.NewDec(8), validators[1].Tokens)) - assert.True(sdk.DecEq(t, sdk.NewDec(7), validators[2].Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(9), validators[0].Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(8), validators[1].Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(7), validators[2].Tokens)) // check the empty keeper first _, found := keeper.GetValidator(ctx, addrVals[0]) @@ -220,7 +220,7 @@ func TestValidatorBasics(t *testing.T) { require.Zero(t, len(resVals)) pool = keeper.GetPool(ctx) - assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) + assert.True(sdk.IntEq(t, sdk.ZeroInt(), pool.BondedTokens)) // set and retrieve a record validators[0] = TestingUpdateValidator(keeper, ctx, validators[0], true) @@ -241,14 +241,14 @@ func TestValidatorBasics(t *testing.T) { require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) assert.Equal(t, sdk.Bonded, validators[0].Status) - assert.True(sdk.DecEq(t, sdk.NewDec(9), validators[0].BondedTokens())) + assert.True(sdk.IntEq(t, sdk.NewInt(9), validators[0].BondedTokens())) pool = keeper.GetPool(ctx) - assert.True(sdk.DecEq(t, pool.BondedTokens, validators[0].BondedTokens())) + assert.True(sdk.IntEq(t, pool.BondedTokens, validators[0].BondedTokens())) // modify a records, save, and retrieve validators[0].Status = sdk.Bonded - validators[0].Tokens = sdk.NewDec(10) + validators[0].Tokens = sdk.NewInt(10) validators[0].DelegatorShares = sdk.NewDec(10) validators[0] = TestingUpdateValidator(keeper, ctx, validators[0], true) resVal, found = keeper.GetValidator(ctx, addrVals[0]) @@ -294,7 +294,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { for i, amt := range amts { validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) validators[i].Status = sdk.Bonded - validators[i].Tokens = sdk.NewDec(amt) + validators[i].Tokens = sdk.NewInt(amt) validators[i].DelegatorShares = sdk.NewDec(amt) TestingUpdateValidator(keeper, ctx, validators[i], true) } @@ -302,11 +302,11 @@ func GetValidatorSortingUnmixed(t *testing.T) { // first make sure everything made it in to the gotValidator group resValidators := keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(100), resValidators[2].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(1), resValidators[3].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(0), resValidators[4].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewInt(400), resValidators[0].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewInt(200), resValidators[1].BondedTokens(), "%v", resValidators) + 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) @@ -314,14 +314,14 @@ func GetValidatorSortingUnmixed(t *testing.T) { assert.Equal(t, validators[0].OperatorAddr, resValidators[4].OperatorAddr, "%v", resValidators) // test a basic increase in voting power - validators[3].Tokens = sdk.NewDec(500) + validators[3].Tokens = sdk.NewInt(500) TestingUpdateValidator(keeper, ctx, validators[3], true) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) // test a decrease in voting power - validators[3].Tokens = sdk.NewDec(300) + validators[3].Tokens = sdk.NewInt(300) TestingUpdateValidator(keeper, ctx, validators[3], true) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) @@ -329,7 +329,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { assert.True(ValEq(t, validators[4], resValidators[1])) // test equal voting power, different age - validators[3].Tokens = sdk.NewDec(200) + validators[3].Tokens = sdk.NewInt(200) ctx = ctx.WithBlockHeight(10) TestingUpdateValidator(keeper, ctx, validators[3], true) resValidators = keeper.GetBondedValidatorsByPower(ctx) @@ -348,8 +348,8 @@ func GetValidatorSortingUnmixed(t *testing.T) { assert.True(ValEq(t, validators[4], resValidators[1])) // change in voting power of both validators, both still in v-set, no age change - validators[3].Tokens = sdk.NewDec(300) - validators[4].Tokens = sdk.NewDec(300) + validators[3].Tokens = sdk.NewInt(300) + validators[4].Tokens = sdk.NewInt(300) TestingUpdateValidator(keeper, ctx, validators[3], true) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) @@ -382,14 +382,14 @@ func GetValidatorSortingMixed(t *testing.T) { validators[0].Status = sdk.Bonded validators[1].Status = sdk.Bonded validators[2].Status = sdk.Bonded - validators[0].Tokens = sdk.NewDec(amts[0]) - validators[1].Tokens = sdk.NewDec(amts[1]) - validators[2].Tokens = sdk.NewDec(amts[2]) + validators[0].Tokens = sdk.NewInt(amts[0]) + validators[1].Tokens = sdk.NewInt(amts[1]) + validators[2].Tokens = sdk.NewInt(amts[2]) validators[3].Status = sdk.Bonded validators[4].Status = sdk.Bonded - validators[3].Tokens = sdk.NewDec(amts[3]) - validators[4].Tokens = sdk.NewDec(amts[4]) + validators[3].Tokens = sdk.NewInt(amts[3]) + validators[4].Tokens = sdk.NewInt(amts[4]) for i := range amts { TestingUpdateValidator(keeper, ctx, validators[i], true) @@ -413,11 +413,11 @@ func GetValidatorSortingMixed(t *testing.T) { // first make sure everything made it in to the gotValidator group resValidators := keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(100), resValidators[2].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(1), resValidators[3].BondedTokens(), "%v", resValidators) - assert.Equal(t, sdk.NewDec(0), resValidators[4].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewInt(400), resValidators[0].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewInt(200), resValidators[1].BondedTokens(), "%v", resValidators) + 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) @@ -672,7 +672,7 @@ func TestApplyAndReturnValidatorSetUpdatesSingleValueChange(t *testing.T) { // test single value change // tendermintUpdate set: {} -> {c1'} validators[0].Status = sdk.Bonded - validators[0].Tokens = sdk.NewDec(600) + validators[0].Tokens = sdk.NewInt(600) validators[0] = TestingUpdateValidator(keeper, ctx, validators[0], false) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -810,8 +810,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { require.Equal(t, 2, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // check initial power - require.Equal(t, sdk.NewDec(100).RoundInt64(), validators[0].GetPower().RoundInt64()) - require.Equal(t, sdk.NewDec(100).RoundInt64(), validators[1].GetPower().RoundInt64()) + require.Equal(t, int64(100), validators[0].GetPower().Int64()) + require.Equal(t, int64(100), validators[1].GetPower().Int64()) // test multiple value change // tendermintUpdate set: {c1, c3} -> {c1', c3'} @@ -823,8 +823,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { validators[1] = TestingUpdateValidator(keeper, ctx, validators[1], false) // power has changed - require.Equal(t, sdk.NewDec(80).RoundInt64(), validators[0].GetPower().RoundInt64()) - require.Equal(t, sdk.NewDec(70).RoundInt64(), validators[1].GetPower().RoundInt64()) + require.Equal(t, int64(80), validators[0].GetPower().Int64()) + require.Equal(t, int64(70), validators[1].GetPower().Int64()) // Tendermint updates should reflect power change updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) diff --git a/x/stake/querier/querier.go b/x/staking/querier/querier.go similarity index 79% rename from x/stake/querier/querier.go rename to x/staking/querier/querier.go index 70627f0b14c5..e842c2197f3a 100644 --- a/x/stake/querier/querier.go +++ b/x/staking/querier/querier.go @@ -5,8 +5,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/types" + keep "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // query endpoints supported by the staking Querier @@ -15,10 +15,10 @@ const ( QueryValidator = "validator" QueryDelegatorDelegations = "delegatorDelegations" QueryDelegatorUnbondingDelegations = "delegatorUnbondingDelegations" - QueryDelegatorRedelegations = "delegatorRedelegations" + QueryRedelegations = "redelegations" QueryValidatorDelegations = "validatorDelegations" - QueryValidatorUnbondingDelegations = "validatorUnbondingDelegations" QueryValidatorRedelegations = "validatorRedelegations" + QueryValidatorUnbondingDelegations = "validatorUnbondingDelegations" QueryDelegator = "delegator" QueryDelegation = "delegation" QueryUnbondingDelegation = "unbondingDelegation" @@ -40,8 +40,6 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { return queryValidatorDelegations(ctx, cdc, req, k) case QueryValidatorUnbondingDelegations: return queryValidatorUnbondingDelegations(ctx, cdc, req, k) - case QueryValidatorRedelegations: - return queryValidatorRedelegations(ctx, cdc, req, k) case QueryDelegation: return queryDelegation(ctx, cdc, req, k) case QueryUnbondingDelegation: @@ -50,8 +48,8 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { return queryDelegatorDelegations(ctx, cdc, req, k) case QueryDelegatorUnbondingDelegations: return queryDelegatorUnbondingDelegations(ctx, cdc, req, k) - case QueryDelegatorRedelegations: - return queryDelegatorRedelegations(ctx, cdc, req, k) + case QueryRedelegations: + return queryRedelegations(ctx, cdc, req, k) case QueryDelegatorValidators: return queryDelegatorValidators(ctx, cdc, req, k) case QueryDelegatorValidator: @@ -61,63 +59,76 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { case QueryParameters: return queryParameters(ctx, cdc, k) default: - return nil, sdk.ErrUnknownRequest("unknown stake query endpoint") + return nil, sdk.ErrUnknownRequest("unknown staking query endpoint") } } } // defines the params for the following queries: -// - 'custom/stake/delegatorDelegations' -// - 'custom/stake/delegatorUnbondingDelegations' -// - 'custom/stake/delegatorRedelegations' -// - 'custom/stake/delegatorValidators' +// - 'custom/staking/delegatorDelegations' +// - 'custom/staking/delegatorUnbondingDelegations' +// - 'custom/staking/delegatorRedelegations' +// - 'custom/staking/delegatorValidators' type QueryDelegatorParams struct { DelegatorAddr sdk.AccAddress } +func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} + // defines the params for the following queries: -// - 'custom/stake/validator' -// - 'custom/stake/validatorDelegations' -// - 'custom/stake/validatorUnbondingDelegations' -// - 'custom/stake/validatorRedelegations' +// - 'custom/staking/validator' +// - 'custom/staking/validatorDelegations' +// - 'custom/staking/validatorUnbondingDelegations' +// - 'custom/staking/validatorRedelegations' type QueryValidatorParams struct { ValidatorAddr sdk.ValAddress } +func NewQueryValidatorParams(validatorAddr sdk.ValAddress) QueryValidatorParams { + return QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } +} + // defines the params for the following queries: -// - 'custom/stake/delegation' -// - 'custom/stake/unbondingDelegation' -// - 'custom/stake/delegatorValidator' +// - 'custom/staking/delegation' +// - 'custom/staking/unbondingDelegation' +// - 'custom/staking/delegatorValidator' type QueryBondsParams struct { DelegatorAddr sdk.AccAddress ValidatorAddr sdk.ValAddress } -// creates a new QueryDelegatorParams -func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { - return QueryDelegatorParams{ +func NewQueryBondsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { + return QueryBondsParams{ DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, } } -// creates a new QueryValidatorParams -func NewQueryValidatorParams(validatorAddr sdk.ValAddress) QueryValidatorParams { - return QueryValidatorParams{ - ValidatorAddr: validatorAddr, - } +// defines the params for the following queries: +// - 'custom/staking/redelegation' +type QueryRedelegationParams struct { + DelegatorAddr sdk.AccAddress + SrcValidatorAddr sdk.ValAddress + DstValidatorAddr sdk.ValAddress } -// creates a new QueryBondsParams -func NewQueryBondsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { - return QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, +func NewQueryRedelegationParams(delegatorAddr sdk.AccAddress, srcValidatorAddr sdk.ValAddress, dstValidatorAddr sdk.ValAddress) QueryRedelegationParams { + return QueryRedelegationParams{ + DelegatorAddr: delegatorAddr, + SrcValidatorAddr: srcValidatorAddr, + DstValidatorAddr: dstValidatorAddr, } } func queryValidators(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { - stakeParams := k.GetParams(ctx) - validators := k.GetValidators(ctx, stakeParams.MaxValidators) + stakingParams := k.GetParams(ctx) + validators := k.GetValidators(ctx, stakingParams.MaxValidators) res, errRes := codec.MarshalJSONIndent(cdc, validators) if err != nil { @@ -180,23 +191,6 @@ func queryValidatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req a return res, nil } -func queryValidatorRedelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { - var params QueryValidatorParams - - errRes := cdc.UnmarshalJSON(req.Data, ¶ms) - if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") - } - - redelegations := k.GetRedelegationsFromValidator(ctx, params.ValidatorAddr) - - res, errRes = codec.MarshalJSONIndent(cdc, redelegations) - if errRes != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) - } - return res, nil -} - func queryDelegatorDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { var params QueryDelegatorParams @@ -231,34 +225,17 @@ func queryDelegatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req a return res, nil } -func queryDelegatorRedelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { - var params QueryDelegatorParams - - errRes := cdc.UnmarshalJSON(req.Data, ¶ms) - if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress("") - } - - redelegations := k.GetAllRedelegations(ctx, params.DelegatorAddr) - - res, errRes = codec.MarshalJSONIndent(cdc, redelegations) - if errRes != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) - } - return res, nil -} - func queryDelegatorValidators(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { var params QueryDelegatorParams - stakeParams := k.GetParams(ctx) + stakingParams := k.GetParams(ctx) errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { return []byte{}, sdk.ErrUnknownAddress("") } - validators := k.GetDelegatorValidators(ctx, params.DelegatorAddr, stakeParams.MaxValidators) + validators := k.GetDelegatorValidators(ctx, params.DelegatorAddr, stakingParams.MaxValidators) res, errRes = codec.MarshalJSONIndent(cdc, validators) if errRes != nil { @@ -327,6 +304,35 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques return res, nil } +func queryRedelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryRedelegationParams + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownRequest(string(req.Data)) + } + + var redels []types.Redelegation + + if !params.DelegatorAddr.Empty() && !params.SrcValidatorAddr.Empty() && !params.DstValidatorAddr.Empty() { + redel, found := k.GetRedelegation(ctx, params.DelegatorAddr, params.SrcValidatorAddr, params.DstValidatorAddr) + if !found { + return []byte{}, types.ErrNoRedelegation(types.DefaultCodespace) + } + redels = []types.Redelegation{redel} + } else if params.DelegatorAddr.Empty() && !params.SrcValidatorAddr.Empty() && params.DstValidatorAddr.Empty() { + redels = k.GetRedelegationsFromValidator(ctx, params.SrcValidatorAddr) + } else { + redels = k.GetAllRedelegations(ctx, params.DelegatorAddr, params.SrcValidatorAddr, params.DstValidatorAddr) + } + + res, errRes = codec.MarshalJSONIndent(cdc, redels) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + func queryPool(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { pool := k.GetPool(ctx) diff --git a/x/stake/querier/querier_test.go b/x/staking/querier/querier_test.go similarity index 85% rename from x/stake/querier/querier_test.go rename to x/staking/querier/querier_test.go index 525f3691a33e..3793e119a429 100644 --- a/x/stake/querier/querier_test.go +++ b/x/staking/querier/querier_test.go @@ -8,8 +8,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/types" + keep "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -57,7 +57,7 @@ func TestNewQuerier(t *testing.T) { bz, errRes := cdc.MarshalJSON(queryValParams) require.Nil(t, errRes) - query.Path = "/custom/stake/validator" + query.Path = "/custom/staking/validator" query.Data = bz _, err = querier(ctx, []string{"validator"}, query) @@ -69,14 +69,11 @@ func TestNewQuerier(t *testing.T) { _, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query) require.Nil(t, err) - _, err = querier(ctx, []string{"validatorRedelegations"}, query) - require.Nil(t, err) - queryDelParams := NewQueryDelegatorParams(addrAcc2) bz, errRes = cdc.MarshalJSON(queryDelParams) require.Nil(t, errRes) - query.Path = "/custom/stake/validator" + query.Path = "/custom/staking/validator" query.Data = bz _, err = querier(ctx, []string{"delegatorDelegations"}, query) @@ -85,10 +82,14 @@ func TestNewQuerier(t *testing.T) { _, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query) require.Nil(t, err) - _, err = querier(ctx, []string{"delegatorRedelegations"}, query) + _, err = querier(ctx, []string{"delegatorValidators"}, query) require.Nil(t, err) - _, err = querier(ctx, []string{"delegatorValidators"}, query) + bz, errRes = cdc.MarshalJSON(NewQueryRedelegationParams(nil, nil, nil)) + require.Nil(t, errRes) + query.Data = bz + + _, err = querier(ctx, []string{"redelegations"}, query) require.Nil(t, err) } @@ -149,7 +150,7 @@ func TestQueryValidators(t *testing.T) { require.Nil(t, errRes) query := abci.RequestQuery{ - Path: "/custom/stake/validator", + Path: "/custom/staking/validator", Data: bz, } res, err = queryValidator(ctx, cdc, query, keeper) @@ -187,7 +188,7 @@ func TestQueryDelegation(t *testing.T) { require.Nil(t, errRes) query := abci.RequestQuery{ - Path: "/custom/stake/delegatorValidators", + Path: "/custom/staking/delegatorValidators", Data: bz, } @@ -215,7 +216,7 @@ func TestQueryDelegation(t *testing.T) { require.Nil(t, errRes) query = abci.RequestQuery{ - Path: "/custom/stake/delegatorValidator", + Path: "/custom/staking/delegatorValidator", Data: bz, } @@ -237,7 +238,7 @@ func TestQueryDelegation(t *testing.T) { // Query delegation query = abci.RequestQuery{ - Path: "/custom/stake/delegation", + Path: "/custom/staking/delegation", Data: bz, } @@ -256,7 +257,7 @@ func TestQueryDelegation(t *testing.T) { // Query Delegator Delegations query = abci.RequestQuery{ - Path: "/custom/stake/delegatorDelegations", + Path: "/custom/staking/delegatorDelegations", Data: bz, } @@ -281,7 +282,7 @@ func TestQueryDelegation(t *testing.T) { require.Nil(t, errRes) query = abci.RequestQuery{ - Path: "custom/stake/validatorDelegations", + Path: "custom/staking/validatorDelegations", Data: bz, } @@ -295,14 +296,15 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, delegationsRes[0], delegation) // Query unbonging delegation - keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + _, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + require.Nil(t, err) queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1) bz, errRes = cdc.MarshalJSON(queryBondParams) require.Nil(t, errRes) query = abci.RequestQuery{ - Path: "/custom/stake/unbondingDelegation", + Path: "/custom/staking/unbondingDelegation", Data: bz, } @@ -327,7 +329,7 @@ func TestQueryDelegation(t *testing.T) { // Query Delegator Delegations query = abci.RequestQuery{ - Path: "/custom/stake/delegatorUnbondingDelegations", + Path: "/custom/staking/delegatorUnbondingDelegations", Data: bz, } @@ -344,6 +346,29 @@ func TestQueryDelegation(t *testing.T) { _, err = queryDelegatorUnbondingDelegations(ctx, cdc, query, keeper) require.NotNil(t, err) + + // Query redelegation + _, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr, sdk.NewDec(10)) + require.Nil(t, err) + redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr) + require.True(t, found) + + bz, errRes = cdc.MarshalJSON(NewQueryRedelegationParams(addrAcc2, val1.OperatorAddr, val2.OperatorAddr)) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "/custom/staking/redelegations", + Data: bz, + } + + res, err = queryRedelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var redelRes []types.Redelegation + errRes = cdc.UnmarshalJSON(res, &redelRes) + require.Nil(t, errRes) + + require.Equal(t, redel, redelRes[0]) } func TestQueryRedelegations(t *testing.T) { @@ -357,7 +382,7 @@ func TestQueryRedelegations(t *testing.T) { keeper.SetValidator(ctx, val2) keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(100)), val1, true) - keeper.ApplyAndReturnValidatorSetUpdates(ctx) + _ = keeper.ApplyAndReturnValidatorSetUpdates(ctx) keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), sdk.NewDec(20)) keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -371,11 +396,11 @@ func TestQueryRedelegations(t *testing.T) { require.Nil(t, errRes) query := abci.RequestQuery{ - Path: "/custom/stake/delegatorRedelegations", + Path: "/custom/staking/redelegations", Data: bz, } - res, err := queryDelegatorRedelegations(ctx, cdc, query, keeper) + res, err := queryRedelegations(ctx, cdc, query, keeper) require.Nil(t, err) var redsRes []types.Redelegation @@ -390,11 +415,11 @@ func TestQueryRedelegations(t *testing.T) { require.Nil(t, errRes) query = abci.RequestQuery{ - Path: "/custom/stake/validatorRedelegations", + Path: "/custom/staking/redelegations", Data: bz, } - res, err = queryValidatorRedelegations(ctx, cdc, query, keeper) + res, err = queryRedelegations(ctx, cdc, query, keeper) require.Nil(t, err) errRes = cdc.UnmarshalJSON(res, &redsRes) diff --git a/x/stake/simulation/invariants.go b/x/staking/simulation/invariants.go similarity index 65% rename from x/stake/simulation/invariants.go rename to x/staking/simulation/invariants.go index 7e875171d340..a5759483eb9e 100644 --- a/x/stake/simulation/invariants.go +++ b/x/staking/simulation/invariants.go @@ -9,14 +9,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/keeper" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// AllInvariants runs all invariants of the stake module. +// AllInvariants runs all invariants of the staking module. // Currently: total supply, positive power -func AllInvariants(ck bank.Keeper, k stake.Keeper, +func AllInvariants(ck bank.Keeper, k staking.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { @@ -45,9 +45,9 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, } } -// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations +// SupplyInvariants checks that the total supply reflects all held not-bonded tokens, bonded tokens, and unbonding delegations // nolint: unparam -func SupplyInvariants(ck bank.Keeper, k stake.Keeper, +func SupplyInvariants(ck bank.Keeper, k staking.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { return func(ctx sdk.Context) error { pool := k.GetPool(ctx) @@ -55,21 +55,21 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, loose := sdk.ZeroDec() bonded := sdk.ZeroDec() am.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom))) + loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom))) return false }) - k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool { - loose = loose.Add(sdk.NewDecFromInt(ubd.Balance.Amount)) + k.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) bool { + for _, entry := range ubd.Entries { + loose = loose.Add(sdk.NewDecFromInt(entry.Balance.Amount)) + } return false }) k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { switch validator.GetStatus() { case sdk.Bonded: - bonded = bonded.Add(validator.GetPower()) - case sdk.Unbonding: - loose = loose.Add(validator.GetTokens()) - case sdk.Unbonded: - loose = loose.Add(validator.GetTokens()) + bonded = bonded.Add(sdk.NewDecFromInt(validator.GetPower())) + case sdk.Unbonding, sdk.Unbonded: + loose = loose.Add(sdk.NewDecFromInt(validator.GetTokens())) } return false }) @@ -77,34 +77,27 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, feePool := d.GetFeePool(ctx) // add outstanding fees - loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(stakeTypes.DefaultBondDenom))) + loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(stakingTypes.DefaultBondDenom))) // add community pool - loose = loose.Add(feePool.CommunityPool.AmountOf(stakeTypes.DefaultBondDenom)) - - // add validator distribution pool - loose = loose.Add(feePool.ValPool.AmountOf(stakeTypes.DefaultBondDenom)) + loose = loose.Add(feePool.CommunityPool.AmountOf(stakingTypes.DefaultBondDenom)) - // add validator distribution commission and yet-to-be-withdrawn-by-delegators - d.IterateValidatorDistInfos(ctx, - func(_ int64, distInfo distribution.ValidatorDistInfo) (stop bool) { - loose = loose.Add(distInfo.DelPool.AmountOf(stakeTypes.DefaultBondDenom)) - loose = loose.Add(distInfo.ValCommission.AmountOf(stakeTypes.DefaultBondDenom)) - return false - }, - ) + // add yet-to-be-withdrawn + loose = loose.Add(d.GetOutstandingRewards(ctx).AmountOf(stakingTypes.DefaultBondDenom)) - // Loose tokens should equal coin supply plus unbonding delegations + // Not-bonded tokens should equal coin supply plus unbonding delegations // plus tokens on unbonded validators - if !pool.LooseTokens.Equal(loose) { - return fmt.Errorf("loose token invariance:\n\tpool.LooseTokens: %v"+ - "\n\tsum of account tokens: %v", pool.LooseTokens, loose) + if !sdk.NewDecFromInt(pool.NotBondedTokens).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 !pool.BondedTokens.Equal(bonded) { - return fmt.Errorf("bonded token invariance:\n\tpool.BondedTokens: %v"+ - "\n\tsum of account tokens: %v", pool.BondedTokens, bonded) + if !sdk.NewDecFromInt(pool.BondedTokens).Equal(bonded) { + return fmt.Errorf("bonded token invariance:\n"+ + "\tpool.BondedTokens: %v\n"+ + "\tsum of account tokens: %v", pool.BondedTokens, bonded) } return nil @@ -112,7 +105,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, } // NonNegativePowerInvariant checks that all stored validators have >= 0 power. -func NonNegativePowerInvariant(k stake.Keeper) simulation.Invariant { +func NonNegativePowerInvariant(k staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { iterator := k.ValidatorsPowerStoreIterator(ctx) @@ -129,7 +122,7 @@ func NonNegativePowerInvariant(k stake.Keeper) simulation.Invariant { "\n\tkey should be: %v\n\tkey in store: %v", validator.GetPower(), powerKey, iterator.Key()) } - if validator.Tokens.LT(sdk.ZeroDec()) { + if validator.Tokens.IsNegative() { return fmt.Errorf("negative tokens for validator: %v", validator) } } @@ -139,7 +132,7 @@ func NonNegativePowerInvariant(k stake.Keeper) simulation.Invariant { } // PositiveDelegationInvariant checks that all stored delegations have > 0 shares. -func PositiveDelegationInvariant(k stake.Keeper) simulation.Invariant { +func PositiveDelegationInvariant(k staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { delegations := k.GetAllDelegations(ctx) for _, delegation := range delegations { @@ -158,7 +151,7 @@ func PositiveDelegationInvariant(k stake.Keeper) simulation.Invariant { // DelegatorSharesInvariant checks whether all the delegator shares which persist // in the delegator object add up to the correct total delegator shares // amount stored in each validator -func DelegatorSharesInvariant(k stake.Keeper) simulation.Invariant { +func DelegatorSharesInvariant(k staking.Keeper) simulation.Invariant { return func(ctx sdk.Context) error { validators := k.GetAllValidators(ctx) for _, validator := range validators { diff --git a/x/stake/simulation/msgs.go b/x/staking/simulation/msgs.go similarity index 77% rename from x/stake/simulation/msgs.go rename to x/staking/simulation/msgs.go index 9fa469d6ca5e..7487671eed2f 100644 --- a/x/stake/simulation/msgs.go +++ b/x/staking/simulation/msgs.go @@ -8,24 +8,24 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" ) // SimulateMsgCreateValidator -func SimulateMsgCreateValidator(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) +func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulation.Operation { + handler := staking.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - description := stake.Description{ + description := staking.Description{ Moniker: simulation.RandStringOfLength(r, 10), } maxCommission := sdk.NewDecWithPrec(r.Int63n(1000), 3) - commission := stake.NewCommissionMsg( + commission := staking.NewCommissionMsg( simulation.RandomDecAmount(r, maxCommission), maxCommission, simulation.RandomDecAmount(r, maxCommission), @@ -42,14 +42,9 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k stake.Keeper) simulation return "no-operation", nil, nil } - msg := stake.MsgCreateValidator{ - Description: description, - Commission: commission, - ValidatorAddr: address, - DelegatorAddr: acc.Address, - PubKey: acc.PubKey, - Delegation: sdk.NewCoin(denom, amount), - } + selfDelegation := sdk.NewCoin(denom, amount) + msg := staking.NewMsgCreateValidator(address, acc.PubKey, + selfDelegation, description, commission) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) @@ -61,7 +56,7 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k stake.Keeper) simulation write() } - event(fmt.Sprintf("stake/MsgCreateValidator/%v", result.IsOK())) + event(fmt.Sprintf("staking/MsgCreateValidator/%v", result.IsOK())) // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) @@ -70,13 +65,13 @@ func SimulateMsgCreateValidator(m auth.AccountKeeper, k stake.Keeper) simulation } // SimulateMsgEditValidator -func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) +func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation { + handler := staking.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { - description := stake.Description{ + description := staking.Description{ Moniker: simulation.RandStringOfLength(r, 10), Identity: simulation.RandStringOfLength(r, 10), Website: simulation.RandStringOfLength(r, 10), @@ -87,7 +82,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { address := val.GetOperator() newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate) - msg := stake.MsgEditValidator{ + msg := staking.MsgEditValidator{ Description: description, ValidatorAddr: address, CommissionRate: &newCommissionRate, @@ -102,15 +97,15 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { if result.IsOK() { write() } - event(fmt.Sprintf("stake/MsgEditValidator/%v", result.IsOK())) + event(fmt.Sprintf("staking/MsgEditValidator/%v", result.IsOK())) action = fmt.Sprintf("TestMsgEditValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil } } // SimulateMsgDelegate -func SimulateMsgDelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) +func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation { + handler := staking.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { @@ -127,11 +122,10 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operat if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil, nil } - msg := stake.MsgDelegate{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - Delegation: sdk.NewCoin(denom, amount), - } + + msg := staking.NewMsgDelegate( + delegatorAddress, validatorAddress, sdk.NewCoin(denom, amount)) + if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } @@ -140,15 +134,15 @@ func SimulateMsgDelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operat if result.IsOK() { write() } - event(fmt.Sprintf("stake/MsgDelegate/%v", result.IsOK())) + event(fmt.Sprintf("staking/MsgDelegate/%v", result.IsOK())) action = fmt.Sprintf("TestMsgDelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil } } -// SimulateMsgBeginUnbonding -func SimulateMsgBeginUnbonding(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) +// SimulateMsgUndelegate +func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation { + handler := staking.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { @@ -165,7 +159,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountKeeper, k stake.Keeper) simulation. if numShares.Equal(sdk.ZeroDec()) { return "no-operation", nil, nil } - msg := stake.MsgBeginUnbonding{ + msg := staking.MsgUndelegate{ DelegatorAddr: delegatorAddress, ValidatorAddr: delegation.ValidatorAddr, SharesAmount: numShares, @@ -179,15 +173,15 @@ func SimulateMsgBeginUnbonding(m auth.AccountKeeper, k stake.Keeper) simulation. if result.IsOK() { write() } - event(fmt.Sprintf("stake/MsgBeginUnbonding/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgBeginUnbonding: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + event(fmt.Sprintf("staking/MsgUndelegate/%v", result.IsOK())) + action = fmt.Sprintf("TestMsgUndelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil } } // SimulateMsgBeginRedelegate -func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) +func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation { + handler := staking.NewHandler(k) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) ( action string, fOp []simulation.FutureOperation, err error) { @@ -207,7 +201,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil, nil } - msg := stake.MsgBeginRedelegate{ + msg := staking.MsgBeginRedelegate{ DelegatorAddr: delegatorAddress, ValidatorSrcAddr: srcValidatorAddress, ValidatorDstAddr: destValidatorAddress, @@ -221,7 +215,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation if result.IsOK() { write() } - event(fmt.Sprintf("stake/MsgBeginRedelegate/%v", result.IsOK())) + event(fmt.Sprintf("staking/MsgBeginRedelegate/%v", result.IsOK())) action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) return action, nil, nil } diff --git a/x/stake/stake.go b/x/staking/staking.go similarity index 78% rename from x/stake/stake.go rename to x/staking/staking.go index f5a029a728d2..53d37fd06cfc 100644 --- a/x/stake/stake.go +++ b/x/staking/staking.go @@ -1,32 +1,37 @@ // nolint -package stake +package staking import ( - "github.com/cosmos/cosmos-sdk/x/stake/keeper" - "github.com/cosmos/cosmos-sdk/x/stake/querier" - "github.com/cosmos/cosmos-sdk/x/stake/tags" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/querier" + "github.com/cosmos/cosmos-sdk/x/staking/tags" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) type ( - Keeper = keeper.Keeper - Validator = types.Validator - Description = types.Description - Commission = types.Commission - Delegation = types.Delegation - UnbondingDelegation = types.UnbondingDelegation - Redelegation = types.Redelegation - Params = types.Params - Pool = types.Pool - MsgCreateValidator = types.MsgCreateValidator - MsgEditValidator = types.MsgEditValidator - MsgDelegate = types.MsgDelegate - MsgBeginUnbonding = types.MsgBeginUnbonding - MsgBeginRedelegate = types.MsgBeginRedelegate - GenesisState = types.GenesisState - QueryDelegatorParams = querier.QueryDelegatorParams - QueryValidatorParams = querier.QueryValidatorParams - QueryBondsParams = querier.QueryBondsParams + Keeper = keeper.Keeper + Validator = types.Validator + Validators = types.Validators + Description = types.Description + Commission = types.Commission + Delegation = types.Delegation + Delegations = types.Delegations + UnbondingDelegation = types.UnbondingDelegation + UnbondingDelegations = types.UnbondingDelegations + Redelegation = types.Redelegation + Redelegations = types.Redelegations + Params = types.Params + Pool = types.Pool + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgUndelegate = types.MsgUndelegate + MsgBeginRedelegate = types.MsgBeginRedelegate + GenesisState = types.GenesisState + QueryDelegatorParams = querier.QueryDelegatorParams + QueryValidatorParams = querier.QueryValidatorParams + QueryBondsParams = querier.QueryBondsParams + QueryRedelegationParams = querier.QueryRedelegationParams ) var ( @@ -66,6 +71,7 @@ var ( KeyBondDenom = types.KeyBondDenom DefaultParams = types.DefaultParams + DefaultBondDenom = types.DefaultBondDenom InitialPool = types.InitialPool NewValidator = types.NewValidator NewDescription = types.NewDescription @@ -80,7 +86,7 @@ var ( NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf NewMsgEditValidator = types.NewMsgEditValidator NewMsgDelegate = types.NewMsgDelegate - NewMsgBeginUnbonding = types.NewMsgBeginUnbonding + NewMsgUndelegate = types.NewMsgUndelegate NewMsgBeginRedelegate = types.NewMsgBeginRedelegate NewQuerier = querier.NewQuerier @@ -92,13 +98,14 @@ var ( const ( QueryValidators = querier.QueryValidators QueryValidator = querier.QueryValidator - QueryValidatorUnbondingDelegations = querier.QueryValidatorUnbondingDelegations + QueryValidatorDelegations = querier.QueryValidatorDelegations QueryValidatorRedelegations = querier.QueryValidatorRedelegations + QueryValidatorUnbondingDelegations = querier.QueryValidatorUnbondingDelegations QueryDelegation = querier.QueryDelegation QueryUnbondingDelegation = querier.QueryUnbondingDelegation QueryDelegatorDelegations = querier.QueryDelegatorDelegations QueryDelegatorUnbondingDelegations = querier.QueryDelegatorUnbondingDelegations - QueryDelegatorRedelegations = querier.QueryDelegatorRedelegations + QueryRedelegations = querier.QueryRedelegations QueryDelegatorValidators = querier.QueryDelegatorValidators QueryDelegatorValidator = querier.QueryDelegatorValidator QueryPool = querier.QueryPool @@ -106,6 +113,10 @@ const ( ) const ( + StoreKey = types.StoreKey + TStoreKey = types.TStoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey DefaultCodespace = types.DefaultCodespace CodeInvalidValidator = types.CodeInvalidValidator CodeInvalidDelegation = types.CodeInvalidDelegation diff --git a/x/stake/tags/tags.go b/x/staking/tags/tags.go similarity index 100% rename from x/stake/tags/tags.go rename to x/staking/tags/tags.go diff --git a/x/stake/test_common.go b/x/staking/test_common.go similarity index 75% rename from x/stake/test_common.go rename to x/staking/test_common.go index 88077d18bcda..d13aab892561 100644 --- a/x/stake/test_common.go +++ b/x/staking/test_common.go @@ -1,4 +1,4 @@ -package stake +package staking import ( "github.com/tendermint/tendermint/crypto" @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -20,7 +20,7 @@ var ( coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} fee = auth.NewStdFee( 100000, - sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., + sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}, ) commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) @@ -43,20 +43,11 @@ func NewTestMsgCreateValidatorWithCommission(address sdk.ValAddress, pubKey cryp } func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)), - } + amount := sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)) + return NewMsgDelegate(delAddr, valAddr, amount) } func NewTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { - return MsgCreateValidator{ - Description: Description{}, - Commission: commissionMsg, - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - PubKey: valPubKey, - Delegation: sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)), - } + amount := sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(amt)) + return NewMsgCreateValidatorOnBehalfOf(delAddr, valAddr, valPubKey, amount, Description{}, commissionMsg) } diff --git a/x/stake/types/codec.go b/x/staking/types/codec.go similarity index 89% rename from x/stake/types/codec.go rename to x/staking/types/codec.go index bc3764ffe901..c290e0d14c8d 100644 --- a/x/stake/types/codec.go +++ b/x/staking/types/codec.go @@ -9,7 +9,7 @@ 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(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil) + cdc.RegisterConcrete(MsgUndelegate{}, "cosmos-sdk/Undelegate", nil) cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) } diff --git a/x/stake/types/commission.go b/x/staking/types/commission.go similarity index 100% rename from x/stake/types/commission.go rename to x/staking/types/commission.go diff --git a/x/staking/types/delegation.go b/x/staking/types/delegation.go new file mode 100644 index 000000000000..e46f89624633 --- /dev/null +++ b/x/staking/types/delegation.go @@ -0,0 +1,342 @@ +package types + +import ( + "bytes" + "fmt" + "strings" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// DVPair is struct that just has a delegator-validator pair with no other data. +// 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 +} + +// 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 +} + +//_______________________________________________________________________ + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type Delegation struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.ValAddress `json:"validator_addr"` + Shares sdk.Dec `json:"shares"` +} + +// NewDelegation creates a new delegation object +func NewDelegation(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress, + shares sdk.Dec) Delegation { + + return Delegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Shares: shares, + } +} + +// return the delegation +func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { + return cdc.MustMarshalBinaryLengthPrefixed(delegation) +} + +// return the delegation +func MustUnmarshalDelegation(cdc *codec.Codec, value []byte) Delegation { + delegation, err := UnmarshalDelegation(cdc, value) + if err != nil { + panic(err) + } + return delegation +} + +// return the delegation +func UnmarshalDelegation(cdc *codec.Codec, value []byte) (delegation Delegation, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(value, &delegation) + return delegation, err +} + +// nolint +func (d Delegation) Equal(d2 Delegation) bool { + return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && + bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && + d.Shares.Equal(d2.Shares) +} + +// ensure fulfills the sdk validator types +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) GetShares() sdk.Dec { return d.Shares } + +// String returns a human readable string representation of a Delegation. +func (d Delegation) String() string { + return fmt.Sprintf(`Delegation: + Delegator: %s + Validator: %s + Shares: %s`, d.DelegatorAddr, + d.ValidatorAddr, d.Shares) +} + +// Delegations is a collection of delegations +type Delegations []Delegation + +func (d Delegations) String() (out string) { + for _, del := range d { + out += del.String() + "\n" + } + return strings.TrimSpace(out) +} + +//________________________________________________________________________ + +// UnbondingDelegation reflects a delegation's passive unbonding queue. +// it may hold multiple entries between the same delegator/validator +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 +} + +// UnbondingDelegationEntry - entry to an UnbondingDelegation +type UnbondingDelegationEntry struct { + CreationHeight int64 `json:"creation_height"` // height which the unbonding took place + CompletionTime time.Time `json:"completion_time"` // unix time for unbonding completion + InitialBalance sdk.Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion + Balance sdk.Coin `json:"balance"` // atoms to receive at completion +} + +// IsMature - is the current entry mature +func (e UnbondingDelegationEntry) IsMature(currentTime time.Time) bool { + return !e.CompletionTime.After(currentTime) +} + +// NewUnbondingDelegation - create a new unbonding delegation object +func NewUnbondingDelegation(delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress, creationHeight int64, minTime time.Time, + balance sdk.Coin) UnbondingDelegation { + + entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance) + return UnbondingDelegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Entries: []UnbondingDelegationEntry{entry}, + } +} + +// NewUnbondingDelegation - create a new unbonding delegation object +func NewUnbondingDelegationEntry(creationHeight int64, completionTime time.Time, + balance sdk.Coin) UnbondingDelegationEntry { + + return UnbondingDelegationEntry{ + CreationHeight: creationHeight, + CompletionTime: completionTime, + InitialBalance: balance, + Balance: balance, + } +} + +// AddEntry - append entry to the unbonding delegation +func (d *UnbondingDelegation) AddEntry(creationHeight int64, + minTime time.Time, balance sdk.Coin) { + + entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance) + d.Entries = append(d.Entries, entry) +} + +// RemoveEntry - remove entry at index i to the unbonding delegation +func (d *UnbondingDelegation) RemoveEntry(i int64) { + d.Entries = append(d.Entries[:i], d.Entries[i+1:]...) +} + +// return the unbonding delegation +func MustMarshalUBD(cdc *codec.Codec, ubd UnbondingDelegation) []byte { + return cdc.MustMarshalBinaryLengthPrefixed(ubd) +} + +// unmarshal a unbonding delegation from a store value +func MustUnmarshalUBD(cdc *codec.Codec, value []byte) UnbondingDelegation { + ubd, err := UnmarshalUBD(cdc, value) + if err != nil { + panic(err) + } + return ubd +} + +// unmarshal a unbonding delegation from a store value +func UnmarshalUBD(cdc *codec.Codec, value []byte) (ubd UnbondingDelegation, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(value, &ubd) + return ubd, err +} + +// nolint +func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) + return bytes.Equal(bz1, bz2) +} + +// String returns a human readable string representation of an UnbondingDelegation. +func (d UnbondingDelegation) String() string { + out := fmt.Sprintf(`Unbonding Delegations between: + Delegator: %s + Validator: %s + Entries:`, d.DelegatorAddr, d.ValidatorAddr) + for i, entry := range d.Entries { + out += fmt.Sprintf(` Unbonding Delegation %d: + Creation Height: %v + Min time to unbond (unix): %v + Expected balance: %s`, i, entry.CreationHeight, + entry.CompletionTime, entry.Balance) + } + return out +} + +// UnbondingDelegations is a collection of UnbondingDelegation +type UnbondingDelegations []UnbondingDelegation + +func (ubds UnbondingDelegations) String() (out string) { + for _, u := range ubds { + out += u.String() + "\n" + } + return strings.TrimSpace(out) +} + +// Redelegation reflects a delegation's passive re-delegation queue. +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 +} + +// RedelegationEntry - entry to a Redelegation +type RedelegationEntry struct { + CreationHeight int64 `json:"creation_height"` // height which the redelegation took place + CompletionTime time.Time `json:"completion_time"` // unix time for redelegation completion + InitialBalance sdk.Coin `json:"initial_balance"` // initial balance when redelegation started + Balance sdk.Coin `json:"balance"` // current balance (current value held in destination validator) + SharesSrc sdk.Dec `json:"shares_src"` // amount of source-validator shares removed by redelegation + SharesDst sdk.Dec `json:"shares_dst"` // amount of destination-validator shares created by redelegation +} + +// NewRedelegation - create a new redelegation object +func NewRedelegation(delegatorAddr sdk.AccAddress, validatorSrcAddr, + validatorDstAddr sdk.ValAddress, creationHeight int64, + minTime time.Time, balance sdk.Coin, + sharesSrc, sharesDst sdk.Dec) Redelegation { + + entry := NewRedelegationEntry(creationHeight, + minTime, balance, sharesSrc, sharesDst) + + return Redelegation{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + Entries: []RedelegationEntry{entry}, + } +} + +// NewRedelegation - create a new redelegation object +func NewRedelegationEntry(creationHeight int64, + completionTime time.Time, balance sdk.Coin, + sharesSrc, sharesDst sdk.Dec) RedelegationEntry { + + return RedelegationEntry{ + CreationHeight: creationHeight, + CompletionTime: completionTime, + InitialBalance: balance, + Balance: balance, + SharesSrc: sharesSrc, + SharesDst: sharesDst, + } +} + +// IsMature - is the current entry mature +func (e RedelegationEntry) IsMature(currentTime time.Time) bool { + return !e.CompletionTime.After(currentTime) +} + +// AddEntry - append entry to the unbonding delegation +func (d *Redelegation) AddEntry(creationHeight int64, + minTime time.Time, balance sdk.Coin, + sharesSrc, sharesDst sdk.Dec) { + + entry := NewRedelegationEntry(creationHeight, minTime, balance, sharesSrc, sharesDst) + d.Entries = append(d.Entries, entry) +} + +// RemoveEntry - remove entry at index i to the unbonding delegation +func (d *Redelegation) RemoveEntry(i int64) { + d.Entries = append(d.Entries[:i], d.Entries[i+1:]...) +} + +// return the redelegation +func MustMarshalRED(cdc *codec.Codec, red Redelegation) []byte { + return cdc.MustMarshalBinaryLengthPrefixed(red) +} + +// unmarshal a redelegation from a store value +func MustUnmarshalRED(cdc *codec.Codec, value []byte) Redelegation { + red, err := UnmarshalRED(cdc, value) + if err != nil { + panic(err) + } + return red +} + +// unmarshal a redelegation from a store value +func UnmarshalRED(cdc *codec.Codec, value []byte) (red Redelegation, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(value, &red) + return red, err +} + +// nolint +func (d Redelegation) Equal(d2 Redelegation) bool { + bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d) + bz2 := MsgCdc.MustMarshalBinaryLengthPrefixed(&d2) + return bytes.Equal(bz1, bz2) +} + +// String returns a human readable string representation of a Redelegation. +func (d Redelegation) String() string { + out := fmt.Sprintf(`Redelegations between: + Delegator: %s + Source Validator: %s + Destination Validator: %s + Entries:`, d.DelegatorAddr, d.ValidatorSrcAddr, d.ValidatorDstAddr) + for i, entry := range d.Entries { + out += fmt.Sprintf(` Redelegation %d: + Creation height: %v + Min time to unbond (unix): %v + Source shares: %s + Dest Shares: %s`, i, entry.CreationHeight, + entry.CompletionTime, entry.SharesSrc, entry.SharesDst) + } + return out +} + +// Redelegations are a collection of Redelegation +type Redelegations []Redelegation + +func (d Redelegations) String() (out string) { + for _, red := range d { + out += red.String() + "\n" + } + return strings.TrimSpace(out) +} diff --git a/x/staking/types/delegation_test.go b/x/staking/types/delegation_test.go new file mode 100644 index 000000000000..a2a637685a06 --- /dev/null +++ b/x/staking/types/delegation_test.go @@ -0,0 +1,78 @@ +package types + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestDelegationEqual(t *testing.T) { + d1 := NewDelegation(sdk.AccAddress(addr1), addr2, sdk.NewDec(100)) + d2 := d1 + + ok := d1.Equal(d2) + require.True(t, ok) + + d2.ValidatorAddr = addr3 + d2.Shares = sdk.NewDec(200) + + ok = d1.Equal(d2) + require.False(t, ok) +} + +func TestDelegationString(t *testing.T) { + d := NewDelegation(sdk.AccAddress(addr1), addr2, sdk.NewDec(100)) + require.NotEmpty(t, d.String()) +} + +func TestUnbondingDelegationEqual(t *testing.T) { + ubd1 := NewUnbondingDelegation(sdk.AccAddress(addr1), addr2, 0, + time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0)) + ubd2 := ubd1 + + ok := ubd1.Equal(ubd2) + require.True(t, ok) + + ubd2.ValidatorAddr = addr3 + + ubd2.Entries[0].CompletionTime = time.Unix(20*20*2, 0) + ok = ubd1.Equal(ubd2) + require.False(t, ok) +} + +func TestUnbondingDelegationString(t *testing.T) { + ubd := NewUnbondingDelegation(sdk.AccAddress(addr1), addr2, 0, + time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0)) + + require.NotEmpty(t, ubd.String()) +} + +func TestRedelegationEqual(t *testing.T) { + r1 := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0, + time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0), + sdk.NewDec(0), sdk.NewDec(0)) + r2 := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0, + time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0), + sdk.NewDec(0), sdk.NewDec(0)) + + ok := r1.Equal(r2) + require.True(t, ok) + + r2.Entries[0].SharesDst = sdk.NewDec(10) + r2.Entries[0].SharesSrc = sdk.NewDec(20) + r2.Entries[0].CompletionTime = time.Unix(20*20*2, 0) + + ok = r1.Equal(r2) + require.False(t, ok) +} + +func TestRedelegationString(t *testing.T) { + r := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0, + time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0), + sdk.NewDec(10), sdk.NewDec(20)) + + require.NotEmpty(t, r.String()) +} diff --git a/x/stake/types/errors.go b/x/staking/types/errors.go similarity index 96% rename from x/stake/types/errors.go rename to x/staking/types/errors.go index c944b489b826..2ed4a2411f84 100644 --- a/x/stake/types/errors.go +++ b/x/staking/types/errors.go @@ -12,7 +12,7 @@ import ( type CodeType = sdk.CodeType const ( - DefaultCodespace sdk.CodespaceType = "STAKE" + DefaultCodespace sdk.CodespaceType = "staking" CodeInvalidValidator CodeType = 101 CodeInvalidDelegation CodeType = 102 @@ -178,11 +178,6 @@ func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") } -func ErrConflictingRedelegation(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDelegation, - "conflicting redelegation from this source validator to this dest validator already exists, you must wait for it to finish") -} - func ErrDelegatorShareExRateInvalid(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to validators with invalid (zero) ex-rate") diff --git a/x/stake/types/genesis.go b/x/staking/types/genesis.go similarity index 100% rename from x/stake/types/genesis.go rename to x/staking/types/genesis.go diff --git a/x/staking/types/keys.go b/x/staking/types/keys.go new file mode 100644 index 000000000000..aa1b92d46def --- /dev/null +++ b/x/staking/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // StoreKey is the string store representation + StoreKey = "staking" + + // TStoreKey is the string transient store representation + TStoreKey = "transient_staking" + + // QuerierRoute is the querier route for the staking module + QuerierRoute = "staking" + + // RouterKey is the msg router key for the staking module + RouterKey = "staking" +) diff --git a/x/stake/types/msg.go b/x/staking/types/msg.go similarity index 68% rename from x/stake/types/msg.go rename to x/staking/types/msg.go index a6692a7f3f3b..9ef062104675 100644 --- a/x/stake/types/msg.go +++ b/x/staking/types/msg.go @@ -2,28 +2,41 @@ package types import ( "bytes" + "encoding/json" "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" ) -// name to identify transaction routes -const MsgRoute = "stake" - -// Verify interface at compile time -var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} +// ensure Msg interface compliance at compile time +var ( + _ sdk.Msg = &MsgCreateValidator{} + _ sdk.Msg = &MsgEditValidator{} + _ sdk.Msg = &MsgDelegate{} + _ sdk.Msg = &MsgUndelegate{} + _ sdk.Msg = &MsgBeginRedelegate{} +) //______________________________________________________________________ // MsgCreateValidator - struct for bonding transactions type MsgCreateValidator struct { - Description - Commission CommissionMsg + Description Description `json:"description"` + Commission CommissionMsg `json:"commission"` DelegatorAddr sdk.AccAddress `json:"delegator_address"` ValidatorAddr sdk.ValAddress `json:"validator_address"` PubKey crypto.PubKey `json:"pubkey"` - Delegation sdk.Coin `json:"delegation"` + Value sdk.Coin `json:"value"` +} + +type msgCreateValidatorJSON struct { + Description Description `json:"description"` + Commission CommissionMsg `json:"commission"` + DelegatorAddr sdk.AccAddress `json:"delegator_address"` + ValidatorAddr 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 @@ -37,19 +50,19 @@ func NewMsgCreateValidator(valAddr sdk.ValAddress, pubkey crypto.PubKey, // Creates validator msg by delegator address on behalf of validator address func NewMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, - pubkey crypto.PubKey, delegation sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { + pubkey crypto.PubKey, value sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { return MsgCreateValidator{ Description: description, DelegatorAddr: delAddr, ValidatorAddr: valAddr, PubKey: pubkey, - Delegation: delegation, + Value: value, Commission: commission, } } //nolint -func (msg MsgCreateValidator) Route() string { return MsgRoute } +func (msg MsgCreateValidator) Route() string { return RouterKey } func (msg MsgCreateValidator) Type() string { return "create_validator" } // Return address(es) that must sign over msg.GetSignBytes() @@ -65,25 +78,41 @@ func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress { return addrs } -// get the bytes for the message signer to sign on -func (msg MsgCreateValidator) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(struct { - Description - Commission CommissionMsg - DelegatorAddr sdk.AccAddress `json:"delegator_address"` - ValidatorAddr sdk.ValAddress `json:"validator_address"` - PubKey string `json:"pubkey"` - Delegation sdk.Coin `json:"delegation"` - }{ +// MarshalJSON implements the json.Marshaler interface to provide custom JSON +// serialization of the MsgCreateValidator type. +func (msg MsgCreateValidator) MarshalJSON() ([]byte, error) { + return json.Marshal(msgCreateValidatorJSON{ Description: msg.Description, + Commission: msg.Commission, + DelegatorAddr: msg.DelegatorAddr, ValidatorAddr: msg.ValidatorAddr, PubKey: sdk.MustBech32ifyConsPub(msg.PubKey), - Delegation: msg.Delegation, + Value: msg.Value, }) - if err != nil { - panic(err) +} + +// UnmarshalJSON implements the json.Unmarshaler interface to provide custom +// JSON deserialization of the MsgCreateValidator type. +func (msg *MsgCreateValidator) UnmarshalJSON(bz []byte) error { + var msgCreateValJSON msgCreateValidatorJSON + if err := json.Unmarshal(bz, &msgCreateValJSON); err != nil { + return err } - return sdk.MustSortJSON(b) + + msg.Description = msgCreateValJSON.Description + msg.Commission = msgCreateValJSON.Commission + msg.DelegatorAddr = msgCreateValJSON.DelegatorAddr + msg.ValidatorAddr = msgCreateValJSON.ValidatorAddr + msg.PubKey = sdk.MustGetConsPubKeyBech32(msgCreateValJSON.PubKey) + msg.Value = msgCreateValJSON.Value + + return nil +} + +// GetSignBytes returns the message bytes to sign over. +func (msg MsgCreateValidator) GetSignBytes() []byte { + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -94,7 +123,7 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrNilValidatorAddr(DefaultCodespace) } - if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { + if !(msg.Value.Amount.GT(sdk.ZeroInt())) { return ErrBadDelegationAmount(DefaultCodespace) } if msg.Description == (Description{}) { @@ -131,7 +160,7 @@ func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRat } //nolint -func (msg MsgEditValidator) Route() string { return MsgRoute } +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)} @@ -139,17 +168,8 @@ func (msg MsgEditValidator) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgEditValidator) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(struct { - Description - ValidatorAddr sdk.ValAddress `json:"address"` - }{ - Description: msg.Description, - ValidatorAddr: msg.ValidatorAddr, - }) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -171,19 +191,19 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error { type MsgDelegate struct { DelegatorAddr sdk.AccAddress `json:"delegator_addr"` ValidatorAddr sdk.ValAddress `json:"validator_addr"` - Delegation sdk.Coin `json:"delegation"` + Value sdk.Coin `json:"value"` } -func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delegation sdk.Coin) MsgDelegate { +func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, value sdk.Coin) MsgDelegate { return MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: delegation, + Value: value, } } //nolint -func (msg MsgDelegate) Route() string { return MsgRoute } +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} @@ -191,11 +211,8 @@ func (msg MsgDelegate) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgDelegate) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -206,7 +223,7 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return ErrNilValidatorAddr(DefaultCodespace) } - if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { + if !(msg.Value.Amount.GT(sdk.ZeroInt())) { return ErrBadDelegationAmount(DefaultCodespace) } return nil @@ -234,7 +251,7 @@ func NewMsgBeginRedelegate(delAddr sdk.AccAddress, valSrcAddr, } //nolint -func (msg MsgBeginRedelegate) Route() string { return MsgRoute } +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} @@ -242,21 +259,8 @@ func (msg MsgBeginRedelegate) GetSigners() []sdk.AccAddress { // get the bytes for the message signer to sign on func (msg MsgBeginRedelegate) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` - ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` - SharesAmount string `json:"shares"` - }{ - DelegatorAddr: msg.DelegatorAddr, - ValidatorSrcAddr: msg.ValidatorSrcAddr, - ValidatorDstAddr: msg.ValidatorDstAddr, - SharesAmount: msg.SharesAmount.String(), - }) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check @@ -278,15 +282,15 @@ func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { //______________________________________________________________________ -// MsgBeginUnbonding - struct for unbonding transactions -type MsgBeginUnbonding struct { +// 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"` } -func NewMsgBeginUnbonding(delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgBeginUnbonding { - return MsgBeginUnbonding{ +func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgUndelegate { + return MsgUndelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, SharesAmount: sharesAmount, @@ -294,29 +298,18 @@ func NewMsgBeginUnbonding(delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares } //nolint -func (msg MsgBeginUnbonding) Route() string { return MsgRoute } -func (msg MsgBeginUnbonding) Type() string { return "begin_unbonding" } -func (msg MsgBeginUnbonding) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr} } +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} } // get the bytes for the message signer to sign on -func (msg MsgBeginUnbonding) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` - SharesAmount string `json:"shares_amount"` - }{ - DelegatorAddr: msg.DelegatorAddr, - ValidatorAddr: msg.ValidatorAddr, - SharesAmount: msg.SharesAmount.String(), - }) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) +func (msg MsgUndelegate) GetSignBytes() []byte { + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // quick validity check -func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { +func (msg MsgUndelegate) ValidateBasic() sdk.Error { if msg.DelegatorAddr == nil { return ErrNilDelegatorAddr(DefaultCodespace) } diff --git a/x/stake/types/msg_test.go b/x/staking/types/msg_test.go similarity index 98% rename from x/stake/types/msg_test.go rename to x/staking/types/msg_test.go index e5cb8a9f09c5..6eba60437783 100644 --- a/x/stake/types/msg_test.go +++ b/x/staking/types/msg_test.go @@ -173,7 +173,7 @@ func TestMsgBeginRedelegate(t *testing.T) { } // test ValidateBasic for MsgUnbond -func TestMsgBeginUnbonding(t *testing.T) { +func TestMsgUndelegate(t *testing.T) { tests := []struct { name string delegatorAddr sdk.AccAddress @@ -189,7 +189,7 @@ func TestMsgBeginUnbonding(t *testing.T) { } for _, tc := range tests { - msg := NewMsgBeginUnbonding(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) + msg := NewMsgUndelegate(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { diff --git a/x/stake/types/params.go b/x/staking/types/params.go similarity index 78% rename from x/stake/types/params.go rename to x/staking/types/params.go index 4e9aba5ab020..7120085e046e 100644 --- a/x/stake/types/params.go +++ b/x/staking/types/params.go @@ -21,7 +21,7 @@ const ( ValidatorUpdateDelay int64 = 1 // Default bondable coin denomination - DefaultBondDenom = "STAKE" + DefaultBondDenom = "stake" ) // nolint - Keys for parameter access @@ -35,10 +35,9 @@ var _ params.ParamSet = (*Params)(nil) // Params defines the high level settings for staking type Params struct { - UnbondingTime time.Duration `json:"unbonding_time"` - - MaxValidators uint16 `json:"max_validators"` // maximum number of validators - BondDenom string `json:"bond_denom"` // bondable coin denomination + UnbondingTime time.Duration `json:"unbonding_time"` // time duration of unbonding + MaxValidators uint16 `json:"max_validators"` // maximum number of validators + BondDenom string `json:"bond_denom"` // bondable coin denomination } // Implements params.ParamSet @@ -66,15 +65,13 @@ func DefaultParams() Params { } } -// HumanReadableString returns a human readable string representation of the -// parameters. -func (p Params) HumanReadableString() string { - - resp := "Params \n" - resp += fmt.Sprintf("Unbonding Time: %s\n", p.UnbondingTime) - resp += fmt.Sprintf("Max Validators: %d\n", p.MaxValidators) - resp += fmt.Sprintf("Bonded Coin Denomination: %s\n", p.BondDenom) - return resp +// String returns a human readable string representation of the parameters. +func (p Params) String() string { + return fmt.Sprintf(`Params: + Unbonding Time: %s) + Max Validators: %d) + Bonded Coin Denomination: %s`, p.UnbondingTime, + p.MaxValidators, p.BondDenom) } // unmarshal the current staking params value from store key or panic diff --git a/x/stake/types/params_test.go b/x/staking/types/params_test.go similarity index 100% rename from x/stake/types/params_test.go rename to x/staking/types/params_test.go diff --git a/x/stake/types/pool.go b/x/staking/types/pool.go similarity index 56% rename from x/stake/types/pool.go rename to x/staking/types/pool.go index 4b227aa8051e..3f56285204ad 100644 --- a/x/stake/types/pool.go +++ b/x/staking/types/pool.go @@ -10,8 +10,8 @@ import ( // Pool - dynamic parameters of the current state type Pool struct { - LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator - BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens + NotBondedTokens sdk.Int `json:"not_bonded_tokens"` // tokens which are not bonded in a validator + BondedTokens sdk.Int `json:"bonded_tokens"` // reserve of bonded tokens } // nolint @@ -24,16 +24,16 @@ func (p Pool) Equal(p2 Pool) bool { // initial pool for testing func InitialPool() Pool { return Pool{ - LooseTokens: sdk.ZeroDec(), - BondedTokens: sdk.ZeroDec(), + NotBondedTokens: sdk.ZeroInt(), + BondedTokens: sdk.ZeroInt(), } } //____________________________________________________________________ // Sum total of all staking tokens in the pool -func (p Pool) TokenSupply() sdk.Dec { - return p.LooseTokens.Add(p.BondedTokens) +func (p Pool) TokenSupply() sdk.Int { + return p.NotBondedTokens.Add(p.BondedTokens) } //____________________________________________________________________ @@ -41,42 +41,42 @@ func (p Pool) TokenSupply() sdk.Dec { // get the bond ratio of the global state func (p Pool) BondedRatio() sdk.Dec { supply := p.TokenSupply() - if supply.GT(sdk.ZeroDec()) { - return p.BondedTokens.Quo(supply) + if supply.IsPositive() { + return sdk.NewDecFromInt(p.BondedTokens). + QuoInt(supply) } return sdk.ZeroDec() } //_______________________________________________________________________ -func (p Pool) looseTokensToBonded(bondedTokens sdk.Dec) Pool { +func (p Pool) notBondedTokensToBonded(bondedTokens sdk.Int) Pool { p.BondedTokens = p.BondedTokens.Add(bondedTokens) - p.LooseTokens = p.LooseTokens.Sub(bondedTokens) - if p.LooseTokens.LT(sdk.ZeroDec()) { - panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + p.NotBondedTokens = p.NotBondedTokens.Sub(bondedTokens) + if p.NotBondedTokens.IsNegative() { + panic(fmt.Sprintf("sanity check: not-bonded tokens negative, pool: %v", p)) } return p } -func (p Pool) bondedTokensToLoose(bondedTokens sdk.Dec) Pool { +func (p Pool) bondedTokensToNotBonded(bondedTokens sdk.Int) Pool { p.BondedTokens = p.BondedTokens.Sub(bondedTokens) - p.LooseTokens = p.LooseTokens.Add(bondedTokens) - if p.BondedTokens.LT(sdk.ZeroDec()) { + p.NotBondedTokens = p.NotBondedTokens.Add(bondedTokens) + if p.BondedTokens.IsNegative() { panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p)) } return p } -// HumanReadableString returns a human readable string representation of a -// pool. -func (p Pool) HumanReadableString() string { - - resp := "Pool \n" - resp += fmt.Sprintf("Loose Tokens: %s\n", p.LooseTokens) - resp += fmt.Sprintf("Bonded Tokens: %s\n", p.BondedTokens) - resp += fmt.Sprintf("Token Supply: %s\n", p.TokenSupply()) - resp += fmt.Sprintf("Bonded Ratio: %v\n", p.BondedRatio()) - return resp +// String returns a human readable string representation of a pool. +func (p Pool) String() string { + return fmt.Sprintf(`Pool: + Loose Tokens: %s + Bonded Tokens: %s + Token Supply: %s + Bonded Ratio: %v`, p.NotBondedTokens, + p.BondedTokens, p.TokenSupply(), + p.BondedRatio()) } // unmarshal the current pool value from store key or panics diff --git a/x/staking/types/pool_test.go b/x/staking/types/pool_test.go new file mode 100644 index 000000000000..ab0838fa6ff3 --- /dev/null +++ b/x/staking/types/pool_test.go @@ -0,0 +1,39 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestPoolEqual(t *testing.T) { + p1 := InitialPool() + p2 := InitialPool() + require.True(t, p1.Equal(p2)) + p2.BondedTokens = sdk.NewInt(3) + require.False(t, p1.Equal(p2)) +} + +func TestAddBondedTokens(t *testing.T) { + pool := InitialPool() + pool.NotBondedTokens = sdk.NewInt(10) + pool.BondedTokens = sdk.NewInt(10) + + pool = pool.notBondedTokensToBonded(sdk.NewInt(10)) + + require.True(sdk.IntEq(t, sdk.NewInt(20), pool.BondedTokens)) + require.True(sdk.IntEq(t, sdk.NewInt(0), pool.NotBondedTokens)) +} + +func TestRemoveBondedTokens(t *testing.T) { + pool := InitialPool() + pool.NotBondedTokens = sdk.NewInt(10) + pool.BondedTokens = sdk.NewInt(10) + + pool = pool.bondedTokensToNotBonded(sdk.NewInt(5)) + + require.True(sdk.IntEq(t, sdk.NewInt(5), pool.BondedTokens)) + require.True(sdk.IntEq(t, sdk.NewInt(15), pool.NotBondedTokens)) +} diff --git a/x/stake/types/test_utils.go b/x/staking/types/test_utils.go similarity index 100% rename from x/stake/types/test_utils.go rename to x/staking/types/test_utils.go diff --git a/x/stake/types/validator.go b/x/staking/types/validator.go similarity index 58% rename from x/stake/types/validator.go rename to x/staking/types/validator.go index 517671c3a2a1..ea8dafe2bed1 100644 --- a/x/stake/types/validator.go +++ b/x/staking/types/validator.go @@ -3,6 +3,7 @@ package types import ( "bytes" "fmt" + "strings" "time" abci "github.com/tendermint/tendermint/abci/types" @@ -26,125 +27,86 @@ type Validator struct { Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) - Tokens sdk.Dec `json:"tokens"` // delegated tokens (incl. self-delegation) + Tokens sdk.Int `json:"tokens"` // delegated tokens (incl. self-delegation) DelegatorShares sdk.Dec `json:"delegator_shares"` // total shares issued to a validator's delegators Description Description `json:"description"` // description terms for the validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator - UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding - UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding + UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding + UnbondingCompletionTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding Commission Commission `json:"commission"` // commission parameters } +// Validators is a collection of Validator +type Validators []Validator + +func (v Validators) String() (out string) { + for _, val := range v { + out += val.String() + "\n" + } + return strings.TrimSpace(out) +} + // NewValidator - initialize a new validator func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { return Validator{ - OperatorAddr: operator, - ConsPubKey: pubKey, - Jailed: false, - Status: sdk.Unbonded, - Tokens: sdk.ZeroDec(), - DelegatorShares: sdk.ZeroDec(), - Description: description, - BondHeight: int64(0), - UnbondingHeight: int64(0), - UnbondingMinTime: time.Unix(0, 0).UTC(), - Commission: NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + OperatorAddr: operator, + ConsPubKey: pubKey, + Jailed: false, + Status: sdk.Unbonded, + Tokens: sdk.ZeroInt(), + DelegatorShares: sdk.ZeroDec(), + Description: description, + BondHeight: int64(0), + UnbondingHeight: int64(0), + UnbondingCompletionTime: time.Unix(0, 0).UTC(), + Commission: NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), } } -// what's kept in the store value -type validatorValue struct { - ConsPubKey crypto.PubKey - Jailed bool - Status sdk.BondStatus - Tokens sdk.Dec - DelegatorShares sdk.Dec - Description Description - BondHeight int64 - UnbondingHeight int64 - UnbondingMinTime time.Time - Commission Commission -} - -// return the redelegation without fields contained within the key for the store +// return the redelegation func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { - val := validatorValue{ - ConsPubKey: validator.ConsPubKey, - Jailed: validator.Jailed, - Status: validator.Status, - Tokens: validator.Tokens, - DelegatorShares: validator.DelegatorShares, - Description: validator.Description, - BondHeight: validator.BondHeight, - UnbondingHeight: validator.UnbondingHeight, - UnbondingMinTime: validator.UnbondingMinTime, - Commission: validator.Commission, - } - return cdc.MustMarshalBinaryLengthPrefixed(val) + return cdc.MustMarshalBinaryLengthPrefixed(validator) } -// unmarshal a redelegation from a store key and value -func MustUnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) Validator { - validator, err := UnmarshalValidator(cdc, operatorAddr, value) +// unmarshal a redelegation from a store value +func MustUnmarshalValidator(cdc *codec.Codec, value []byte) Validator { + validator, err := UnmarshalValidator(cdc, value) if err != nil { panic(err) } return validator } -// unmarshal a redelegation from a store key and value -func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator Validator, err error) { - if len(operatorAddr) != sdk.AddrLen { - err = fmt.Errorf("%v", ErrBadValidatorAddr(DefaultCodespace).Data()) - return - } - var storeValue validatorValue - err = cdc.UnmarshalBinaryLengthPrefixed(value, &storeValue) - if err != nil { - return - } - - return Validator{ - OperatorAddr: operatorAddr, - ConsPubKey: storeValue.ConsPubKey, - Jailed: storeValue.Jailed, - Tokens: storeValue.Tokens, - Status: storeValue.Status, - DelegatorShares: storeValue.DelegatorShares, - Description: storeValue.Description, - BondHeight: storeValue.BondHeight, - UnbondingHeight: storeValue.UnbondingHeight, - UnbondingMinTime: storeValue.UnbondingMinTime, - Commission: storeValue.Commission, - }, nil +// unmarshal a redelegation from a store value +func UnmarshalValidator(cdc *codec.Codec, value []byte) (validator Validator, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(value, &validator) + return validator, err } -// HumanReadableString returns a human readable string representation of a -// validator. An error is returned if the operator or the operator's public key -// cannot be converted to Bech32 format. -func (v Validator) HumanReadableString() (string, error) { +// String returns a human readable string representation of a validator. +func (v Validator) String() string { bechConsPubKey, err := sdk.Bech32ifyConsPub(v.ConsPubKey) if err != nil { - return "", err + panic(err) } - - resp := "Validator \n" - resp += fmt.Sprintf("Operator Address: %s\n", v.OperatorAddr) - resp += fmt.Sprintf("Validator Consensus Pubkey: %s\n", bechConsPubKey) - resp += fmt.Sprintf("Jailed: %v\n", v.Jailed) - resp += fmt.Sprintf("Status: %s\n", sdk.BondStatusToString(v.Status)) - resp += fmt.Sprintf("Tokens: %s\n", v.Tokens) - resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares) - resp += fmt.Sprintf("Description: %s\n", v.Description) - resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) - resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight) - resp += fmt.Sprintf("Minimum Unbonding Time: %v\n", v.UnbondingMinTime) - resp += fmt.Sprintf("Commission: {%s}\n", v.Commission) - - return resp, nil + return fmt.Sprintf(`Validator + Operator Address: %s + Validator Consensus Pubkey: %s + Jailed: %v + Status: %s + Tokens: %s + Delegator Shares: %s + Description: %s + Bond Height: %d + Unbonding Height: %d + Unbonding Completion Time: %v + Commission: %s`, v.OperatorAddr, bechConsPubKey, + v.Jailed, sdk.BondStatusToString(v.Status), v.Tokens, + v.DelegatorShares, v.Description, v.BondHeight, + v.UnbondingHeight, v.UnbondingCompletionTime, v.Commission) } //___________________________________________________________________ @@ -156,14 +118,14 @@ type bechValidator struct { Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) - Tokens sdk.Dec `json:"tokens"` // delegated tokens (incl. self-delegation) + Tokens sdk.Int `json:"tokens"` // delegated tokens (incl. self-delegation) DelegatorShares sdk.Dec `json:"delegator_shares"` // total shares issued to a validator's delegators Description Description `json:"description"` // description terms for the validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator - UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding - UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding + UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding + UnbondingCompletionTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding Commission Commission `json:"commission"` // commission parameters } @@ -176,17 +138,17 @@ func (v Validator) MarshalJSON() ([]byte, error) { } return codec.Cdc.MarshalJSON(bechValidator{ - OperatorAddr: v.OperatorAddr, - ConsPubKey: bechConsPubKey, - Jailed: v.Jailed, - Status: v.Status, - Tokens: v.Tokens, - DelegatorShares: v.DelegatorShares, - Description: v.Description, - BondHeight: v.BondHeight, - UnbondingHeight: v.UnbondingHeight, - UnbondingMinTime: v.UnbondingMinTime, - Commission: v.Commission, + OperatorAddr: v.OperatorAddr, + ConsPubKey: bechConsPubKey, + Jailed: v.Jailed, + Status: v.Status, + Tokens: v.Tokens, + DelegatorShares: v.DelegatorShares, + Description: v.Description, + BondHeight: v.BondHeight, + UnbondingHeight: v.UnbondingHeight, + UnbondingCompletionTime: v.UnbondingCompletionTime, + Commission: v.Commission, }) } @@ -201,17 +163,17 @@ func (v *Validator) UnmarshalJSON(data []byte) error { return err } *v = Validator{ - OperatorAddr: bv.OperatorAddr, - ConsPubKey: consPubKey, - Jailed: bv.Jailed, - Tokens: bv.Tokens, - Status: bv.Status, - DelegatorShares: bv.DelegatorShares, - Description: bv.Description, - BondHeight: bv.BondHeight, - UnbondingHeight: bv.UnbondingHeight, - UnbondingMinTime: bv.UnbondingMinTime, - Commission: bv.Commission, + OperatorAddr: bv.OperatorAddr, + ConsPubKey: consPubKey, + Jailed: bv.Jailed, + Tokens: bv.Tokens, + Status: bv.Status, + DelegatorShares: bv.DelegatorShares, + Description: bv.Description, + BondHeight: bv.BondHeight, + UnbondingHeight: bv.UnbondingHeight, + UnbondingCompletionTime: bv.UnbondingCompletionTime, + Commission: bv.Commission, } return nil } @@ -302,7 +264,7 @@ func (d Description) EnsureLength() (Description, sdk.Error) { func (v Validator) ABCIValidatorUpdate() abci.ValidatorUpdate { return abci.ValidatorUpdate{ PubKey: tmtypes.TM2PB.PubKey(v.ConsPubKey), - Power: v.BondedTokens().RoundInt64(), + Power: v.BondedTokens().Int64(), } } @@ -326,7 +288,7 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, case sdk.Unbonded: return v, pool case sdk.Bonded: - pool = pool.looseTokensToBonded(v.Tokens) + pool = pool.notBondedTokensToBonded(v.Tokens) } case sdk.Unbonding: @@ -334,7 +296,7 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, case sdk.Unbonding: return v, pool case sdk.Bonded: - pool = pool.looseTokensToBonded(v.Tokens) + pool = pool.notBondedTokensToBonded(v.Tokens) } case sdk.Bonded: @@ -342,7 +304,7 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, case sdk.Bonded: return v, pool default: - pool = pool.bondedTokensToLoose(v.Tokens) + pool = pool.bondedTokensToNotBonded(v.Tokens) } } @@ -351,7 +313,7 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, } // removes tokens from a validator -func (v Validator) RemoveTokens(pool Pool, tokens sdk.Dec) (Validator, Pool) { +func (v Validator) RemoveTokens(pool Pool, tokens sdk.Int) (Validator, Pool) { if tokens.IsNegative() { panic(fmt.Sprintf("should not happen: trying to remove negative tokens %v", tokens)) } @@ -360,7 +322,7 @@ func (v Validator) RemoveTokens(pool Pool, tokens sdk.Dec) (Validator, Pool) { } v.Tokens = v.Tokens.Sub(tokens) if v.Status == sdk.Bonded { - pool = pool.bondedTokensToLoose(tokens) + pool = pool.bondedTokensToNotBonded(tokens) } return v, pool } @@ -383,29 +345,50 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, // bondedShare/delegatedShare exRate := v.DelegatorShareExRate() - amountDec := sdk.NewDecFromInt(amount) + if exRate.IsZero() { + panic("zero exRate should not happen") + } if v.Status == sdk.Bonded { - pool = pool.looseTokensToBonded(amountDec) + pool = pool.notBondedTokensToBonded(amount) } - if exRate.IsZero() { - panic("zero exRate should not happen") - } - v.Tokens = v.Tokens.Add(amountDec) - issuedShares := amountDec.Quo(exRate) + v.Tokens = v.Tokens.Add(amount) + issuedShares := sdk.NewDecFromInt(amount).Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedShares) return v, pool, issuedShares } // RemoveDelShares removes delegator shares from a validator. -func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Pool, sdk.Dec) { - delTokens := v.DelegatorShareExRate().Mul(delShares) - delTokens = sdk.MinDec(delTokens, v.Tokens) - v, pool = v.RemoveTokens(pool, delTokens) - v.DelegatorShares = v.DelegatorShares.Sub(delShares) - return v, pool, delTokens +// NOTE: because token fractions are left in the valiadator, +// the exchange rate of future shares of this validator can increase. +func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Pool, sdk.Int) { + + remainingShares := v.DelegatorShares.Sub(delShares) + var issuedTokens sdk.Int + if remainingShares.IsZero() { + + // last delegation share gets any trimmings + issuedTokens = v.Tokens + v.Tokens = sdk.ZeroInt() + } else { + + // leave excess tokens in the validator + // however fully use all the delegator shares + issuedTokens = v.DelegatorShareExRate().Mul(delShares).TruncateInt() + v.Tokens = v.Tokens.Sub(issuedTokens) + if v.Tokens.IsNegative() { + panic("attempting to remove more tokens than available in validator") + } + } + + v.DelegatorShares = remainingShares + if v.Status == sdk.Bonded { + pool = pool.bondedTokensToNotBonded(issuedTokens) + } + + return v, pool, issuedTokens } // DelegatorShareExRate gets the exchange rate of tokens over delegator shares. @@ -414,15 +397,15 @@ func (v Validator) DelegatorShareExRate() sdk.Dec { if v.DelegatorShares.IsZero() { return sdk.OneDec() } - return v.Tokens.Quo(v.DelegatorShares) + return sdk.NewDecFromInt(v.Tokens).Quo(v.DelegatorShares) } // Get the bonded tokens which the validator holds -func (v Validator) BondedTokens() sdk.Dec { +func (v Validator) BondedTokens() sdk.Int { if v.Status == sdk.Bonded { return v.Tokens } - return sdk.ZeroDec() + return sdk.ZeroInt() } //______________________________________________________________________ @@ -431,14 +414,15 @@ func (v Validator) BondedTokens() sdk.Dec { 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) GetPower() sdk.Dec { return v.BondedTokens() } -func (v Validator) GetTokens() sdk.Dec { return v.Tokens } -func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } -func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } -func (v Validator) GetBondHeight() int64 { return v.BondHeight } +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) GetPower() sdk.Int { return v.BondedTokens() } +func (v Validator) GetTokens() sdk.Int { return v.Tokens } +func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } +func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } +func (v Validator) GetBondHeight() int64 { return v.BondHeight } +func (v Validator) GetDelegatorShareExRate() sdk.Dec { return v.DelegatorShareExRate() } diff --git a/x/stake/types/validator_test.go b/x/staking/types/validator_test.go similarity index 71% rename from x/stake/types/validator_test.go rename to x/staking/types/validator_test.go index b01b2b7444fc..2beba8e671d4 100644 --- a/x/stake/types/validator_test.go +++ b/x/staking/types/validator_test.go @@ -59,7 +59,7 @@ func TestABCIValidatorUpdate(t *testing.T) { abciVal := validator.ABCIValidatorUpdate() require.Equal(t, tmtypes.TM2PB.PubKey(validator.ConsPubKey), abciVal.PubKey) - require.Equal(t, validator.BondedTokens().RoundInt64(), abciVal.Power) + require.Equal(t, validator.BondedTokens().Int64(), abciVal.Power) } func TestABCIValidatorUpdateZero(t *testing.T) { @@ -76,38 +76,38 @@ func TestRemoveTokens(t *testing.T) { OperatorAddr: addr1, ConsPubKey: pk1, Status: sdk.Bonded, - Tokens: sdk.NewDec(100), + Tokens: sdk.NewInt(100), DelegatorShares: sdk.NewDec(100), } pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) + pool.NotBondedTokens = sdk.NewInt(10) pool.BondedTokens = validator.BondedTokens() validator, pool = validator.UpdateStatus(pool, sdk.Bonded) require.Equal(t, sdk.Bonded, validator.Status) // remove tokens and test check everything - validator, pool = validator.RemoveTokens(pool, sdk.NewDec(10)) - require.Equal(t, int64(90), validator.Tokens.RoundInt64()) - require.Equal(t, int64(90), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(20), pool.LooseTokens.RoundInt64()) + validator, pool = validator.RemoveTokens(pool, sdk.NewInt(10)) + require.Equal(t, int64(90), validator.Tokens.Int64()) + require.Equal(t, int64(90), pool.BondedTokens.Int64()) + require.Equal(t, int64(20), pool.NotBondedTokens.Int64()) // update validator to unbonded and remove some more tokens validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) require.Equal(t, sdk.Unbonded, validator.Status) - require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(110), pool.LooseTokens.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens.Int64()) + require.Equal(t, int64(110), pool.NotBondedTokens.Int64()) - validator, pool = validator.RemoveTokens(pool, sdk.NewDec(10)) - require.Equal(t, int64(80), validator.Tokens.RoundInt64()) - require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(110), pool.LooseTokens.RoundInt64()) + validator, pool = validator.RemoveTokens(pool, sdk.NewInt(10)) + require.Equal(t, int64(80), validator.Tokens.Int64()) + require.Equal(t, int64(0), pool.BondedTokens.Int64()) + require.Equal(t, int64(110), pool.NotBondedTokens.Int64()) } func TestAddTokensValidatorBonded(t *testing.T) { pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) + pool.NotBondedTokens = sdk.NewInt(10) validator := NewValidator(addr1, pk1, Description{}) validator, pool = validator.UpdateStatus(pool, sdk.Bonded) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) @@ -115,12 +115,12 @@ func TestAddTokensValidatorBonded(t *testing.T) { require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.BondedTokens())) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens())) } func TestAddTokensValidatorUnbonding(t *testing.T) { pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) + pool.NotBondedTokens = sdk.NewInt(10) validator := NewValidator(addr1, pk1, Description{}) validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) @@ -129,12 +129,12 @@ func TestAddTokensValidatorUnbonding(t *testing.T) { assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.Equal(t, sdk.Unbonding, validator.Status) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) } func TestAddTokensValidatorUnbonded(t *testing.T) { pool := InitialPool() - pool.LooseTokens = sdk.NewDec(10) + pool.NotBondedTokens = sdk.NewInt(10) validator := NewValidator(addr1, pk1, Description{}) validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) @@ -143,7 +143,7 @@ func TestAddTokensValidatorUnbonded(t *testing.T) { assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.Equal(t, sdk.Unbonded, validator.Status) - assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) + assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) } // TODO refactor to make simpler like the AddToken tests above @@ -152,29 +152,29 @@ func TestRemoveDelShares(t *testing.T) { OperatorAddr: addr1, ConsPubKey: pk1, Status: sdk.Bonded, - Tokens: sdk.NewDec(100), + Tokens: sdk.NewInt(100), DelegatorShares: sdk.NewDec(100), } poolA := InitialPool() - poolA.LooseTokens = sdk.NewDec(10) + 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)) - assert.Equal(t, int64(10), coinsB.RoundInt64()) - assert.Equal(t, int64(90), valB.DelegatorShares.RoundInt64()) - assert.Equal(t, int64(90), valB.BondedTokens().RoundInt64()) - assert.Equal(t, int64(90), poolB.BondedTokens.RoundInt64()) - assert.Equal(t, int64(20), poolB.LooseTokens.RoundInt64()) + require.Equal(t, int64(10), coinsB.Int64()) + require.Equal(t, int64(90), valB.DelegatorShares.RoundInt64()) + require.Equal(t, int64(90), valB.BondedTokens().Int64()) + require.Equal(t, int64(90), poolB.BondedTokens.Int64()) + require.Equal(t, int64(20), poolB.NotBondedTokens.Int64()) // conservation of tokens - require.True(sdk.DecEq(t, - poolB.LooseTokens.Add(poolB.BondedTokens), - poolA.LooseTokens.Add(poolA.BondedTokens))) + require.True(sdk.IntEq(t, + poolB.NotBondedTokens.Add(poolB.BondedTokens), + poolA.NotBondedTokens.Add(poolA.BondedTokens))) // specific case from random tests - poolTokens := sdk.NewDec(5102) + poolTokens := sdk.NewInt(5102) delShares := sdk.NewDec(115) validator := Validator{ OperatorAddr: addr1, @@ -184,48 +184,45 @@ func TestRemoveDelShares(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - BondedTokens: sdk.NewDec(248305), - LooseTokens: sdk.NewDec(232147), + BondedTokens: sdk.NewInt(248305), + NotBondedTokens: sdk.NewInt(232147), } shares := sdk.NewDec(29) _, newPool, tokens := validator.RemoveDelShares(pool, shares) - exp, err := sdk.NewDecFromStr("1286.5913043477") - require.NoError(t, err) - - require.True(sdk.DecEq(t, exp, tokens)) + require.True(sdk.IntEq(t, sdk.NewInt(1286), tokens)) - require.True(sdk.DecEq(t, - newPool.LooseTokens.Add(newPool.BondedTokens), - pool.LooseTokens.Add(pool.BondedTokens))) + require.True(sdk.IntEq(t, + newPool.NotBondedTokens.Add(newPool.BondedTokens), + pool.NotBondedTokens.Add(pool.BondedTokens))) } func TestUpdateStatus(t *testing.T) { pool := InitialPool() - pool.LooseTokens = sdk.NewDec(100) + pool.NotBondedTokens = sdk.NewInt(100) validator := NewValidator(addr1, pk1, Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(100)) require.Equal(t, sdk.Unbonded, validator.Status) - require.Equal(t, int64(100), validator.Tokens.RoundInt64()) - require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(100), pool.LooseTokens.RoundInt64()) + require.Equal(t, int64(100), validator.Tokens.Int64()) + require.Equal(t, int64(0), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.NotBondedTokens.Int64()) validator, pool = validator.UpdateStatus(pool, sdk.Bonded) require.Equal(t, sdk.Bonded, validator.Status) - require.Equal(t, int64(100), validator.Tokens.RoundInt64()) - require.Equal(t, int64(100), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(0), pool.LooseTokens.RoundInt64()) + require.Equal(t, int64(100), validator.Tokens.Int64()) + require.Equal(t, int64(100), pool.BondedTokens.Int64()) + require.Equal(t, int64(0), pool.NotBondedTokens.Int64()) validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) require.Equal(t, sdk.Unbonding, validator.Status) - require.Equal(t, int64(100), validator.Tokens.RoundInt64()) - require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) - require.Equal(t, int64(100), pool.LooseTokens.RoundInt64()) + require.Equal(t, int64(100), validator.Tokens.Int64()) + require.Equal(t, int64(0), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.NotBondedTokens.Int64()) } func TestPossibleOverflow(t *testing.T) { - poolTokens := sdk.NewDec(2159) + poolTokens := sdk.NewInt(2159) delShares := sdk.NewDec(391432570689183511).Quo(sdk.NewDec(40113011844664)) validator := Validator{ OperatorAddr: addr1, @@ -235,8 +232,8 @@ func TestPossibleOverflow(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - LooseTokens: sdk.NewDec(100), - BondedTokens: poolTokens, + NotBondedTokens: sdk.NewInt(100), + BondedTokens: poolTokens, } tokens := int64(71) msg := fmt.Sprintf("validator %#v", validator) @@ -248,16 +245,6 @@ func TestPossibleOverflow(t *testing.T) { msg, newValidator.DelegatorShareExRate()) } -func TestHumanReadableString(t *testing.T) { - validator := NewValidator(addr1, pk1, Description{}) - - // NOTE: Being that the validator's keypair is random, we cannot test the - // actual contents of the string. - valStr, err := validator.HumanReadableString() - require.Nil(t, err) - require.NotEmpty(t, valStr) -} - func TestValidatorMarshalUnmarshalJSON(t *testing.T) { validator := NewValidator(addr1, pk1, Description{}) js, err := codec.Cdc.MarshalJSON(validator)